#include "APU.h"
#include <SDL2/SDL.h>
//#include "audio_viewer.h"
namespace APU
{
    #define SAMPLING_FREQ 44100
    #define AUDIO_BUFFER_SIZE 2048
    const float cycles_per_sample = 4194304.0f / SAMPLING_FREQ;

    SDL_AudioDeviceID sdlAudioDevice;
    uint8_t sound_buffer[AUDIO_BUFFER_SIZE];
    uint16_t sound_pos=0;
    uint16_t sound_start=0;
    float t_sound = 0.0f;
    uint32_t samples_generated=0;

    #define CH1 channels[0]
    #define CH2 channels[1]
    #define CH3 channels[2]
    #define CH4 channels[3]

    #define apu_enabled (NR52&0x80)
    #define DAC1_enabled ((NR12&0xf8)!=0)
    #define DAC2_enabled ((NR22&0xf8)!=0)
    #define DAC3_enabled ((NR30&0x80)!=0)
    #define DAC4_enabled ((NR42&0xf8)!=0)

    uint8_t duty_cycles[4][8] = {
        {1, 1, 1, 1, 1, 1, 1, 0},
        {1, 1, 1, 1, 1, 1, 0, 0},
        {1, 1, 1, 1, 0, 0, 0, 0},
        {1, 1, 0, 0, 0, 0, 0, 0},
    };
    struct channel_t
    {
        bool enabled = false;
        uint8_t length_timer = 0;
        bool length_enable = false;
        uint16_t period_divider = 0;
        uint8_t duty_cycle=0;
        uint8_t duty_step = 0;
        uint8_t volume = 0;
        uint8_t envelope_sweep_timer=0;
    };

    channel_t channels[4];
    uint8_t DIVAPU = 0;

    uint8_t NR10 = 0; // 0xff10 - Sound channel 1 sweep
    uint8_t NR11 = 0; // 0xff11 - Sound channel 1 length timer & duty cycle
    uint8_t NR12 = 0; // 0xff12 - Sound channel 1 volume & envelope
    uint8_t NR13 = 0; // 0xff13 - Sound channel 1 period low
    uint8_t NR14 = 0; // 0xff14 - Sound channel 1 period high & control
    // 0xff15 - Not used?
    uint8_t NR21 = 0; // 0xff16 - Sound channel 2 length timer & duty cycle
    uint8_t NR22 = 0; // 0xff17 - Sound channel 2 volume & envelope
    uint8_t NR23 = 0; // 0xff18 - Sound channel 2 period low
    uint8_t NR24 = 0; // 0xff19 - Sound channel 2 period high & control

    uint8_t NR30 = 0; // 0xff1a - Sound channel 3 DAC enable
    uint8_t NR31 = 0; // 0xff1b - Sound channel 3 length timer
    uint8_t NR32 = 0; // 0xff1c - Sound channel 3 output level
    uint8_t NR33 = 0; // 0xff1d - Sound channel 3 period low
    uint8_t NR34 = 0; // 0xff1e - Sound channel 3 period high & control
    // 0xff1f - Not used?
    uint8_t NR41 = 0; // 0xff20 - Sound channel 4 length timer
    uint8_t NR42 = 0; // 0xff21 - Sound channel 4 volume & envelope
    uint8_t NR43 = 0; // 0xff22 - Sound channel 4 frequency & randomness
    uint8_t NR44 = 0; // 0xff23 - Sound channel 4 control
    uint8_t NR50 = 0; // 0xff24 - Master volume & VIN panning
    uint8_t NR51 = 0; // 0xff25 - Sound panning
    uint8_t NR52 = 0; // 0xff26 - Sound on/off
    // 0xff27-0xff2f - Not used?
    uint8_t WaveRAM[16]; // 0xff30-0xff3f
    uint16_t LFSR = 0;


    void silence()
    {
        SDL_PauseAudioDevice(sdlAudioDevice, 1);
    }

    void resume()
    {
        SDL_PauseAudioDevice(sdlAudioDevice, 0);
    }

    void init()
    {
        SDL_AudioSpec audioSpec{SAMPLING_FREQ, AUDIO_U8, 1, 0, AUDIO_BUFFER_SIZE, 0, 0, NULL, NULL};
        sdlAudioDevice = SDL_OpenAudioDevice(NULL, 0, &audioSpec, NULL, 0);
        resume();
    }

