#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string.h>
#include "midifile.h"
// http://www.sonicspot.com/guide/midifiles.html

int midi_debug=1;

#define MTYPE_MThd 0x4d546864
#define MTYPE_MTrk 0x4d54726b

int midi_read_vl(unsigned char **pos,unsigned char *end) // {{{
{
  if ( (!pos)||(!*pos)||(*pos>=end) ) {
    return -1;
  } else if (end-*pos>4) {
    end=*pos+4;
  }
  unsigned char *tmp=*pos;
  int ret=*tmp&0x7f;
  while (*tmp&0x80) {
    tmp++;
    if (tmp==end) {
      return -1;
    }
    ret<<=7;
    ret+=*tmp&0x7f;
  }
  *pos=++tmp;
  return ret;
}
// }}}

int midi_write_vl(unsigned char *pos,unsigned char *end,unsigned int val) // {{{
{
  unsigned char buf[4],*tmp=buf+3;
  if (val>=0x10000000) {
    assert(0);
    return 0;
  }

  *tmp=(val&0x7f);
  while (val>0x7f) {
    val>>=7;
    tmp--;
    *tmp=0x80|(val&0x7f);
  }

  const int len=buf+4-tmp;
  if (!pos) {
    return len;
  } else if (pos+len>=end) {
    return 0;
  }
  for (;tmp<buf+4;tmp++,pos++) {
    *pos=*tmp;
  }
  return len;
}
// }}}

static unsigned short midi_read_s(unsigned char *pos) // {{{
{
  return (pos[0]<<8)|pos[1];
}
// }}}

static void midi_write_s(unsigned char *pos,unsigned short val) // {{{
{
  pos[0]=val>>8;
  pos[1]=val&0xff;
}
// }}}

static unsigned long midi_read_l(unsigned char *pos) // {{{
{
  return (pos[0]<<24)|(pos[1]<<16)|(pos[2]<<8)|pos[3];
}
// }}}

static void midi_write_l(unsigned char *pos,unsigned long val) // {{{
{
  pos[0]=val>>24;
  pos[1]=(val>>16)&0xff;
  pos[2]=(val>>8)&0xff;
  pos[3]=val&0xff;
}
// }}}

/* ERRORS:
 -1  !mf
 -2  fopen
 -3  !MTYPE_MThd
 -4  ... len!=6
 -5  malloc
 -6  malloc
 -7  corrupt file: >no_tracks

write:
 -8  ferror
*/
int read_midifile(MIDIFILE *mf, const char *filename) // {{{
{
  FILE *f;
  unsigned char buf[16];
  int iA,trackno;
  unsigned int type,len;

  if (!mf) {
    return -1;
  }
  init_midifile(mf);
  if ((f=fopen(filename,"r"))==NULL) {
    printf("Error opening \"%s\": %m\n",filename);
    return -2;
  }
  // read first chunk
  fread(buf,1,8,f);
  type=midi_read_l(buf);
  len=midi_read_l(buf+4);
  if (type!=MTYPE_MThd) { // TODO? read RIFF-midi's
    printf("Not a midi file\n");
    fclose(f);
    return -3;
  }
  if (midi_debug) {
    printf("Name: %c%c%c%c(%08x), Length: %d\n",buf[0],buf[1],buf[2],buf[3],type,len);
  }
  // process header
  if (len!=6) {
    printf("Unknown header format\n");
    fclose(f);
    return -4;
  } 

  fread(buf,1,6,f);
  mf->format=midi_read_s(buf);
  mf->no_tracks=midi_read_s(buf+2);
  if (midi_debug) {
    printf("Header says: Format: %02x, Number of Tracks: %d, ",mf->format,mf->no_tracks);
  }
  if (buf[4]&0x80) { // SMTPE Timestamp
    mf->ppq=0;
    mf->smtpe_fps=-buf[4];
    mf->smtpe_subframes=buf[5]; // buf[5]==4 ^=> midi time code
    if (midi_debug) {
      printf("SMTPE: %d Frames per second, %d Subframes per Frame\n",mf->smtpe_fps,mf->smtpe_subframes);
    }
  } else {
    mf->ppq=midi_read_s(buf+4);
    mf->smtpe_fps=mf->smtpe_subframes=0;
    if (midi_debug) {
      printf("Pulses per quarter: %d\n",mf->ppq);
    }
  }
  len=0;

  assert( (mf->format!=0)||(mf->no_tracks==1) );
  // allocate tracks
  mf->tracks=malloc(sizeof(MIDITRACK)*mf->no_tracks);
  if (!mf->tracks) {
    printf("Alloc failed: %m\n");
    fclose(f);
    return -5;
  }
 
  // read all other chunks
  trackno=0;
  while (!feof(f)) {
    fseek(f,len,SEEK_CUR);
    iA=fread(buf,1,8,f);
    if (iA<=0) {
      break;
    }
    type=midi_read_l(buf);
    len=midi_read_l(buf+4);
    if (midi_debug) {
      printf("Name: %c%c%c%c(%08x), Length: %d\n",buf[0],buf[1],buf[2],buf[3],type,len);
    }
    if (type==MTYPE_MTrk) {
      if (trackno>=mf->no_tracks) { // too much tracks!
        trackno++;
        break;
      }
      mf->tracks[trackno]=malloc(sizeof(int)+len);
      if (!mf->tracks[trackno]) {
        printf("Alloc failed: %m\n");
        for (trackno--;trackno>=0;trackno--) {
          free(mf->tracks[trackno]);
        }
        free(mf->tracks);
        fclose(f);
        return -6;
      }
      mf->tracks[trackno]->len=len;
      fread(mf->tracks[trackno]->data,1,len,f);
//      dump_track(trk,len);
      len=0;
      trackno++;
    } else {
      // TODO
    }
  }

  fclose(f);
  if (trackno!=mf->no_tracks) {
    printf("Error: wrong number of tracks (%d) in header, got: %d\n",mf->no_tracks,trackno);
    for (trackno--;trackno>=0;trackno--) {
      free(mf->tracks[trackno]);
    }
    free(mf->tracks);
    return -7;
  }
  return 0;
}
// }}}

