#include <stdio.h>
#include <time.h>
#include <string.h>
#include <ctype.h>
// oslib
#include "os.h"
#include "sound.h"

#include "midiutils.h"
#include "midi.h"
#include "main.h"


struct tracks tracks[MAXTRACKS];
struct channelinfo channelinfo[16];
struct soundchannels soundchannels[9];

char voicemap[128], drummap[128], internalmap[33];
int transposemap[128], drumtranspose[128];
os_dynamic_area_no eventarea = 0;

int TRACKS, TICKSPERQUARTER, CURRENTBEAT, TEMPO, PLAYING;
int CHANNELS, RATE, POSITION, NEXTEVENT, EVENTS, STARTTIME;
soundevent *START;


// set up voice-maps
void midi_init(char *map, char *voices, int defaultvoice) {

  FILE *file;
  char line[256], name[256];
  int i, midi, transp, voice;

  eventarea = 0;
  TEMPO = 500000;
  PLAYING = 0;

  // default transpose map
  for (i = 1; i <= 128; i++)   transposemap[i] = 0;

  // default mapping of midi voices to internal voices
  for (i =   0; i <   8; i++)  voicemap[i] = 2;
  for (i =   8; i <  16; i++)  voicemap[i] = 1;
  for (i =  16; i <  24; i++)  voicemap[i] = 1;
  for (i =  24; i <  32; i++)  voicemap[i] = 4;
  for (i =  32; i <  40; i++)  voicemap[i] = 2;
  for (i =  32; i <  40; i++)  transposemap[i] = -12;
  for (i =  40; i <  48; i++)  voicemap[i] = 5;
  for (i =  48; i <  56; i++)  voicemap[i] = 2;
  for (i =  56; i <  64; i++)  voicemap[i] = 4;
  for (i =  64; i <  72; i++)  voicemap[i] = 1;
  for (i =  72; i <  80; i++)  voicemap[i] = 2;
  for (i =  80; i <  88; i++)  voicemap[i] = 2;
  for (i =  88; i <  96; i++)  voicemap[i] = 2;
  for (i =  96; i < 104; i++)  voicemap[i] = 2;
  for (i = 104; i < 112; i++)  voicemap[i] = 2;
  for (i = 112; i < 120; i++)  voicemap[i] = 6;
  for (i = 120; i < 128; i++)  voicemap[i] = 6;

//  for (i = 0; i < 128; i++)  voicemap[i] = 9;

  // default mapping of drum notes to internal voices
  for (i =   0; i <  35; i++)  drummap[i] = 7;
  for (i =  35; i <  56; i++)  drummap[i] = 8;
  for (i =  56; i <  69; i++)  drummap[i] = 7;
  for (i =  69; i <  82; i++)  drummap[i] = 6;
  for (i =  82; i < 128; i++)  drummap[i] = 7;

  // read mapping from midi voices to internal voices
  if (map) {
    file = fopen(map, "r");
    if (file) {
      while ((!feof(file)) && (fgets(line, 200, file))) {
        if ((line[0] != '#') && (line[0] >= 32) &&
            (sscanf(line, "%d %d %d", &midi, &transp, &voice) == 3)) {
          if ((voice >= 1) && (voice <= 32) && (transp >= -24) && (transp <= 24)) {
            if ((midi >= 1) && (midi <= 128)) {
              voicemap[midi-1] = voice;
              transposemap[midi-1] = transp;
            } else if ((midi <= -1) && (midi >= -128)) {
              drummap[-midi-1] = voice;
              drumtranspose[-midi-1] = transp;
            }
          }
        }
      }
      fclose(file);
    }
  }

  // read mapping from internal voice numbers to sound system voice names/numbers
  for (i = 0; i <= 32; i++)  internalmap[i] = 2;
  if (voices) {
    file = fopen(voices, "r");
    if (file) {
      while ((!feof(file)) && (fgets(line, 200, file))) {
        if ((line[0] != '#') && (line[0] >= 32) &&
            (sscanf(line, "%d %s", &voice, (char *)name) == 2)) {
              if ((voice >= 0) && (voice < 32))
                internalmapping(voice, (char *)name);
        }
      }
      fclose(file);
    }
  }

  // default state of each midi channel
  for (i =  0; i < 16; i++) {
    channelinfo[i].internalvoice = defaultvoice;
    channelinfo[i].midiinstrument = 0;
    channelinfo[i].pan = 64;
    channelinfo[i].volume = 100;
    channelinfo[i].transpose = 0;
  }
}


// stop playback
void midi_stop() {

  if (eventarea)  xosdynamicarea_delete(eventarea);
  reset_soundsystem();
}