    void reset()
    {
        NR10 = 0;
        NR11 = 0;
        NR12 = 0;
        NR13 = 0;
        NR14 = 0;
        NR21 = 0;
        NR22 = 0;
        NR23 = 0;
        NR24 = 0;
        NR30 = 0;
        NR31 = 0;
        NR32 = 0;
        NR33 = 0;
        NR34 = 0;
        NR41 = 0;
        NR42 = 0;
        NR43 = 0;
        NR44 = 0;
        NR50 = 0;
        NR51 = 0;
        NR52 = 0;
        CH1.duty_cycle = 0;
        LFSR = 0;
        for (int i=0; i<16; ++i) WaveRAM[i]=0;
    }

    uint8_t getLFSR()
    {
        LFSR = (LFSR&0x7fff) | ( (LFSR&1)==((LFSR>>1)&1) ? 0x8000 : 0x0000 );
        if (NR43&0x08) LFSR = (LFSR&0xff7f) | ((LFSR>>8)&0x0080);
        LFSR=LFSR>>1;

        return LFSR&1;
    }

    void triggerCH1()
    {
        CH1.enabled = true;
        CH1.length_timer=NR11&0x3f;
        CH1.period_divider = NR13 | ((NR14 &0x7)<<8);
        CH1.envelope_sweep_timer = 0;
        CH1.volume = NR12>>4;
        // sweep does several things (check documentation)
    }

    void triggerCH2()
    {
        CH2.enabled = true;
        CH2.length_timer=NR21&0x3f;
        CH2.period_divider = NR23 | ((NR24 &0x7)<<8);
        CH2.envelope_sweep_timer = 0;
        CH2.volume = NR22>>4;
        // sweep does several things (check documentation)
    }

    void triggerCH3()
    {
        CH3.enabled = true;
        CH3.length_timer=NR31;
        CH3.period_divider = NR33 | ((NR34 &0x7)<<8);
        CH3.envelope_sweep_timer = 0;
        CH3.volume = (NR32>>5)&0x3;
        // sweep does several things (check documentation)
    }

    void triggerCH4()
    {
        CH4.enabled = true;
        CH4.length_timer=NR41&0x3f;
        uint8_t clock_shift = ((NR43&0xf0)>>4);// if (clock_shift==0) clock_shift = 1;
        CH4.period_divider = clock_shift << (NR43&0x07);
        CH4.envelope_sweep_timer = 0;
        CH4.volume = NR42>>4;
        // sweep does several things (check documentation)
    }

    uint8_t readRegister(uint16_t address)
    {
        switch(address)
        {
            case 0xff10: return NR10; break;
            case 0xff11: return NR11 & 0xc0; break;
            case 0xff12: return NR12; break;
            case 0xff13: return 0x00; break;
            case 0xff14: return NR14 & 0x40; break;

            case 0xff16: return NR21 & 0xc0; break;
            case 0xff17: return NR22; break;
            case 0xff18: return 0x00; break;
            case 0xff19: return NR24 & 0x40; break;

            case 0xff1a: return NR30; break;
            case 0xff1b: return 0x00; break;
            case 0xff1c: return NR32; break;
            case 0xff1d: return 0x00; break;
            case 0xff1e: return NR34 & 0x40; break;

            case 0xff20: return 0x00; break;
            case 0xff21: return NR42; break;
            case 0xff22: return NR43; break;
            case 0xff23: return NR44 & 0x40; break;

            case 0xff24: return NR50; break;
            case 0xff25: return NR51; break;
            case 0xff26: return NR52; break;

            case 0xff30: return WaveRAM[0]; break;
            case 0xff31: return WaveRAM[1]; break;
            case 0xff32: return WaveRAM[2]; break;
            case 0xff33: return WaveRAM[3]; break;
            case 0xff34: return WaveRAM[4]; break;
            case 0xff35: return WaveRAM[5]; break;
            case 0xff36: return WaveRAM[6]; break;
            case 0xff37: return WaveRAM[7]; break;
            case 0xff38: return WaveRAM[8]; break;
            case 0xff39: return WaveRAM[9]; break;
            case 0xff3a: return WaveRAM[10]; break;
            case 0xff3b: return WaveRAM[11]; break;
            case 0xff3c: return WaveRAM[12]; break;
            case 0xff3d: return WaveRAM[13]; break;
            case 0xff3e: return WaveRAM[14]; break;
            case 0xff3f: return WaveRAM[15]; break;
        }
        return 0x00;
    }

