#include "audioout.h"
#include <assert.h>
#include <math.h>
#include "midievent.h"

static void ao_set_tempo(audio_out_t *ao,int usec_per_quarter) // {{{
{
  assert(ao);
  usec_per_quarter/=ao->temposcale;
  float remainder=ao->q_sample_pos/(float)ao->samples_per_quarter;

  // essentially:
  // samples_per_quarter=(usec_per_quarter*ao->samplerate)/1000000; // but: avoid overflow
  ao->samples_per_quarter=(usec_per_quarter/1000)*(ao->samplerate/1000)+
                         ((usec_per_quarter/1000)*(ao->samplerate%1000)+
                          (usec_per_quarter%1000)*(ao->samplerate/1000))/1000;

  // retroscale q_sample_pos
  ao->q_sample_pos=ceilf(remainder*ao->samples_per_quarter);
}
// }}}

// intercept  tempo changes
static void one_midi_event(void *data,midi_event_t *ev) // {{{
{
  assert( (data)&&(ev) );
  audio_out_t *ao=data;
  assert(ao->outfn);

  if ( (ev->type==EV_META)&&(ev->meta.type==META_TEMPO) ) {
    ao_set_tempo(ao,ev->meta.tempo.usec_per_quarter);
  }
  (*ao->outfn)(ao->outdata,ev);
}
// }}}

void ao_execute_midi(audio_out_t *ao,int samples) // {{{
{
  if ( (!ao)||(!ao->player) ) {
    return;
  }

  int nexttickpos=ao->quarter_pos*ao->ppq+((ao->q_sample_pos+samples)*ao->ppq)/ao->samples_per_quarter;

  int next_tempo_change=midiplayer_next_tempochange(ao->player);
  while (next_tempo_change<nexttickpos) {
    midiplayer_runto(ao->player,next_tempo_change+1,one_midi_event,ao); // so it does process tempo_change
    if (midiplayer_isdone(ao->player)) {
      break;
    }
    // recalculate nexttickpos according to new tempo
    nexttickpos=ao->quarter_pos*ao->ppq+((ao->q_sample_pos+samples)*ao->ppq)/ao->samples_per_quarter;
    // NOTE: no need to update quarter_pos (or, q_sample_pos) here
    next_tempo_change=midiplayer_next_tempochange(ao->player);
  }

  // remainder
  midiplayer_runto(ao->player,nexttickpos,one_midi_event,ao);

  ao->q_sample_pos+=samples;
  int fullquarters=ao->q_sample_pos/ao->samples_per_quarter;
  if (fullquarters) {
    ao->quarter_pos+=fullquarters;
    ao->q_sample_pos-=ao->samples_per_quarter*fullquarters;
  }
}
// }}}

void ao_setup(audio_out_t *ao,midiplayer_t *player,int samplerate,MIDIPLAYER_OUTFN outfn,void *outdata) // {{{
{
  assert( (ao)&&(outfn) );
  ao->outfn=outfn;
  ao->outdata=outdata;
  ao->player=player;
  ao->samplerate=samplerate;
  ao->temposcale=1.0;
  ao_rewind(ao);
}
// }}}

void ao_rewind(audio_out_t *ao) // {{{
{
  assert(ao);

  if (ao->player) {
    ao->ppq=midiplayer_getppq(ao->player);
  } else {
    ao->ppq=0;
  }
  ao->samples_per_quarter=ao->samplerate/2; // -> 120BPM initially
  ao->q_sample_pos=0;
  ao->quarter_pos=0;
}
// }}}

/*
  TODO? high_precision
void ao_sample_seek(audio_out_t *ao,int abssample) // {{{
{
  const int curtickpos;
  const int newtickpos;
  if (curtickpos>newtickpos) {
    rewind()
//    system_reset
  } else {
//    all_notes_off
  }
  ao_execute_midi(...,abssample-cursample);
}
// }}}
*/

void ao_simple_seek(audio_out_t *ao,float abstime) // {{{
{
  assert(ao);
  if (!ao->player) {
    return;
  }
  if (abstime<0) {
    ao_rewind(ao);
  }

  const int abstickpos=midiplayer_seek(ao->player,abstime,ao->outfn,ao->outdata);  // one_midi_event,ao);   not needed
  const int upq=midiplayer_gettempo(ao->player);
  ao_seek(ao,abstickpos,upq);
}
// }}}

void ao_seek(audio_out_t *ao,int abstickpos,int usec_per_quarter) // {{{
{
  assert( (ao)&&(ao->ppq) );
  assert( (abstickpos>=0)&&(usec_per_quarter>=0) );
 
  // TODO? we only need usec_p_q to s_p_q conversion
  ao->q_sample_pos=0;
  ao->quarter_pos=0;
  ao_set_tempo(ao,usec_per_quarter);

  ao->quarter_pos=abstickpos/ao->ppq;
  ao->q_sample_pos=(abstickpos-ao->quarter_pos*ao->ppq)*ao->samples_per_quarter/ao->ppq;
}
// }}}

void ao_set_tempo_scaling(audio_out_t *ao,float factor) // {{{
{
  assert( (ao)&&(factor>0) );
  ao->temposcale=factor;

  if (ao->player) {
    ao_set_tempo(ao,midiplayer_gettempo(ao->player)); // bad trick
  }
}
// }}}

