#include "midiplayer.h"
#include <assert.h>
#include <stdlib.h>
#include "midifile.h"
#include "pqueue.h"
#include "trackstate.h"
#include "tempotracker.h"
#include "midicontroller.h"

midiplayer_t *midiplayer_from_midi(MIDIFILE *mf); // takes ownership

struct _MIDIPLAYER {
  int tickpos;
  MIDIFILE *mf;  // TODO? no alloc, and midi_file_move/_copy?
  trackstate_t *ts;
  PQUEUE pq;
  tempotracker_t tt;
};

midiplayer_t *midiplayer_load(const char *midifile) // {{{
{
  MIDIFILE *mf=malloc(sizeof(MIDIFILE));
  if (!mf) {
    return NULL;
  }
  int res=read_midifile(mf,midifile);
  if (res!=0) {
    free(mf);
    return NULL;
  }
  return midiplayer_from_midi(mf);
}
// }}}

midiplayer_t *midiplayer_from_midi(MIDIFILE *mf) // {{{
{
  if (!mf) {
    return NULL;
  }
  const int len=mf->no_tracks;
  midiplayer_t *player=malloc(sizeof(midiplayer_t));
  if (!player) {
    free_midifile(mf);
    free(mf);
    return NULL;
  }
  player->tickpos=0;
  player->mf=mf;
  player->ts=malloc(sizeof(player->ts[0])*len);
  if (!player->ts) {
    free_midifile(player->mf);
    free(player->mf);
    free(player);
    return NULL;
  }
  
  int iA;
  for (iA=0;iA<len;iA++) {
    init_trackstate(player->ts+iA,player->mf->tracks[iA]);
  }

  init_pq(&player->pq,len,PQ_MODE_LOWEST_ALSO_DATAPTR);

  init_tempotracker(&player->tt);
  tempotracker_load(&player->tt,player->mf);

  return player;
}
// }}}

void free_midiplayer(midiplayer_t *player) // {{{
{
  if (player) {
    free_tempotracker_data(&player->tt);
    free_pq_data(&player->pq);

    // free trackstate data
    int iA;
    const int len=player->mf->no_tracks;
    for (iA=0;iA<len;iA++) {
      free_trackstate_data(player->ts+iA);
    }
    
    free(player->ts);
    free_midifile(player->mf);
    free(player->mf);
    free(player);
  }
}
// }}}

void midiplayer_rewind(midiplayer_t *player) // {{{
{
  assert( (player)&&(player->mf)&&(player->ts) );

  player->tickpos=0;
  clear_pq(&player->pq);
  // load first events
  int iA=0;
  const int len=player->mf->no_tracks;
  for (iA=0;iA<len;iA++) {
    const int res=track_rewind(player->ts+iA);
    if (res>=0) {
      add_pq(&player->pq,res,(intptr_t)(player->ts+iA));
    }
  }
}
// }}}

struct _FILTER_DATA {
  MIDIPLAYER_OUTFN outfn;
  void *user;
};

static void filter_ctrl_outfn(void *user,midi_event_t *ev) // {{{
{
  struct _FILTER_DATA *data=user;
  if ( (ev->type!=EV_NOTE_ON)&&
       (ev->type!=EV_NOTE_OFF)&&
       (ev->type!=EV_NOTE_AFTERTOUCH) ) {
    (*data->outfn)(data->user,ev);
  }
}
// }}}

// TODO: mute track -> filter_ctrl_outfn

void midiplayer_runto(midiplayer_t *player,int abstickpos,MIDIPLAYER_OUTFN outfn,void *user) // {{{
{
  assert( (player)&&(player->mf)&&(player->ts) );

  if (abstickpos<=player->tickpos) {
    return;
  }
  int nexttime=peek_pq(&player->pq);
  while (nexttime<abstickpos) {
    trackstate_t *track=(trackstate_t *)get_pq(&player->pq);
    if (!track) { // "done"
      break;
    }
    assert(track->next.time>=0); // event valid?
    assert(nexttime>=player->tickpos);

    if (outfn) {
      // TODO?! absolutize?
      // track->next.time=nexttime; // =^=  +=track->tickpos
      (*outfn)(user,&track->next);
      // track->next.time-=track->tickpos; // restore
    }

    const int res=track_next(track);
    if (res>=0) {
      add_pq(&player->pq,res,(intptr_t)track);
    }
    nexttime=peek_pq(&player->pq);
  }
  player->tickpos=abstickpos;
}
// }}}