    void writeRegister(uint16_t address, uint8_t value)
    {
        if (!apu_enabled && (address!=0xff26)) return;

        switch(address)
        {
            case 0xff10: NR10 = value; break;
            case 0xff11: NR11 = value; CH1.length_timer=NR11&0x3f; CH1.duty_cycle = NR11>>6; break;
            case 0xff12: NR12 = value; break;
            case 0xff13: NR13 = value; break;
            case 0xff14: NR14 = value; CH1.length_enable=(value&0x40); if (value&0x80) triggerCH1(); break;

            case 0xff16: NR21 = value; CH2.length_timer=NR21&0x3f; CH2.duty_cycle = NR21>>6; break;
            case 0xff17: NR22 = value; break;
            case 0xff18: NR23 = value; break;
            case 0xff19: NR24 = value; CH2.length_enable=(value&0x40); if (value&0x80) triggerCH2(); break;

            case 0xff1a: NR30 = value; break;
            case 0xff1b: NR31 = value; CH3.length_timer=NR31; break;
            case 0xff1c: NR32 = value; break;
            case 0xff1d: NR33 = value; break;
            case 0xff1e: NR34 = value; CH3.length_enable=(value&0x40); if (value&0x80) triggerCH3(); break;

            case 0xff20: NR41 = value; CH1.length_timer=NR11&0x3f; CH1.duty_cycle = NR11>>6; break;
            case 0xff21: NR42 = value; break;
            case 0xff22: NR43 = value; break;
            case 0xff23: NR44 = value; CH4.length_enable=(value&0x40); if (value&0x80) triggerCH4(); break;

            case 0xff24: NR50 = value; break;
            case 0xff25: NR51 = value; break;
            case 0xff26: if (value&0x80) reset(); NR52 = (value&0x80) | (NR52 & 0x0f); break;

            case 0xff30: WaveRAM[0] = value; break;
            case 0xff31: WaveRAM[1] = value; break;
            case 0xff32: WaveRAM[2] = value; break;
            case 0xff33: WaveRAM[3] = value; break;
            case 0xff34: WaveRAM[4] = value; break;
            case 0xff35: WaveRAM[5] = value; break;
            case 0xff36: WaveRAM[6] = value; break;
            case 0xff37: WaveRAM[7] = value; break;
            case 0xff38: WaveRAM[8] = value; break;
            case 0xff39: WaveRAM[9] = value; break;
            case 0xff3a: WaveRAM[10] = value; break;
            case 0xff3b: WaveRAM[11] = value; break;
            case 0xff3c: WaveRAM[12] = value; break;
            case 0xff3d: WaveRAM[13] = value; break;
            case 0xff3e: WaveRAM[14] = value; break;
            case 0xff3f: WaveRAM[15] = value; break;
        }
    }

    uint8_t DIVAPU_envelope_sweep = 0;
    uint8_t DIVAPU_length = 0;
    uint8_t DIVAPU_CH1_freq_sweep = 0;

    void incDIVAPU()
    {
        DIVAPU++;
        DIVAPU_length++;
        if (DIVAPU_length==2) {
            DIVAPU_length=0;
            if (CH1.enabled && CH1.length_enable) {
                if (CH1.length_timer==63) {
                    CH1.enabled = false;
                    CH1.length_timer=0;
                } else {
                    CH1.length_timer++;
                }
            }
            if (CH2.enabled && CH2.length_enable) {
                if (CH2.length_timer==63) {
                    CH2.enabled = false;
                    CH2.length_timer=0;
                } else {
                    CH2.length_timer++;
                }
            }
            if (CH3.enabled && CH3.length_enable) {
                if (CH3.length_timer==255) {
                    CH3.enabled = false;
                    CH3.length_timer = 0;
                } else {
                    CH3.length_timer++;
                }
            }
            if (CH4.enabled && CH4.length_enable) {
                if (CH4.length_timer==63) {
                    CH4.enabled = false;
                    CH4.length_timer=0;
                } else {
                    CH4.length_timer++;
                }
            }
        }
        DIVAPU_CH1_freq_sweep++;
        if (DIVAPU_CH1_freq_sweep==4) {
            DIVAPU_CH1_freq_sweep=0;
            // Do the freq sweep thing
        }
        DIVAPU_envelope_sweep++;
        if (DIVAPU_envelope_sweep==8) {
            DIVAPU_envelope_sweep=0;
            if ( CH1.enabled && (NR12&0x7) ) {   // If sweep pace != 0, envelope sweep is enabled
                CH1.envelope_sweep_timer++;
                if ( CH1.envelope_sweep_timer == (NR12&0x07) ) {    // if timer == envelope sweep, increase or decrease volume
                    CH1.envelope_sweep_timer=0;
                    if (NR12&0x8) {    // bit set increases, reset decreases
                        if (CH1.volume<0x0f) CH1.volume++;
                    } else {
                        if (CH1.volume>0) CH1.volume--;
                    }
                }
            }
            if ( CH2.enabled && (NR22&0x7) ) {   // If sweep pace != 0, envelope sweep is enabled
                CH2.envelope_sweep_timer++;
                if ( CH2.envelope_sweep_timer == (NR22&0x07) ) {    // if timer == envelope sweep, increase or decrease volume
                    CH2.envelope_sweep_timer=0;
                    if (NR22&0x8) {    // bit set increases, reset decreases
                        if (CH2.volume<0x0f) CH2.volume++;
                    } else {
                        if (CH2.volume>0) CH2.volume--;
                    }
                }
            }
            if ( CH4.enabled && (NR42&0x7) ) {   // If sweep pace != 0, envelope sweep is enabled
                CH4.envelope_sweep_timer++;
                if ( CH4.envelope_sweep_timer == (NR42&0x07) ) {    // if timer == envelope sweep, increase or decrease volume
                    CH4.envelope_sweep_timer=0;
                    if (NR42&0x8) {    // bit set increases, reset decreases
                        if (CH4.volume<0x0f) CH4.volume++;
                    } else {
                        if (CH4.volume>0) CH4.volume--;
                    }
                }
            }
        }
    }

