#include <stdint.h>

#include "printf/printf.h"
#include "aica/aica.hpp"

#include "xm_player/interpreter.hpp"
#include "xm_player/sound.hpp"
#include "xm_player/playlist.hpp"
#include "xm_player/cover.hpp"

namespace interpreter {

struct interpreter_state state = {};

// quater-semitones
//
//   for i in range(48):
//       round(1024 * (2 ** (i / 48) - 1))
//
const static int16_t cent_to_fns[] = {
    0,  15,  30,  45,  61,  77,  93, 109, 125, 142, 159, 176,
  194, 211, 229, 248, 266, 285, 304, 323, 343, 363, 383, 403,
  424, 445, 467, 488, 510, 533, 555, 578, 601, 625, 649, 673,
  698, 723, 749, 774, 801, 827, 854, 881, 909, 937, 966, 995
};
const int cent_to_fns_length = (sizeof (cent_to_fns)) / (sizeof (cent_to_fns[0]));

uint16_t
note_to_oct_fns(const int8_t note)
{
  // log(8363 / 44100) / log(2)
  const float base_ratio = -2.3986861877015477;

  float c4_note = (float)note - 49.0;
  float ratio = base_ratio + (c4_note / 12.0);

  float whole = (int)ratio;
  float fraction;
  if (ratio < 0) {
    if (whole > ratio)
      whole -= 1;
    fraction = -(whole - ratio);
  } else {
    fraction = ratio - whole;
  }

  assert(fraction >= 0.0);
  assert(fraction < 1.0);

  int fns = cent_to_fns[(int)(fraction * cent_to_fns_length)];

  return aica::oct_fns::OCT((int)whole) | aica::oct_fns::FNS((int)fns);
}

const static int8_t volume_table[] = {
    0,  3,  5,  6,  7,  8,  8,  9,  9,  9, 10, 10, 10, 10, 11, 11,
   11, 11, 11, 11, 12, 12, 12, 12, 12, 12, 12, 13, 13, 13, 13, 13,
   13, 13, 13, 13, 13, 13, 13, 14, 14, 14, 14, 14, 14, 14, 14, 14,
   14, 14, 14, 14, 14, 14, 15, 15, 15, 15, 15, 15, 15, 15, 15, 15,
   15
};

void _play_note(int ch, const xm_pattern_format_t * pf)
{
  int instrument = (pf->instrument != 0) ? pf->instrument : state.channel[ch].instrument;
  if (instrument == 0)
    instrument = 1;
  state.channel[ch].instrument = instrument;

  xm_sample_header_t * sample_header = state.xm.sample_header[instrument - 1];
  int start = state.xm.sample_data_offset[instrument - 1];

  int sample_type = ((sample_header->type & (1 << 4)) != 0);
  int bytes_per_sample = 1 + sample_type;

  int loop_type = sample_header->type & 0b11;
  int lpctl = (loop_type == 0) ? 0 : 1;
  int lsa = s32(&sample_header->sample_loop_start) / bytes_per_sample;
  int len = s32(&sample_header->sample_loop_length) / bytes_per_sample;
  if (len == 0) {
    len = s32(&sample_header->sample_length) / bytes_per_sample;
  }
  if (len >= 65535) {
    len = 65532;
  }
  assert(start >= 0);
  assert(lsa >= 0);
  assert(len >= 0);

  if (loop_type == 2) // bidirectional
    len += len - 2;

  int volume_column = state.channel[ch].volume;
  if (pf->volume_column_byte >= 0x10 && pf->volume_column_byte <= 0x50) {
    volume_column = pf->volume_column_byte - 0x10;
    state.channel[ch].volume = volume_column;
  }

  assert(sample_header->volume >= 0 && sample_header->volume <= 64);
  int volume = (sample_header->volume * volume_column) / 64;
  assert(volume >= 0 && volume <= 64);
  int disdl = volume_table[volume];
  bool pcms = !sample_type;
  wait(); aica_sound.channel[ch].PCMS(pcms);
  wait(); aica_sound.channel[ch].SA(start);
  wait(); aica_sound.channel[ch].LPCTL(lpctl);
  wait(); aica_sound.channel[ch].LSA((lsa) & ~(0b11));
  wait(); aica_sound.channel[ch].LEA((lsa + len) & ~(0b11));
  wait(); aica_sound.channel[ch].DISDL(disdl);
  wait(); aica_sound.channel[ch].oct_fns = note_to_oct_fns(pf->note + sample_header->relative_note_number);

  if (pf->effect_type == 0x04) { // vibrato
    wait(); aica_sound.channel[ch].LFOF(0x12);
    wait(); aica_sound.channel[ch].ALFOWS(2);
    wait(); aica_sound.channel[ch].PLFOWS(2);
    wait(); aica_sound.channel[ch].ALFOS(0);
    wait(); aica_sound.channel[ch].PLFOS(4);
  } else {
    //wait(); aica_sound.channel[ch].LFOF(0x11);
    //wait(); aica_sound.channel[ch].ALFOWS(2);
    //wait(); aica_sound.channel[ch].PLFOWS(2);
    wait(); aica_sound.channel[ch].ALFOS(0);
    wait(); aica_sound.channel[ch].PLFOS(0);
  }

  state.channel[ch].keyon = 255;
  wait(); aica_sound.channel[ch].KYONB(0);
}

void play_note_effect(int ch, const xm_pattern_format_t * pf)
{
  int effect_tick = state.tick % state.ticks_per_line;

  switch (pf->effect_type) {
  case 0x04: // 4 vibrato
    wait(); aica_sound.channel[ch].LFOF(0x12);
    wait(); aica_sound.channel[ch].ALFOWS(2);
    wait(); aica_sound.channel[ch].PLFOWS(2);
    wait(); aica_sound.channel[ch].ALFOS(0);
    wait(); aica_sound.channel[ch].PLFOS(4);
    break;
  case 0x0d: // D pattern break
    state.pattern_break = pf->effect_parameter;
    break;
  case 0x0e: // E
    switch (pf->effect_parameter & 0xf0) {
    case 0xd0: // ED note delay
      if (effect_tick == (pf->effect_parameter & 0x0f)) {
        _play_note(ch, pf);
      }
      break;
    }
    break;
  case 0x14: // K delayed tick
    if (effect_tick == pf->effect_parameter) {
      wait(); aica_sound.channel[ch].KYONB(0);
    }
    break;
  }
}

void play_note(int ch, const xm_pattern_format_t * pf)
{
  if (pf->note == 97) {
    wait(); aica_sound.channel[ch].KYONB(0);
  } else if (pf->note != 0) {
    bool note_delay = (pf->effect_type == 0xe) && ((pf->effect_parameter & 0xf0) == 0xd0); // ED note delay
    if (!note_delay)
      _play_note(ch, pf);
  }

  play_note_effect(ch, pf);
}

/*
void play_debug_note(int ch, xm_pattern_format_t * pf)
{
  debug_note(ch, pf);
  play_note(ch, pf);
}
*/

void rekey_note(int ch, const xm_pattern_format_t * pf)
{
  if (pf->note == 97) {
  } else if (pf->note != 0) {
    wait(); aica_sound.channel[ch].KYONB(0);
  }
}

static inline void next_pattern()
{
  if (state.reverse)
    state.pattern_order_table_index -= 1;
  else
    state.pattern_order_table_index += 1;

  if (state.pattern_order_table_index < 0)
    state.pattern_order_table_index = state.xm.song_length - 1;
  if (state.pattern_order_table_index >= state.xm.song_length) {
    if (state.repeat) {
      state.pattern_order_table_index = 0;
    } else if (state.reverse) {
      if (playlist::prev(false))
        return;
    } else {
      if (playlist::next(false))
        return;
    }
  }
  printf("pattern_order_table_index: %d\n", state.pattern_order_table_index);

  state.pattern_index = state.xm.header->pattern_order_table[state.pattern_order_table_index];

  if (state.pattern_break >= 0) {
    state.next_line_index = state.pattern_break;
  } else {
    int line_count = state.xm.pattern_note_count[state.pattern_index] / state.xm.number_of_channels;
    if (state.reverse)
      state.next_line_index = line_count - 1;
    else
      state.next_line_index = 0;
  }
  state.pattern_break = -1;

  printf("note_count: %d\n", state.xm.pattern_note_count[state.pattern_index]);
}

template <void (*F)(int, const xm_pattern_format_t *)>
void execute_line(int line_index)
{
  int line_pattern_index = line_index * state.xm.number_of_channels;
  for (int ch = 0; ch < state.xm.number_of_channels; ch++) {
    xm_pattern_format_t * pattern = state.xm.pattern[state.pattern_index];
    F(ch, &pattern[line_pattern_index + ch]);
  }
}

void interrupt()
{
  if (state.deferred_load_tick == 1) {
    deferred_load_finish();
  }
  if (state.deferred_load_tick > 0) {
    state.deferred_load_tick -= 1;
    return;
  }

  if (state.paused)
    return;

  state.interrupt_clock += 1;
  // execute keyons
  for (int ch = 0; ch < 64; ch++) {
    /*
    if (state.channel[ch].keyon == 2) {
      int sgc = aica_sound.common.SGC();
      if (sgc == 3) {
        wait(); aica_sound.channel[ch].KYONB(1);
        state.channel[ch].keyon = 0;
      }
      break;
    } else if (state.channel[ch].keyon == 1) {
      wait(); aica_sound.common.afsel_mslc_mobuf
                = aica::afsel_mslc_mobuf::AFSEL(0)
                | aica::afsel_mslc_mobuf::MSLC(ch);
      state.channel[ch].keyon = 2;
      break;
    }
    */
    const int keyon_tick = 254;
    if (state.channel[ch].keyon > keyon_tick) {
      state.channel[ch].keyon -= 1;
    }
    else if (state.channel[ch].keyon == keyon_tick) {
      wait(); aica_sound.channel[ch].KYONB(1);
      state.channel[ch].keyon -= 1;
    }
  }
  wait(); aica_sound.channel[0].KYONEX(1);

  if ((state.interrupt_clock % state.current_tick_rate) != 0) {
    return;
  }
  for (int ch = 0; ch < 64; ch++) {
    int keyon = state.channel[ch].keyon;
    if (keyon != 0) {
      state.channel[ch].keyon -= 1;
    }
  }

  int tick = state.tick % state.ticks_per_line;
  bool note_tick = tick == 0;
  if (note_tick) {
    // execute notes
    state.line_index = state.next_line_index;
    if (state.reverse)
      state.next_line_index -= 1;
    else
      state.next_line_index += 1;

    execute_line<play_note>(state.line_index);
  } else {
    // execute effects
    execute_line<play_note_effect>(state.line_index);
  }
  wait(); aica_sound.channel[0].KYONEX(1);

  bool pattern_break_tick = tick == (state.ticks_per_line - 1);
  if (pattern_break_tick) {
    if (state.pattern_break >= 0) {
      printf("pattern_break\n");
      next_pattern();
    }
    int note_index = state.next_line_index * state.xm.number_of_channels;
    bool end_of_pattern
       = note_index < 0
      || note_index >= state.xm.pattern_note_count[state.pattern_index];

    if (end_of_pattern) {
      printf("end_of_pattern\n");
      next_pattern();
    }
  }

  state.tick += 1;
}

void init(float clock_multiplier)
{
  // 195 = 1ms
  // 2500 / bpm milliseconds

  int default_bpm = s16(&state.xm.header->default_bpm);
  int default_tempo = s16(&state.xm.header->default_tempo);
  int tick_rate = clock_multiplier * 2500 / default_bpm;

  printf("default_bpm %d\n", default_bpm);
  printf("default_tempo %d\n", default_tempo);
  printf("tick_rate %d\n", tick_rate);

  state.current_tick_rate = tick_rate;
  state.default_tick_rate = tick_rate;
  state.ticks_per_line = default_tempo;
  state.tick = 0;
  state.line_index = 0;
  state.pattern_order_table_index = -1;
  next_pattern();
  for (int ch = 0; ch < 64; ch++) {
    state.channel[ch].keyon = 0;
    state.channel[ch].volume = 64;
  }
  state.paused = false;

  printf("tick_rate %d\n", state.current_tick_rate);
}

void stop_sound()
{
  for (int ch = 0; ch < 64; ch++) {
    wait();
    //bool kyonb = aica_sound.channel[ch].KYONB() != 0;
    wait(); aica_sound.channel[ch].KYONB(0);
    //state.channel[ch].keyon = kyonb ? 255 : 0;
  }
  wait(); aica_sound.channel[0].KYONEX(1);
}

void resume_sound()
{
  for (int ch = 0; ch < 64; ch++) {
    wait(); aica_sound.channel[ch].RR(0xa);
  }
}

void pause()
{
  stop_sound();
  state.paused = true;
}

void unpause()
{
  state.paused = false;
}

static uint8_t __attribute__((aligned(32))) sample_data[1024 * 1024 * 2];
const int sample_data_length = (sizeof (sample_data));

void deferred_load(int buf, int cover_ix)
{
  const float aica_clock_multiplier = 44.1 / 3;
  state.deferred_cover_ix = cover_ix;

  state.deferred_load_tick = aica_clock_multiplier * 1000 / 2;

  state.sample_data_ix = xm_init(&interpreter::state.xm,
                                 buf,
                                 sample_data,
                                 sample_data_length);
  interpreter::init(aica_clock_multiplier);
}

void deferred_load_finish()
{
  sound::transfer(sample_data, state.sample_data_ix);

  cover::cover_ix = state.deferred_cover_ix;

  resume_sound();
}

}