static void send_bank_pgm(unsigned char channel,int bank,unsigned char pgm,MIDIPLAYER_OUTFN outfn,void *user) // {{{
{
  midi_event_t ev=midictrl_event_init(channel,MIDICTRL_BANK_SELECT,bank>>7);
  (*outfn)(user,&ev);
  ev.controller.number|=MIDICTRL__LSB;
  ev.controller.value=bank&0x7f;
  (*outfn)(user,&ev);

  ev.type=EV_PROGRAM_CHANGE; 
  ev.program.number=0;
  (*outfn)(user,&ev);
}
// }}}

// TODO? different
static void send_midi_reinit(MIDIPLAYER_OUTFN outfn,void *user) // {{{
{
  int iA;
  midi_event_t ev=midictrl_event_init(0,MIDICTRL_RESET_ALL_CONTROLLERS,0);
  for (iA=0;iA<16;iA++) {
    ev.channel=iA;
    (*outfn)(user,&ev);

    send_bank_pgm(iA,(iA==9)?128:0,0,outfn,user);
  }
}
// }}}

static void send_midictrl_all(unsigned char ctrl,MIDIPLAYER_OUTFN outfn,void *user) // {{{
{
  int iA;
  midi_event_t ev=midictrl_event_init(0,ctrl,0);
  for (iA=0;iA<16;iA++) {
    ev.channel=iA;
    (*outfn)(user,&ev);
  }
}
// }}}

// TODO? different
static void send_midi_reset(MIDIPLAYER_OUTFN outfn,void *user) // {{{
{
  midi_event_t ev;
  init_midi_event(&ev);
  ev.time=0;
  ev.type=EV_SYSEX_VERBATIM;
  ev.sysex.len=1;

  unsigned char tmp=SYSREAL_RESET;
  ev.sysex.data=&tmp;
  (*outfn)(user,&ev);
  ev.sysex.data=NULL;
}
// }}}

int midiplayer_seek(midiplayer_t *player,float abstime,MIDIPLAYER_OUTFN outfn,void *user) // {{{
{
  assert(player);

  if (abstime<0) {
/*
    send_midi_reset(outfn,user);
*/
//    send_midictrl_all(MIDICTRL_ALL_NOTES_OFF,outfn,user);
    send_midictrl_all(MIDICTRL_ALL_SOUND_OFF,outfn,user);
    send_midi_reinit(outfn,user);
    midiplayer_rewind(player);
    return 0;
  }

  const int newtickpos=tempotracker_get_tick(&player->tt,abstime);
  assert(newtickpos>=0);

  send_midictrl_all(MIDICTRL_ALL_NOTES_OFF,outfn,user);
  if (newtickpos<player->tickpos) { // we have to start all over again
    midiplayer_rewind(player);
    send_midi_reinit(outfn,user);
  }
  // now just fast-forward
  struct _FILTER_DATA fdata={
    .outfn = outfn,
    .user = user
  };
  midiplayer_runto(player,newtickpos,filter_ctrl_outfn,&fdata);

  return player->tickpos;
}
// }}}

int midiplayer_getppq(midiplayer_t *player) // {{{
{
  assert( (player)&&(player->mf) );
  return player->mf->ppq;
}
// }}}

MIDIFILE *midiplayer_getmf(midiplayer_t *player) // {{{
{
  assert(player);
  return player->mf;
}
// }}}

float midiplayer_getlength(midiplayer_t *player) // {{{
{
  assert(player);
  return tempotracker_get_timelength(&player->tt);
}
// }}}

bool midiplayer_isdone(midiplayer_t *player) // {{{
{
  assert(player);
  return (peek_pq(&player->pq)<0)&&(player->tickpos>=tempotracker_get_ticklength(&player->tt));
}
// }}}

// TODO? handle -1  (esp. end_of_file?)
float midiplayer_gettime(midiplayer_t *player) // {{{
{
  return tempotracker_get_time(&player->tt,player->tickpos);
}
// }}}

// TODO? handle -1  (esp. end_of_file?)
// TODO? cache value?
int midiplayer_gettempo(midiplayer_t *player) // {{{
{
  return tempotracker_get_tempo(&player->tt,player->tickpos);
}
// }}}

int midiplayer_next_tempochange(midiplayer_t *player) // {{{
{
  return tempotracker_next_tempochange(&player->tt,player->tickpos);
}
// }}}

