#include <string.h>

#include "../Util.h"
#include "../gba/Sound.h"
#include "gb.h"
#include "gbGlobals.h"
#include "gbSound.h"

#include "../apu/Effects_Buffer.h"
#include "../apu/Gb_Apu.h"

extern long soundSampleRate; // current sound quality

gb_effects_config_t gb_effects_config = { false, 0.20f, 0.15f, false };

static gb_effects_config_t gb_effects_config_current;
static Simple_Effects_Buffer* stereo_buffer;
static Gb_Apu* gb_apu;

static float soundVolume_ = -1;
static int prevSoundEnable = -1;
static bool declicking = false;

int const chan_count = 4;
int const ticks_to_time = 2 * GB_APU_OVERCLOCK;

uint8_t gbSoundRead(int st, uint16_t address)
{
    if (gb_apu && address >= NR10 && address <= 0xFF3F)
        return gb_apu->read_register((blip_time_t)(st * ticks_to_time), address);

    return gbMemory[address];
}

void gbSoundEvent(int st, uint16_t address, int data)
{
    gbMemory[address] = data;

    if (gb_apu && address >= NR10 && address <= 0xFF3F)
        gb_apu->write_register((blip_time_t)(st * ticks_to_time), address, data);
}

static void end_frame(blip_time_t time)
{
    gb_apu->end_frame(time);
    stereo_buffer->end_frame(time);
}

static void apply_effects()
{
    prevSoundEnable = soundGetEnable();
    gb_effects_config_current = gb_effects_config;

    stereo_buffer->config().enabled = gb_effects_config_current.enabled;
    stereo_buffer->config().echo = gb_effects_config_current.echo;
    stereo_buffer->config().stereo = gb_effects_config_current.stereo;
    stereo_buffer->config().surround = gb_effects_config_current.surround;
    stereo_buffer->apply_config();

    for (int i = 0; i < chan_count; i++) {
        Multi_Buffer::channel_t ch = { 0, 0, 0 };
        if (prevSoundEnable >> i & 1)
            ch = stereo_buffer->channel(i);
        gb_apu->set_output(ch.center, ch.left, ch.right, i);
    }
}

void gbSoundConfigEffects(gb_effects_config_t const& c)
{
    gb_effects_config = c;
}

static void apply_volume()
{
    soundVolume_ = soundGetVolume();

    if (gb_apu)
        gb_apu->volume(soundVolume_);
}

void gbSoundTick(int st)
{
    if (gb_apu && stereo_buffer) {
        // Run sound hardware to present
        end_frame((blip_time_t)(st * ticks_to_time));

        flush_samples(stereo_buffer);

        // Update effects config if it was changed
        if (memcmp(&gb_effects_config_current, &gb_effects_config,
                sizeof gb_effects_config)
            || soundGetEnable() != prevSoundEnable)
            apply_effects();

        if (soundVolume_ != soundGetVolume())
            apply_volume();
    }

    soundTicks = 0;
}

static void reset_apu()
{
    Gb_Apu::mode_t mode = Gb_Apu::mode_dmg;
    if (gbHardware & 2)
        mode = Gb_Apu::mode_cgb;
    if (gbHardware & 8 || declicking)
        mode = Gb_Apu::mode_agb;
    gb_apu->reset(mode);
    gb_apu->reduce_clicks(declicking);

    if (stereo_buffer)
        stereo_buffer->clear();

    soundTicks = 0;
}

static void remake_stereo_buffer()
{
    // APU
    if (!gb_apu) {
        gb_apu = new Gb_Apu; // TODO: handle errors
        reset_apu();
    }

    // Stereo_Buffer
    delete stereo_buffer;
    stereo_buffer = 0;

    stereo_buffer = new Simple_Effects_Buffer; // TODO: handle out of memory
    if (stereo_buffer->set_sample_rate(soundSampleRate)) {
    } // TODO: handle out of memory
    stereo_buffer->clock_rate(gb_apu->clock_rate);

    // Multi_Buffer
    static int const chan_types[chan_count] = {
        Multi_Buffer::wave_type + 1, Multi_Buffer::wave_type + 2,
        Multi_Buffer::wave_type + 3, Multi_Buffer::mixed_type + 1
    };
    if (stereo_buffer->set_channel_count(chan_count, chan_types)) {
    } // TODO: handle errors

    // Volume Level
    apply_effects();
    apply_volume();
}

void gbSoundSetDeclicking(bool enable)
{
    if (declicking != enable) {
        declicking = enable;
        if (gb_apu) {
            // Can't change sound hardware mode without resetting APU, so save/load
            // state around mode change
            gb_apu_state_t state;
            gb_apu->save_state(&state);
            reset_apu();
            if (gb_apu->load_state(state)) {
            } // ignore error
        }
    }
}

bool gbSoundGetDeclicking()
{
    return declicking;
}