// parse file, start playback
// midiptr          pointer to midi-file in memory
// midisize         size of midi-file
// totalduration    returns the duration in centiseconds
int midi_start(byte *midiptr, int midisize, int *totalduration) {

  int id, len, format, track, size, totalevents;
  int time, nexttrack, event, duration;
  byte *midi;
  midievent *eventptr, *eventbuffers;
  soundevent *sound;
  os_dynamic_area_no area;
  double timescale;

  reset_soundsystem();
  init_soundsystem();

  midi = midiptr;
  midi = readword(midi, &id);
  midi = readword(midi, &len);
  midi = readshort(midi, &format);
  midi = readshort(midi, &TRACKS);
  midi = readshort(midi, &TICKSPERQUARTER);

  if ((id != 0x4d546864) || (format >= 2) || (TRACKS > MAXTRACKS))   return MIDI_NOT_OK;

  // read track pointers and reset track info
  for (track = 0; track < TRACKS; track++) {
    midi = readword(midi, &id);
    midi = readword(midi, &size);
    tracks[track].start = midi;
    tracks[track].end = midi + size;
    tracks[track].events = 0;
    tracks[track].nextevent = 0;
    midi = tracks[track].end;
  }

  // count events on each track
  totalevents = 0;
  for (track = 0; track < TRACKS; track++) {
    tracks[track].events = midi_countevents(tracks[track].start, tracks[track].end);
    totalevents += tracks[track].events;
  } // next track

  // allocate memory for events
  size = sizeof(struct midievent)*totalevents;
  if (xosdynamicarea_create(osdynamicarea_ALLOCATE_AREA, size, (byte *)-1,
                            os_AREA_NO_USER_DRAG, size, NULL, NULL, "MIDI Events",
                            &area, (byte **)&eventbuffers, NULL))  return 0;
  // event buffers for each track
  for (track = 0; track < TRACKS; track++) {
    tracks[track].unpacked = eventbuffers;
    eventbuffers += tracks[track].events;
  }

  // unpack events
  for (track = 0; track < TRACKS; track++) {
    midi_unpacktrack(tracks[track].start, tracks[track].end,
                     tracks[track].unpacked, tracks[track].events);
  } // next track

  // allocate new dynamic area
  size = sizeof(struct soundevent)*totalevents;
  if (xosdynamicarea_create(osdynamicarea_ALLOCATE_AREA, size, (byte *)-1,
                            os_AREA_NO_USER_DRAG, size, NULL, NULL, "MIDI Events",
                            &eventarea, (byte **)&START, NULL)) {
    xosdynamicarea_delete(area);
    return MIDI_NOT_OK;
  }

  EVENTS = totalevents;
  event = 0;

  timescale = (double)TEMPO / (double)TICKSPERQUARTER / 10000.0; // MIDI ticks -> centisecs

  while (totalevents > 0) {
    time = 0x7f000000;
    nexttrack = 0;
    for (track = 0; track < TRACKS; track++) {
      if (tracks[track].nextevent < tracks[track].events) {
        eventptr = tracks[track].unpacked + tracks[track].nextevent;
        if (eventptr->time <= time) {
          time = eventptr->time;
          nexttrack = track;
        }
      }
    }

    eventptr = tracks[nexttrack].unpacked + tracks[nexttrack].nextevent;

    sound = START + event;

    duration = (int)(eventptr->duration * timescale * 0.2);  // 1/20th sec
    if (duration >= 0xff)  duration = 0xfe;
    if (duration <= 1)     duration = 1;
    sound->duration = duration;
    sound->note = eventptr->note;
    sound->time = (int)((double)eventptr->time * timescale + 0.5);
    sound->pan = eventptr->pan;
    sound->volume = eventptr->volume;
    sound->internalvoice = eventptr->voice;
    sound->midiinstrument = eventptr->midiinstrument;
    tracks[nexttrack].nextevent++;
    totalevents--;
    event++;
  }

  if (totalduration)  *totalduration = sound->time;

  xosdynamicarea_delete(area);
  PLAYING = 1;

  return MIDI_OK;
}


// poll playback, return information
// position         returns current position in centiseconds from start
// future           returns a list of the sound-events for the immediate future
// used             returns the size of the list in future
int midi_poll(int *position, soundevent **future, int *used) {

  int free, event, filledto, done, n;
  soundevent *sound;

  if (!PLAYING)  return MIDI_NOT_OK;
  *position = CURRENTBEAT;

  xsound_qfree(&free);
  free -= 5;
  if (free <= 0)  return MIDI_OK;

  CURRENTBEAT = clock() - STARTTIME;
  event = NEXTEVENT;
  filledto = POSITION;
  done = 0;

  n = 0;

  while ((event < EVENTS) && (free > 0) && (filledto < CURRENTBEAT+PLAY_AHEAD) &&
         (n < PLAY_AHEAD_NOTES) && (!done)) {
    sound = START + event;
    if (sound->time < CURRENTBEAT+PLAY_AHEAD) {
      play_note(sound->time,  sound->duration, sound->note,
                 sound->pan,  sound->volume,   sound->internalvoice);
      event++;
      free--;
      filledto = sound->time;
      if (future)  future[n++] = sound;
    }
    else
      done = 1;
  }

  if (used)  *used = n;

  POSITION = filledto;
  NEXTEVENT = event;

  if (NEXTEVENT >= EVENTS)  return MIDI_NOT_OK;   // done
  return MIDI_OK;
}



void midi_set_tempo(int tempo) {
}


void midi_set_volume(int volume) {
}


void midi_set_transpose(int transpose) {
}


void midi_set_position(int cs) {
}