void init_midifile(MIDIFILE *mf) // {{{
{
  if (!mf) {
    assert(0);
    return;
  }
  // minimal initialization would be:
  // mf->no_tracks=0; mf->tracks=0;
  memset(mf,0,sizeof(MIDIFILE));
}
// }}}

int write_midifile(MIDIFILE *mf, const char *filename) // {{{
{
  FILE *f;
  unsigned char buf[16];
  int trackno;

  if ( (!mf)||(mf->format>2) ) {
    return -1;
  }
  // TODO: check >mf  for consistency
assert( (mf->ppq!=0)||(mf->smtpe_fps<128) );

  if ((f=fopen(filename,"w"))==NULL) {
    printf("Error opening \"%s\": %m\n",filename);
    return -2;
  }
  // write first chunk
  midi_write_l(buf,MTYPE_MThd);
  midi_write_l(buf+4,6);
  fwrite(buf,1,8,f);

  midi_write_s(buf,mf->format);
  midi_write_s(buf+2,mf->no_tracks);

  if (mf->ppq==0) { // SMTPE Timestamp
    buf[4]=-mf->smtpe_fps;
    buf[5]=mf->smtpe_subframes;
  } else {
    midi_write_s(buf+4,mf->ppq);
  }
  fwrite(buf,1,6,f);

  assert( (mf->format!=0)||(mf->no_tracks==1) );
  assert(mf->tracks);
 
  // write all other chunks
  for (trackno=0;trackno<mf->no_tracks;trackno++) {
    const unsigned int len=mf->tracks[trackno]->len;

    midi_write_l(buf,MTYPE_MTrk); // TODO? other types
    midi_write_l(buf+4,len);
    fwrite(buf,1,8,f);

    fwrite(mf->tracks[trackno]->data,1,len,f);
  }

  trackno=0; // reuse >trackno for return value
  if (ferror(f)) {
    printf("Write error: %m\n");
    trackno=-8;
  }

  fclose(f);
  return trackno;
}
// }}}

void free_midifile(MIDIFILE *mf) // {{{
{ 
  for (;mf->no_tracks>0;mf->no_tracks--) {
    free(mf->tracks[mf->no_tracks-1]);
  }
  free(mf->tracks);
}
// }}}