void gbSoundReset()
{
    SOUND_CLOCK_TICKS = 35112;

    remake_stereo_buffer();
    reset_apu();

    soundPaused = 1;

    gbSoundEvent(0, 0xff10, 0x80);
    gbSoundEvent(0, 0xff11, 0xbf);
    gbSoundEvent(0, 0xff12, 0xf3);
    gbSoundEvent(0, 0xff14, 0xbf);
    gbSoundEvent(0, 0xff16, 0x3f);
    gbSoundEvent(0, 0xff17, 0x00);
    gbSoundEvent(0, 0xff19, 0xbf);

    gbSoundEvent(0, 0xff1a, 0x7f);
    gbSoundEvent(0, 0xff1b, 0xff);
    gbSoundEvent(0, 0xff1c, 0xbf);
    gbSoundEvent(0, 0xff1e, 0xbf);

    gbSoundEvent(0, 0xff20, 0xff);
    gbSoundEvent(0, 0xff21, 0x00);
    gbSoundEvent(0, 0xff22, 0x00);
    gbSoundEvent(0, 0xff23, 0xbf);
    gbSoundEvent(0, 0xff24, 0x77);
    gbSoundEvent(0, 0xff25, 0xf3);

    if (gbHardware & 0x4)
        gbSoundEvent(0, 0xff26, 0xf0);
    else
        gbSoundEvent(0, 0xff26, 0xf1);

    /* workaround for game Beetlejuice */
    if (gbHardware & 0x1) {
        gbSoundEvent(0, 0xff24, 0x77);
        gbSoundEvent(0, 0xff25, 0xf3);
    }

    int addr = 0xff30;

    while (addr < 0xff40) {
        gbMemory[addr++] = 0x00;
        gbMemory[addr++] = 0xff;
    }
}

void gbSoundSetSampleRate(long sampleRate)
{
    if (soundSampleRate != sampleRate) {
        if (systemCanChangeSoundQuality()) {
            soundShutdown();
            soundSampleRate = sampleRate;
            soundInit();
        } else {
            soundSampleRate = sampleRate;
        }

        remake_stereo_buffer();
    }
}

static struct {
    int version;
    gb_apu_state_t apu;
} state;

static char dummy_state[735 * 2];

#define SKIP(type, name)          \
    {                             \
        dummy_state, sizeof(type) \
    }

#define LOAD(type, name)    \
    {                       \
        &name, sizeof(type) \
    }

#ifndef __LIBRETRO__
// Old save state support

static variable_desc gbsound_format[] = {
    SKIP(int, soundPaused),
    SKIP(int, soundPlay),
    SKIP(int, soundTicks),
    SKIP(int, SOUND_CLOCK_TICKS),
    SKIP(int, soundLevel1),
    SKIP(int, soundLevel2),
    SKIP(int, soundBalance),
    SKIP(int, soundMasterOn),
    SKIP(int, soundIndex),
    SKIP(int, soundVIN),
    SKIP(int, soundOn[0]),
    SKIP(int, soundATL[0]),
    SKIP(int, sound1Skip),
    SKIP(int, soundIndex[0]),
    SKIP(int, sound1Continue),
    SKIP(int, soundEnvelopeVolume[0]),
    SKIP(int, soundEnvelopeATL[0]),
    SKIP(int, sound1EnvelopeATLReload),
    SKIP(int, sound1EnvelopeUpDown),
    SKIP(int, sound1SweepATL),
    SKIP(int, sound1SweepATLReload),
    SKIP(int, sound1SweepSteps),
    SKIP(int, sound1SweepUpDown),
    SKIP(int, sound1SweepStep),
    SKIP(int, soundOn[1]),
    SKIP(int, soundATL[1]),
    SKIP(int, sound2Skip),
    SKIP(int, soundIndex[1]),
    SKIP(int, sound2Continue),
    SKIP(int, soundEnvelopeVolume[1]),
    SKIP(int, soundEnvelopeATL[1]),
    SKIP(int, sound2EnvelopeATLReload),
    SKIP(int, sound2EnvelopeUpDown),
    SKIP(int, soundOn[2]),
    SKIP(int, soundATL[2]),
    SKIP(int, sound3Skip),
    SKIP(int, soundIndex[2]),
    SKIP(int, sound3Continue),
    SKIP(int, sound3OutputLevel),
    SKIP(int, soundOn[3]),
    SKIP(int, soundATL[3]),
    SKIP(int, sound4Skip),
    SKIP(int, soundIndex[3]),
    SKIP(int, sound4Clock),
    SKIP(int, sound4ShiftRight),
    SKIP(int, sound4ShiftSkip),
    SKIP(int, sound4ShiftIndex),
    SKIP(int, sound4NSteps),
    SKIP(int, sound4CountDown),
    SKIP(int, sound4Continue),
    SKIP(int, soundEnvelopeVolume[2]),
    SKIP(int, soundEnvelopeATL[2]),
    SKIP(int, sound4EnvelopeATLReload),
    SKIP(int, sound4EnvelopeUpDown),
    SKIP(int, soundEnableFlag),
    { NULL, 0 }
};

