#include "xm/xm.h"
#include "printf/printf.h"
#include "xm.h"
#include "malloc.h"

static int xm_unpack_sample(int buf,
                            int offset,
                            xm_sample_header_t * sample_header,
                            uint8_t * sample_data,
                            int sample_data_ix)
{
  int size = s32(&sample_header->sample_length);
  int loop_start = s32(&sample_header->sample_loop_start);
  int loop_length = s32(&sample_header->sample_loop_length);

  int loop_type = sample_header->type & 0b11;

  if (sample_header->type & (1 << 4)) { // 16-bit samples
    int num_samples = size / 2;
    int lsa = loop_start / 2;
    int len = loop_length / 2;

    int old = 0;
    volatile int16_t * out = (volatile int16_t *)(&sample_data[sample_data_ix]);
    int16_t * in = (int16_t *)(buf + offset);
    for (int i = 0; i < num_samples; i++) {
      old += s16(&in[i]);
      out[i] = old;
    }

    if (loop_type == 2) { // bidirectional
      for (int i = 0; i < len - 2; i++) {
        out[num_samples + i] = out[lsa + (len - i - 2)];
      }

      size += (len - 2) * 2;
    }

  } else { // 8-bit
    int num_samples = size;
    int lsa = loop_start;
    int len = loop_length;

    int old = 0;
    volatile int8_t * out = (volatile int8_t *)(&sample_data[sample_data_ix]);
    int8_t * in = (int8_t *)(buf + offset);
    for (int i = 0; i < num_samples; i++) {
      old += in[i];
      out[i] = old;
    }

    if (loop_type == 2) { // bidirectional
      for (int i = 0; i < len - 2; i++) {
        out[num_samples + i] = out[lsa + (len - i - 2)];
      }

      size += (len - 2);
    }
  }

  if (size & 1) {
    size += 1;
  }

  return size;
}

static int xm_samples_init(xm_state_t * xm,
                           int buf,
                           int offset,
                           int instrument_ix,
                           int number_of_samples,
                           uint8_t * sample_data,
                           int sample_data_length,
                           int * sample_data_ix)
{
  xm_sample_header_t * sample_header[number_of_samples];
  xm->sample_header[instrument_ix] = (xm_sample_header_t *)(buf + offset);
  //if (instrument_ix <= 12)
    //debug_xm_sample_header(instrument_ix, xm->sample_header[instrument_ix]);

  for (int i = 0; i < number_of_samples; i++) {
    sample_header[i] = (xm_sample_header_t *)(buf + offset);
    offset += (sizeof (xm_sample_header_t));
  }

  for (int i = 0; i < number_of_samples; i++) {
    int sample_length = s32(&sample_header[i]->sample_length);
    if (sample_length > 0) {
      //printf("  sample_length % 6d\n", sample_length);
      xm->sample_data_offset[instrument_ix] = *sample_data_ix;
      *sample_data_ix += xm_unpack_sample(buf,
                                          offset,
                                          sample_header[i],
                                          sample_data,
                                          *sample_data_ix);
      assert(*sample_data_ix <= sample_data_length);
    }
    offset += sample_length;
  }
  return offset;
}

static inline xm_pattern_format_t parse_pattern_line(uint8_t * pattern, int * note_offset)
{
  int offset = *note_offset;

  int p = pattern[offset];
  if (p & 0x80) {
    offset += 1;
    xm_pattern_format_t pf = {};
    if (p & (1 << 0))
      pf.note = pattern[offset++];
    if (p & (1 << 1))
      pf.instrument = pattern[offset++];
    if (p & (1 << 2))
      pf.volume_column_byte = pattern[offset++];
    if (p & (1 << 3))
      pf.effect_type = pattern[offset++];
    if (p & (1 << 4))
      pf.effect_parameter = pattern[offset++];
    *note_offset = offset;
    return pf;
  } else {
    xm_pattern_format_t * pf = (xm_pattern_format_t *)&pattern[offset];
    offset += 5;
    *note_offset = offset;
    return *pf;
  }
}

static inline int count_pattern_notes(uint8_t * pattern, int pattern_data_size)
{
  int note_offset = 0;
  int note_count = 0;

  while (note_offset < pattern_data_size) {
    parse_pattern_line(pattern, &note_offset);
    note_count += 1;
  }
  assert(note_offset == pattern_data_size);

  return note_count;
}

void xm_unpack_pattern(xm_state_t * xm,
                       int pattern_index)
{
  xm_pattern_header_t * pattern_header = xm->pattern_header[pattern_index];
  uint8_t * pattern = (uint8_t *)(((int)pattern_header) + s32(&pattern_header->pattern_header_length));

  int pattern_data_size = s16(&pattern_header->packed_pattern_data_size);

  int note_count = count_pattern_notes(pattern, pattern_data_size);

  xm_pattern_format_t * pf = (xm_pattern_format_t *)malloc_arena((sizeof (xm_pattern_format_t)) * note_count);

  xm->pattern[pattern_index] = pf;
  xm->pattern_note_count[pattern_index] = note_count;

  int note_offset = 0;
  for (int i = 0; i < note_count; i++) {
    pf[i] = parse_pattern_line(pattern, &note_offset);
  }
  assert(note_offset == pattern_data_size);
}

int xm_init(xm_state_t * xm,
            int buf,
            uint8_t * sample_data,
            int sample_data_length)
{
  int sample_data_ix = 0;

  xm->header = (xm_header_t *)(buf);

  int offset = s32(&xm->header->header_size) + (offsetof (struct xm_header, header_size));
  int number_of_patterns = s16(&xm->header->number_of_patterns);
  printf("number_of_patterns: %d\n", number_of_patterns);

  for (int i = 0; i < number_of_patterns; i++) {
    xm_pattern_header_t * pattern_header = (xm_pattern_header_t *)(buf + offset);
    xm->pattern_header[i] = pattern_header;
    offset += s32(&pattern_header->pattern_header_length) + s16(&pattern_header->packed_pattern_data_size);
  }
  printf("end_of_patterns: %d\n", offset);

  int number_of_instruments = s16(&xm->header->number_of_instruments);
  for (int instrument_ix = 0; instrument_ix < number_of_instruments; instrument_ix++) {
    xm_instrument_header_t * instrument_header = (xm_instrument_header_t *)(buf + offset);

    xm->instrument_header[instrument_ix] = instrument_header;
    offset += s32(&instrument_header->instrument_size);

    int number_of_samples = s16(&instrument_header->number_of_samples);
    offset = xm_samples_init(xm,
                             buf,
                             offset,
                             instrument_ix,
                             number_of_samples,
                             sample_data,
                             sample_data_length,
                             &sample_data_ix);
  }
  printf("end_of_instruments: %d\n", offset);

  int number_of_channels = s16(&xm->header->number_of_channels);
  xm->number_of_channels = number_of_channels;
  printf("number_of_channels: %d\n", number_of_channels);

  int song_length = s16(&xm->header->song_length);
  xm->song_length = song_length;
  printf("song_length: %d\n", song_length);

  // reset arena
  malloc_arena_reset();

  for (int pattern_index = 0; pattern_index < number_of_patterns; pattern_index++) {
    xm_unpack_pattern(xm, pattern_index);
  }

  return sample_data_ix;
}
