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

char track_name[100];
char note_event[100];

unsigned int fread4(FILE *file)
{
    unsigned int quad;
    fread(&quad, 4, 1, file);
    quad = (quad>>24) | ((quad<<8) & 0x00FF0000) | ((quad>>8) & 0x0000FF00) | (quad<<24);
    return quad;
}

unsigned short fread2(FILE *file)
{
    unsigned short bi;
    fread(&bi, 2, 1, file);
    bi = (bi >> 8) | (bi << 8);
    return bi;
}

unsigned long ReadVarLen(FILE *infile)
{
    register unsigned long value;
    register unsigned char c;

    if ( (value = getc(infile)) & 0x80 )
    {
       value &= 0x7F;
       do
       {
         value = (value << 7) + ((c = getc(infile)) & 0x7F);
       } while (c & 0x80);
    }

    return(value);
}

void readnote(FILE *midi)
{
     unsigned char note, speed;
     if (strcmp(note_event, "Note-on") == 0 || strcmp(note_event, "Note-off") == 0)
     {
        note = fgetc(midi);
        speed = fgetc(midi);
        printf("%s > Note %i, Speed %i\n", note_event, note, speed);
     }
     else if (strcmp(note_event, "Parameter") == 0 || strcmp(note_event, "Pitch") == 0 || strcmp(note_event, "Note aftertouch") == 0)
     {
        note = fgetc(midi);
        speed = fgetc(midi);
        //printf("%s > Value 1 %i, Value 2 %i\n", note_event, note, speed);
     }
     else if (strcmp(note_event, "Program") == 0 || strcmp(note_event, "Channel aftertouch") == 0)
     {
          note = fgetc(midi);
          //printf("%s > Value 1 %i\n", note_event, note);
     }
     else
     {
         printf("Unhandled!\n");
         exit(-1);
     }
}
         
