
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "miditypes.h"
#include "midifile.h"
#include "util.h"

/* lengths of various MIDI commands */
static int cmdlen[16] = {0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0};

static void readlong(FILE * ifp, int * num) {
  unsigned char a, b, c, d;

  a = fgetc(ifp);
  b = fgetc(ifp);
  c = fgetc(ifp);
  d = fgetc(ifp);
   
  *num = (a << 24) | (b << 16) | (c << 8) | d; 
}

static void readshort(FILE * ifp, short * num) {
  unsigned char a, b;

  a = fgetc(ifp);
  b = fgetc(ifp);
  *num = (a << 8) | b; 
}

static int ReadVarLen(FILE * ifp) {
  int value;
  unsigned char c;

  if ((value = fgetc(ifp)) & 0x80) {
    value &= 0x7f;
    do {
      value = (value << 7) + ((c = fgetc(ifp)) & 0x7f);
    } while (c & 0x80);
  }

  return value;
}



int loadmidifile(char * filename, MidiSequence * seq) {
  FILE * ifp;

  ifp = fopen(filename, "rb");
  if (ifp == NULL) {
    printf("Couldn't open %s for input!  Skipping..\n", filename);
    return -1;
  }

  printf("Processing MIDI file %s ... \n", filename);
 
  while (!feof(ifp)) {
    char chunk[4];
   
    if (fread(chunk, 1, 4, ifp) == 4) {
      if (strncmp(chunk, "MThd", 4) == 0) {
        if (loadmidifileheader(ifp, seq) == -1) {
          printf("Error reading MIDI file..\n");
          return -1;
        }
      }
      else if (strncmp(chunk, "MTrk", 4) == 0) {
        loadmiditrackheader(ifp, seq);
        seq->numtracks++;
      }
      else {
        printf("Error reading MIDI file, unrecognized chunk type.\n");
#ifdef DEBUG
        printf("UNKOWN CHUNK TYPE! %c%c%c%c\n",
                  chunk[1], chunk[2], chunk[3], chunk[4]);
#endif
        return -1;
      }
    }
  }
 
  return 0;
}


int loadmidifileheader(FILE * ifp, MidiSequence * seq) {
  int chunksize;
  short tmps;

  readlong(ifp, &chunksize);
  printf("MIDI File Header: chunk size %d\n", chunksize);

  readshort(ifp, &tmps);
  printf("  MIDI File Type: %d\n", tmps);

  if (tmps != 1) {
    printf("Unsupported MIDI file;  File is of type: %d\n", tmps);
    return -1;  /* we only handle Type 1 MIDI files at the moment */
  }

  readshort(ifp, &tmps);
  printf("  Number of Tracks: %d\n", tmps);

  readshort(ifp, &tmps);
  printf("  Division: %d %x\n", tmps, tmps);

  seq->timebase.tempo = 120;
  seq->timebase.ppqn = tmps; /* Pulses per quarter note */

  return 0;
}

#if 0
void printffcmd(FILE * ifp, MidiTrack * track) {
  unsigned char type;
  int len;

  type = fgetc(ifp);

  switch (type) {
    case 0x00: /* sequence number */
    case 0x01: /* text event */
    case 0x02: /* Copyright */
      break;

    case 0x03: /* Sequence / Track name */
      { char * text;
        int i; 

        len = ReadVarLen(ifp);
        text = malloc(len + 1);
        memset(text, 0, len+1);
        for (i=0; i<len; i++) {
          text[i] = fgetc(ifp);
        }
        printf("Track: %s\n", text);
      }
      return;

    case 0x04: /* Instrument name */
      { char * text;
        int i; 

        len = ReadVarLen(ifp);
        text = malloc(len + 1);
        memset(text, 0, len+1);
        for (i=0; i<len; i++) {
          text[i] = fgetc(ifp);
        }
        printf("Instrument: %s\n", text);
      }
      return;

    case 0x05: /* Lyric text */
    case 0x06: /* Marker */
    case 0x07: /* Cue Point */
      break;

    case 0x20: /* MIDI Channel */
      { unsigned int channel;
        len = ReadVarLen(ifp);
        channel = fgetc(ifp);
        printf("MIDI Channel: %d \n", channel);
      }
      return;

    case 0x21: /* MIDI Port */
      { unsigned int port;
        len = ReadVarLen(ifp);
        port = fgetc(ifp);
        printf("MIDI Port: %d \n", port);
      }
      return;

    case 0x2f: /* End of Track */
      break;

    case 0x51: /* Tempo */
      { unsigned int tempo;
        unsigned char t1, t2, t3;
        len = ReadVarLen(ifp);
        t1 = fgetc(ifp);
        t2 = fgetc(ifp);
        t3 = fgetc(ifp);
        tempo = (t1 << 16) | (t2 << 8) | t3;
        printf("Tempo change: %d  ", tempo);
        tempo = 60000000 / tempo; 
        printf(" bpm: %d\n", tempo);
      }
      return;

    case 0x54: /* SMPTE Clock */
    case 0x58: /* Time Signature */
    case 0x59: /* Key Signature */
    case 0x7f: /* Proprietary Event */
      break;
  }

  len = ReadVarLen(ifp);
  if (len > 0) {
    int i;

    for (i=0; i<len; i++)
      fgetc(ifp);
  }
}
#endif


void loadmiditrackcontents(FILE * ifp, MidiTrack * track) {
  int time;
  static int len;
  static unsigned char oldcmd;
  unsigned char cmd;
  MidiEvent * newev;
  char newmsg[4];

  time = ReadVarLen(ifp);
  cmd = fgetc(ifp);

  /* Sequence Meta Event Handling */
  if (cmd == 0xff) {
    unsigned char seqcmd;
    int seqdatalen = 0;
    char * seqdata = NULL;
    int i;

    seqcmd = fgetc(ifp);
    seqdatalen = ReadVarLen(ifp);

    if (seqdatalen > 0) {
      seqdata = malloc(seqdatalen + 1); /* add byte for nul char on strings */
      memset(seqdata, 0, seqdatalen + 1); 
      for (i=0; i<seqdatalen; i++) {    
        seqdata[i] = fgetc(ifp);
      }
    }
    newev = NewSeqEvent(seqcmd, time, seqdata, seqdatalen);
    TrackAddEventToEnd(newev, track);  

    return;
  }

  /* Running Status Handling */
  if ((cmd & 0x80) == 0) {
    newmsg[0] = oldcmd;
    newmsg[1] = cmd;
    if ((len - 1) > 0) {
      int i;

      for (i=0; i<(len-1); i++) {
        newmsg[2+i] = fgetc(ifp);
      }
    }
    newev = NewEvent(newmsg, time, len + 1);
    TrackAddEventToEnd(newev, track);  
    return;
  }

  /* Normal Event Handling */
  oldcmd = cmd; 
  len = cmdlen[(cmd &0xf0) >> 4];

  newmsg[0] = cmd;

  if (len > 0) {
    int i;
    for (i=0; i<len; i++) {
      newmsg[1+i] = fgetc(ifp);
    }
  }
  newev = NewEvent(newmsg, time, len + 1);
  TrackAddEventToEnd(newev, track);  
}

void loadmiditrackheader(FILE * ifp, MidiSequence * seq) {
  int chunksize;
  int startpos, endpos;

  readlong(ifp, &chunksize);

  startpos = ftell(ifp);
  endpos = startpos + chunksize - 1;
  while (ftell(ifp) < endpos)
    loadmiditrackcontents(ifp, &seq->track[seq->numtracks]);
}