    uint32_t dots = 0;
    uint32_t dotsCH3 = 0;
    uint32_t dotsCH4 = 0;
    void update(uint32_t dt)
    {
        dots += dt;
        while (dots>=4) {
            dots -= 4;
            CH1.period_divider++;
            if (CH1.period_divider==2048) {
                CH1.period_divider = NR13 | ((NR14 &0x7)<<8);
                CH1.duty_step++;
                if (CH1.duty_step==8) CH1.duty_step=0;
            }
            CH2.period_divider++;
            if (CH2.period_divider==2048) {
                CH2.period_divider = NR23 | ((NR24 &0x7)<<8);
                CH2.duty_step++;
                if (CH2.duty_step==8) CH2.duty_step=0;
            }
        }
        dotsCH3 += dt;
        while (dotsCH3>=2) {
            dotsCH3 -= 2;
            CH3.period_divider++;
            if (CH3.period_divider==2048) {
                CH3.period_divider = NR33 | ((NR34 &0x7)<<8);
                CH3.duty_step++;
                if (CH3.duty_step==32) CH3.duty_step=0;
            }
        }
        dotsCH4 += dt;
        while (dotsCH4>=64) {
            dotsCH4 -= 64;
            CH4.period_divider--;
            if (CH4.period_divider==0) {
                uint8_t clock_shift = ((NR43&0xf0)>>3); if (clock_shift==0) clock_shift = 1;
                CH4.period_divider = clock_shift << (NR43&0x07);
                CH4.duty_step=getLFSR();
            }
        }

        t_sound += dt;
        if (t_sound>=cycles_per_sample) {
            t_sound-=cycles_per_sample;

            uint16_t sampleCH1 = 0;
            if (apu_enabled && DAC1_enabled) sampleCH1 = (duty_cycles[CH1.duty_cycle][CH1.duty_step]*CH1.volume)<<2;

            uint16_t sampleCH2 = 0;
            if (apu_enabled && DAC2_enabled) sampleCH2 = (duty_cycles[CH2.duty_cycle][CH2.duty_step]*CH2.volume)<<2;

            uint16_t sampleCH3 = 0;
            if (apu_enabled && DAC3_enabled) {
                uint8_t step = CH3.duty_step>>1;
                uint8_t actual_sample = (CH3.duty_step&1 ? WaveRAM[step]&0x0f : WaveRAM[step]>>4)<<2;
                sampleCH3 = CH3.volume==0 ? 0 : CH3.volume==1 ? actual_sample : CH3.volume==2 ? actual_sample>>1 : actual_sample>>2;
            }

            uint16_t sampleCH4 = 0;
            if (apu_enabled && DAC4_enabled) sampleCH4 = (CH4.duty_step*CH4.volume)<<2;

            uint8_t sample = (sampleCH1+sampleCH2+sampleCH3+sampleCH4)&0xff;

            sound_buffer[(sound_pos++)&(AUDIO_BUFFER_SIZE-1)] = sample;
            //audio_viewer::addsample(sample);
        }
        if (sound_pos>=1000) {
            SDL_QueueAudio(sdlAudioDevice, sound_buffer, sound_pos);
            sound_pos = 0;
            while (SDL_GetQueuedAudioSize(sdlAudioDevice) > 4096 ) {}
        }
     }
}