int main(int argc, char *argv[])
{
    printf("GH MIDI Parse Engine 0.1 by GameZelda\nUsage: ghparse file.mid\n");
    
    // Check args
    if (argc != 2)
    {
       printf("Invalid argument count!\n");
       exit(-1);
    }
    
    // Open the MIDI file
    FILE *midi;
    midi = fopen(argv[1], "rb");
    if (midi == NULL)
    {
       printf("Can't open %s for reading!\n", argv[1]);
       exit(-1);
    }
    
    // Read the MThd chunk...
    char mthd[5];
    unsigned int mthdlength;
    unsigned short mthdformat, mthdnumtracks, mthddivision;
    fgets(mthd, 5, midi);
    mthdlength = fread4(midi);
    mthdformat = fread2(midi);
    mthdnumtracks = fread2(midi);
    mthddivision = fread2(midi);
    
    if (strcmp(mthd, "MThd") != 0)
    {
       printf("Invalid MIDI file!\n");
       exit(-1);
    }
    else
    {
        printf("Valid MThd chunk! (%i bytes): Format %i, %i tracks, %i \"division\"\n", mthdlength, mthdformat, mthdnumtracks, mthddivision);
    }
    
    // Parse all MTrk chunks...
    int i;
    for (i = 0; i < mthdnumtracks; i++)
    {
        char mtrk[5];
        unsigned int mtrklength, trkstart, totaltime = 0;
        fgets(mtrk, 5, midi);
        mtrklength = fread4(midi);
        trkstart = ftell(midi);
        if (strcmp(mtrk, "MTrk") != 0)
        {
           printf("Invalid MIDI track!\n");
           exit(-1);
        }
        else
        {
            printf("Valid MTrk chunk! (%i bytes)\n", mtrklength);
        }
        while (ftell(midi) < trkstart + mtrklength)
        {
              unsigned char type;
              unsigned int time;
              time = ReadVarLen(midi);
              totaltime += time;
              printf("%i (+%i): ", totaltime, time);
              
              type = fgetc(midi);
              // Event
              if (type == 0xFF)
              {
                 unsigned char typeff;
                 typeff = fgetc(midi);
                 if (typeff == 0x03)
                 {
                    // Track name
                    unsigned int strlength;
                    char *string;
                    strlength = ReadVarLen(midi) + 1;
                    string = malloc(strlength);
                    if (string == NULL)
                    {
                       printf("Can't alloc. %i bytes of memory!\n", strlength);
                       exit(-1);
                    }
                    fgets(string, strlength, midi);
                    printf("Track name > %s\n", string);
                    strcpy(track_name, string);
                    free(string);
                 }
                 else if (typeff == 0x01)
                 {
                    // Text (copy-paste >_>)
                    unsigned int strlength;
                    char *string;
                    strlength = ReadVarLen(midi) + 1;
                    string = malloc(strlength);
                    if (string == NULL)
                    {
                       printf("Can't alloc. %i bytes of memory!\n", strlength);
                       exit(-1);
                    }
                    fgets(string, strlength, midi);
                    printf("Text > %s\n", string);
                    free(string);
                 }
                 else if (typeff == 0x58)
                 {
                      // Time signature
                      unsigned int timesiglength;
                      unsigned char num, denum, clocks, notated32;
                      timesiglength = ReadVarLen(midi);
                      if (timesiglength != 4)
                      {
                         printf("Invalid time signature length!\n");
                         exit(-1);
                      }
                      num = fgetc(midi);
                      denum = fgetc(midi);
                      clocks = fgetc(midi);
                      notated32 = fgetc(midi);
                      printf("Time signature > %i/%i, %i MIDI clocks, %i notated 32nds in a 1/4\n", num, denum, clocks, notated32);
                 }
                 else if (typeff == 0x51)
                 {
                      // Tempo
                      unsigned int tempolength, tempo = 0;
                      tempolength = ReadVarLen(midi);
                      if (tempolength != 3)
                      {
                         printf("Invalid tempo length!\n");
                         exit(-1);
                      }
                      fread(&tempo, 3, 1, midi);
                      // Swap Endians
                      tempo = (tempo>>24) | ((tempo<<8) & 0x00FF0000) | ((tempo>>8) & 0x0000FF00) | (tempo<<24);
                      // Shift more...
                      tempo >>= 8;
                      printf("Tempo > %i (%f BPM)\n", tempo, 60000000.0 / tempo);    
                 }
                 else if (typeff == 0x2F)
                 {
                      unsigned char length;
                      length = fgetc(midi);
                      if (length != 0)
                      {
                         printf("Invalid track end length!\n");
                         exit(-1);
                      }
                      printf("MTrk End\n\n");
                 }
                 else if (typeff == 0x7F)
                 {
                      unsigned int length;
                      length = ReadVarLen(midi);
                      fseek(midi, length, SEEK_CUR);
                      printf("Propietary event > %i bytes\n", length);
                 }     
                 else if (typeff == 0x20)
                 {
                      unsigned char channel;
                      channel = fgetc(midi);
                      printf("Set channel > %i\n", channel);
                 }
                 else if (typeff == 0x05)
                 {   
                    // Lyric (copy-paste >_>)
                    unsigned int strlength;
                    char *string;
                    strlength = ReadVarLen(midi) + 1;
                    string = malloc(strlength);
                    if (string == NULL)
                    {
                       printf("Can't alloc. %i bytes of memory!\n", strlength);
                       exit(-1);
                    }
                    fgets(string, strlength, midi);
                    printf("Lyric > %s\n", string);
                    free(string);
                 }
                 else
                 {
                     printf("Unknown 0xFF%X event!\n", typeff);
                     exit(-1);
                 }
              }
              else if (type < 128)
              {
                   fseek(midi, -1, SEEK_CUR);
                   readnote(midi);
              }
              else if (type >= 0x80 && type < 0x90)
              {
                   //printf("Define note-off\n");
                   strcpy(note_event, "Note-off");
                   readnote(midi);
              }
              else if (type >= 0x90 && type < 0xA0)
              {
                   //printf("Define note-on\n");
                   strcpy(note_event, "Note-on");
                   readnote(midi);
              }
              else if (type >= 0xA0 && type < 0xB0)
              {
                   //printf("Define parameter + ");
                   strcpy(note_event, "Note aftertouch");
                   readnote(midi);
              }
              else if (type >= 0xB0 && type < 0xC0)
              {
                   //printf("Define parameter + ");
                   strcpy(note_event, "Parameter");
                   readnote(midi);
              }
              else if (type >= 0xC0 && type < 0xD0)
              {
                   //printf("Define program + ");
                   strcpy(note_event, "Program");
                   readnote(midi);
              }
              else if (type >= 0xD0 && type < 0xE0)
              {
                   //printf("Define program + ");
                   strcpy(note_event, "Channel aftertouch");
                   readnote(midi);
              }
              else if (type >= 0xE0 && type < 0xF0)
              {
                   //printf("Define pitch + ");
                   strcpy(note_event, "Pitch");
                   readnote(midi);
              }
              else
              {
                  printf("Unknown flag %X!\n", type);
                  exit(-1);
              }
        }
    }
    printf("MIDI Parsed!\n");
    return 0;
}
