#include <stdint.h>

#include "memorymap.hpp"

#include "systembus.hpp"
#include "systembus_bits.hpp"

#include "sh7091/sh7091.hpp"
#include "sh7091/sh7091_bits.hpp"
#include "sh7091/serial.hpp"

#include "printf/printf.h"

#include "aica/aica.hpp"

#include "sound.hpp"

#include "assert.h"

static void g2_aica_dma(uint32_t g2_address, uint32_t system_address, int length)
{
  using namespace dmac;

  constexpr uint32_t dma_address_mask = 0x1fffffe0;

  length = (length + 31) & (~31);

  // is DMAOR needed?
  sh7091.DMAC.DMAOR = dmaor::ddt::on_demand_data_transfer_mode       /* on-demand data transfer mode */
                    | dmaor::pr::ch2_ch0_ch1_ch3                     /* priority mode; CH2 > CH0 > CH1 > CH3 */
                    | dmaor::dme::operation_enabled_on_all_channels; /* DMAC master enable */


  g2_if.ADEN = 0; // disable G2-AICA-DMA

  g2_if.G2APRO = 0x4659007f; // disable protection

  g2_if.ADSTAG = dma_address_mask & g2_address; // G2 address
  g2_if.ADSTAR = dma_address_mask & system_address; // system memory address
  g2_if.ADLEN = length;
  g2_if.ADDIR = 0; // from root bus to G2 device
  g2_if.ADTSEL = 0; // CPU controlled trigger
  g2_if.ADEN = 1; // enable G2-AICA-DMA
  g2_if.ADST = 1; // start G2-AICA-DMA
}

static void g2_aica_dma_wait_complete()
{
  // wait for maple DMA completion
  while ((system.ISTNRM & istnrm::end_of_dma_aica_dma) == 0);
  system.ISTNRM = istnrm::end_of_dma_aica_dma;
  assert(g2_if.ADST == 0);
}

static void writeback(void const * const buf, uint32_t size)
{
  uint8_t const * const buf8 = reinterpret_cast<uint8_t const * const>(buf);

  for (uint32_t i = 0; i < size / (32); i++) {
    asm volatile ("ocbwb @%0"
		  :                          // output
		  : "r" (&buf8[i * 32]) // input
                  : "memory"
		  );
  }
}

static uint8_t __attribute__((aligned(32))) zero[0x28c0] = {};

namespace sound {

void init()
{
  printf("sound::init\n");

  wait(); aica_sound.common.vreg_armrst = aica::vreg_armrst::ARMRST(1);
  wait(); aica_sound.common.dmea0_mrwinh = aica::dmea0_mrwinh::MRWINH(0b0111);
  system.ISTNRM = istnrm::end_of_dma_aica_dma;

  writeback(zero, (sizeof (zero)));

  // slot/common: 00700000 - 007028c0 (excludes vreg_armrst)
  g2_aica_dma((uint32_t)0x00700000, (int)zero, 0x28c0);
  g2_aica_dma_wait_complete();

  // dsp        : 00703000 - 007045c8
  g2_aica_dma((uint32_t)0x00703000, (int)zero, 0x15e0);
  g2_aica_dma_wait_complete();

  wait(); aica_sound.common.dmea0_mrwinh = aica::dmea0_mrwinh::MRWINH(0b0001);

  for (int i = 0; i < 64; i++) {
    wait(); aica_sound.channel[i].KYONB(0);
    wait(); aica_sound.channel[i].LPCTL(0);
    wait(); aica_sound.channel[i].PCMS(0);
    wait(); aica_sound.channel[i].LSA(0);
    wait(); aica_sound.channel[i].LEA(0);

    wait(); aica_sound.channel[i].D2R(0xa);
    wait(); aica_sound.channel[i].D1R(0xa);
    wait(); aica_sound.channel[i].RR(0xa);
    wait(); aica_sound.channel[i].AR(0x1f);
    /*
    wait(); aica_sound.channel[i].D2R(0);
    wait(); aica_sound.channel[i].D1R(0);
    wait(); aica_sound.channel[i].RR(0x1f);
    wait(); aica_sound.channel[i].AR(0x1f);
    */

    wait(); aica_sound.channel[i].ALFOS(0);
    wait(); aica_sound.channel[i].PLFOS(0);

    wait(); aica_sound.channel[i].OCT(0);
    wait(); aica_sound.channel[i].FNS(0);
    wait(); aica_sound.channel[i].DISDL(0);
    wait(); aica_sound.channel[i].DIPAN(0);

    wait(); aica_sound.channel[i].Q(0b00100);
    wait(); aica_sound.channel[i].TL(0);
    wait(); aica_sound.channel[i].LPOFF(1);
  }

  wait(); aica_sound.channel[0].KYONEX(1);

  wait(); aica_sound.common.mono_mem8mb_dac18b_ver_mvol =
      aica::mono_mem8mb_dac18b_ver_mvol::MONO(0)   // enable panpots
    | aica::mono_mem8mb_dac18b_ver_mvol::MEM8MB(0) // 16Mbit SDRAM
    | aica::mono_mem8mb_dac18b_ver_mvol::DAC18B(0) // 16-bit DAC
    | aica::mono_mem8mb_dac18b_ver_mvol::MVOL(0xc) // volume
    ;

  wait(); aica_sound.common.tactl_tima =
      aica::tactl_tima::TACTL(0)  // increment once every sample
    | aica::tactl_tima::TIMA(0xffff) // interrupt after 1 counts
    ;

  wait(); aica_sound.common.mcieb = (1 << 6); // interrupt timer A
  wait(); aica_sound.common.mcire = (1 << 6); // interrupt timer A
}

void transfer(const void * sample_data, int sample_data_ix)
{
  printf("aica transfer 0x%08x 0x%08x 0x%x\n", (int)aica_wave_memory, (int)sample_data, sample_data_ix);

  int size = (sample_data_ix + 31) & (~31);
  writeback(sample_data, size);

  g2_aica_dma((int)aica_wave_memory, (int)sample_data, size);
  g2_aica_dma_wait_complete();
}

}