static variable_desc gbsound_format2[] = {
    SKIP(int, sound1ATLreload),
    SKIP(int, freq1low),
    SKIP(int, freq1high),
    SKIP(int, sound2ATLreload),
    SKIP(int, freq2low),
    SKIP(int, freq2high),
    SKIP(int, sound3ATLreload),
    SKIP(int, freq3low),
    SKIP(int, freq3high),
    SKIP(int, sound4ATLreload),
    SKIP(int, freq4),
    { NULL, 0 }
};

static variable_desc gbsound_format3[] = {
    SKIP(uint8_t[2 * 735], soundBuffer),
    SKIP(uint8_t[2 * 735], soundBuffer),
    SKIP(uint16_t[735], soundFinalWave),
    { NULL, 0 }
};

enum {
    nr10 = 0,
    nr11,
    nr12,
    nr13,
    nr14,
    nr20,
    nr21,
    nr22,
    nr23,
    nr24,
    nr30,
    nr31,
    nr32,
    nr33,
    nr34,
    nr40,
    nr41,
    nr42,
    nr43,
    nr44,
    nr50,
    nr51,
    nr52
};

static void gbSoundReadGameOld(int version, gzFile gzFile)
{
    if (version == 11) {
        // Version 11 didn't save any state
        // TODO: same for version 10?
        state.apu.regs[nr50] = 0x77; // volume at max
        state.apu.regs[nr51] = 0xFF; // channels enabled
        state.apu.regs[nr52] = 0x80; // power on
        return;
    }

    // Load state
    utilReadData(gzFile, gbsound_format);

    if (version >= 11) // TODO: never executed; remove?
        utilReadData(gzFile, gbsound_format2);

    utilReadData(gzFile, gbsound_format3);

    int quality = 1;
    if (version >= 7)
        quality = utilReadInt(gzFile);

    gbSoundSetSampleRate(44100 / quality);

    // Convert to format Gb_Apu uses
    gb_apu_state_t& s = state.apu;

    // Only some registers are properly preserved
    static int const regs_to_copy[] = {
        nr10, nr11, nr12, nr21, nr22, nr30, nr32, nr42, nr43, nr50, nr51, nr52, -1
    };
    for (int i = 0; regs_to_copy[i] >= 0; i++)
        s.regs[regs_to_copy[i]] = gbMemory[0xFF10 + regs_to_copy[i]];

    memcpy(&s.regs[0x20], &gbMemory[0xFF30], 0x10); // wave
}
#endif // ! __LIBRETRO__

// New state format
static variable_desc gb_state[] = {
    LOAD(int, state.version), // room_for_expansion will be used by later versions

    // APU
    LOAD(uint8_t[0x40], state.apu.regs), // last values written to registers and wave RAM (both banks)
    LOAD(int, state.apu.frame_time), // clocks until next frame sequencer action
    LOAD(int, state.apu.frame_phase), // next step frame sequencer will run

    LOAD(int, state.apu.sweep_freq), // sweep's internal frequency register
    LOAD(int, state.apu.sweep_delay), // clocks until next sweep action
    LOAD(int, state.apu.sweep_enabled),
    LOAD(int, state.apu.sweep_neg), // obscure internal flag
    LOAD(int, state.apu.noise_divider),
    LOAD(int, state.apu.wave_buf), // last read byte of wave RAM

    LOAD(int[4], state.apu.delay), // clocks until next channel action
    LOAD(int[4], state.apu.length_ctr),
    LOAD(int[4], state.apu.phase), // square/wave phase, noise LFSR
    LOAD(int[4], state.apu.enabled), // internal enabled flag

    LOAD(int[3], state.apu.env_delay), // clocks until next envelope action
    LOAD(int[3], state.apu.env_volume),
    LOAD(int[3], state.apu.env_enabled),

    SKIP(int[13], room_for_expansion),

    // Emulator
    LOAD(int, soundTicks),
    SKIP(int[15], room_for_expansion),

    { NULL, 0 }
};

#ifndef __LIBRETRO__
void gbSoundSaveGame(gzFile out)
{
    gb_apu->save_state(&state.apu);

    // Be sure areas for expansion get written as zero
    memset(dummy_state, 0, sizeof dummy_state);

    state.version = 1;
    utilWriteData(out, gb_state);
}

void gbSoundReadGame(int version, gzFile in)
{
    // Prepare APU and default state
    reset_apu();
    gb_apu->save_state(&state.apu);

    if (version > 11)
        utilReadData(in, gb_state);
    else
        gbSoundReadGameOld(version, in);

    gb_apu->load_state(state.apu);
}
#endif // ! __LIBRETRO__

#ifdef __LIBRETRO__
void gbSoundSaveGame(uint8_t*& out)
{
    gb_apu->save_state(&state.apu);

    // Be sure areas for expansion get written as zero
    memset(dummy_state, 0, sizeof dummy_state);

    state.version = 1;
    utilWriteDataMem(out, gb_state);
}

void gbSoundReadGame(const uint8_t*& in)
{
    // Prepare APU and default state
    reset_apu();
    gb_apu->save_state(&state.apu);

    utilReadDataMem(in, gb_state);
    gb_apu->load_state(state.apu);
}
#endif // __LIBRETRO__
