using System;

namespace VirtualNes.Core
{
    public class APU_VRC6 : APU_INTERFACE
    {
        public const int RECTANGLE_VOL_SHIFT = 8;
        public const int SAWTOOTH_VOL_SHIFT = 6;

        private RECTANGLE ch0 = new RECTANGLE();
        private RECTANGLE ch1 = new RECTANGLE();
        private SAWTOOTH ch2 = new SAWTOOTH();

        private int cycle_rate;
        private float cpu_clock;

        public APU_VRC6()
        {
            Reset(APU_CLOCK, 22050);
        }

        public override void Reset(float fClock, int nRate)
        {
            ch0.ZeroMemory();
            ch1.ZeroMemory();
            ch2.ZeroMemory();

            Setup(fClock, nRate);
        }

        public override void Setup(float fClock, int nRate)
        {
            cpu_clock = fClock;
            cycle_rate = (int)(fClock * 65536.0f / nRate);
        }

        public override void Write(ushort addr, byte data)
        {
            switch (addr)
            {
                // VRC6 CH0 rectangle
                case 0x9000:
                    ch0.reg[0] = data;
                    ch0.gate = (byte)(data & 0x80);
                    ch0.volume = (byte)(data & 0x0F);
                    ch0.duty_pos = (byte)((data >> 4) & 0x07);
                    break;
                case 0x9001:
                    ch0.reg[1] = data;
                    ch0.freq = INT2FIX((((ch0.reg[2] & 0x0F) << 8) | data) + 1);
                    break;
                case 0x9002:
                    ch0.reg[2] = data;
                    ch0.enable = (byte)(data & 0x80);
                    ch0.freq = INT2FIX((((data & 0x0F) << 8) | ch0.reg[1]) + 1);
                    break;
                // VRC6 CH1 rectangle
                case 0xA000:
                    ch1.reg[0] = data;
                    ch1.gate = (byte)(data & 0x80);
                    ch1.volume = (byte)(data & 0x0F);
                    ch1.duty_pos = (byte)((data >> 4) & 0x07);
                    break;
                case 0xA001:
                    ch1.reg[1] = data;
                    ch1.freq = INT2FIX((((ch1.reg[2] & 0x0F) << 8) | data) + 1);
                    break;
                case 0xA002:
                    ch1.reg[2] = data;
                    ch1.enable = (byte)(data & 0x80);
                    ch1.freq = INT2FIX((((data & 0x0F) << 8) | ch1.reg[1]) + 1);
                    break;
                // VRC6 CH2 sawtooth
                case 0xB000:
                    ch2.reg[1] = data;
                    ch2.phaseaccum = (byte)(data & 0x3F);
                    break;
                case 0xB001:
                    ch2.reg[1] = data;
                    ch2.freq = INT2FIX((((ch2.reg[2] & 0x0F) << 8) | data) + 1);
                    break;
                case 0xB002:
                    ch2.reg[2] = data;
                    ch2.enable = (byte)(data & 0x80);
                    ch2.freq = INT2FIX((((data & 0x0F) << 8) | ch2.reg[1]) + 1);
                    //			ch2.adder = 0;	// 僋儕傾偡傞偲僲僀僘偺尨場偵側傞
                    //			ch2.accum = 0;	// 僋儕傾偡傞偲僲僀僘偺尨場偵側傞
                    break;
            }
        }

        public override int Process(int channel)
        {
            switch (channel)
            {
                case 0:
                    return RectangleRender(ch0);
                case 1:
                    return RectangleRender(ch1);
                case 2:
                    return SawtoothRender(ch2);
            }

            return 0;
        }

        public override int GetFreq(int channel)
        {
            if (channel == 0 || channel == 1)
            {
                RECTANGLE ch = null;
                if (channel == 0) ch = ch0;
                else ch = ch1;
                if (ch.enable == 0 || ch.gate != 0 || ch.volume == 0)
                    return 0;
                if (ch.freq < INT2FIX(8))
                    return 0;
                return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 16.0f));
            }
            if (channel == 2)
            {
                SAWTOOTH ch = ch2;
                if (ch.enable == 0 || ch.phaseaccum == 0)
                    return 0;
                if (ch.freq < INT2FIX(8))
                    return 0;
                return (int)(256.0f * cpu_clock / (FIX2INT(ch.freq) * 14.0f));
            }

            return 0;
        }

        private int RectangleRender(RECTANGLE ch)
        {
            // Enable?
            if (ch.enable == 0)
            {
                ch.output_vol = 0;
                ch.adder = 0;
                return ch.output_vol;
            }

            // Digitized output
            if (ch.gate != 0)
            {
                ch.output_vol = ch.volume << RECTANGLE_VOL_SHIFT;
                return ch.output_vol;
            }

            // 堦掕埲忋偺廃攇悢偼張棟偟側偄(柍懯)
            if (ch.freq < INT2FIX(8))
            {
                ch.output_vol = 0;
                return ch.output_vol;
            }

            ch.phaseacc -= cycle_rate;
            if (ch.phaseacc >= 0)
                return ch.output_vol;

            int output = ch.volume << RECTANGLE_VOL_SHIFT;

            if (ch.freq > cycle_rate)
            {
                // add 1 step
                ch.phaseacc += ch.freq;
                ch.adder = (byte)((ch.adder + 1) & 0x0F);
                if (ch.adder <= ch.duty_pos)
                    ch.output_vol = output;
                else
                    ch.output_vol = -output;
            }
            else
            {
                // average calculate
                int num_times, total;
                num_times = total = 0;
                while (ch.phaseacc < 0)
                {
                    ch.phaseacc += ch.freq;
                    ch.adder = (byte)((ch.adder + 1) & 0x0F);
                    if (ch.adder <= ch.duty_pos)
                        total += output;
                    else
                        total += -output;
                    num_times++;
                }
                ch.output_vol = total / num_times;
            }

            return ch.output_vol;
        }

        private int SawtoothRender(SAWTOOTH ch)
        {
            // Digitized output
            if (ch.enable == 0)
            {
                ch.output_vol = 0;
                return ch.output_vol;
            }

            // 堦掕埲忋偺廃攇悢偼張棟偟側偄(柍懯)
            if (ch.freq < INT2FIX(9))
            {
                return ch.output_vol;
            }

            ch.phaseacc -= cycle_rate / 2;
            if (ch.phaseacc >= 0)
                return ch.output_vol;

            if (ch.freq > cycle_rate / 2)
            {
                // add 1 step
                ch.phaseacc += ch.freq;
                if (++ch.adder >= 7)
                {
                    ch.adder = 0;
                    ch.accum = 0;
                }
                ch.accum += ch.phaseaccum;
                ch.output_vol = ch.accum << SAWTOOTH_VOL_SHIFT;
            }
            else
            {
                // average calculate
                int num_times, total;
                num_times = total = 0;
                while (ch.phaseacc < 0)
                {
                    ch.phaseacc += ch.freq;
                    if (++ch.adder >= 7)
                    {
                        ch.adder = 0;
                        ch.accum = 0;
                    }
                    ch.accum += ch.phaseaccum;
                    total += ch.accum << SAWTOOTH_VOL_SHIFT;
                    num_times++;
                }
                ch.output_vol = (total / num_times);
            }

            return ch.output_vol;
        }

        public override uint GetSize()
        {
            return ch0.GetSize() + ch1.GetSize() + ch2.GetSize();
        }
        public override void SaveState(StateBuffer p)
        {
            ch0.SaveState(p);
            ch1.SaveState(p);
            ch2.SaveState(p);
        }

        public class RECTANGLE : IStateBufferObject
        {
            public byte[] reg = new byte[3];

            public byte enable;
            public byte gate;
            public byte volume;

            public int phaseacc;
            public int freq;
            public int output_vol;

            public byte adder;
            public byte duty_pos;

            public void ZeroMemory()
            {
                Array.Clear(reg, 0, reg.Length);
                enable = 0;
                gate = 0;
                volume = 0;

                phaseacc = 0;
                freq = 0;
                output_vol = 0;

                adder = 0;
                duty_pos = 0;
            }

            public uint GetSize()
            {
                return 20;
            }

            public void SaveState(StateBuffer buffer)
            {
                buffer.Write(reg);
                buffer.Write(enable);
                buffer.Write(gate);
                buffer.Write(volume);
                buffer.Write(phaseacc);
                buffer.Write(freq);
                buffer.Write(output_vol);
                buffer.Write(adder);
                buffer.Write(duty_pos);
            }

            public void LoadState(StateReader buffer)
            {
                reg = buffer.Read_bytes(3);
                enable = buffer.Read_byte();
                gate = buffer.Read_byte();
                volume = buffer.Read_byte();
                phaseacc = buffer.Read_int();
                freq = buffer.Read_int();
                output_vol = buffer.Read_int();
                adder = buffer.Read_byte();
                duty_pos = buffer.Read_byte();
            }
        }

        public class SAWTOOTH : IStateBufferObject
        {
            public byte[] reg = new byte[3];

            public byte enable;
            public byte volume;

            public int phaseacc;
            public int freq;
            public int output_vol;

            public byte adder;
            public byte accum;
            public byte phaseaccum;

            public void ZeroMemory()
            {
                Array.Clear(reg, 0, reg.Length);
                enable = 0;
                volume = 0;

                phaseacc = 0;
                freq = 0;
                output_vol = 0;

                adder = 0;
                accum = 0;
                phaseaccum = 0;
            }

            public uint GetSize()
            {
                return 20;
            }

            public void SaveState(StateBuffer buffer)
            {
                buffer.Write(reg);
                buffer.Write(enable);
                buffer.Write(volume);
                buffer.Write(phaseacc);
                buffer.Write(freq);
                buffer.Write(output_vol);
                buffer.Write(adder);
                buffer.Write(accum);
                buffer.Write(phaseaccum);
            }

            public void LoadState(StateReader buffer)
            {
                reg = buffer.Read_bytes(3);
                enable = buffer.Read_byte();
                volume = buffer.Read_byte();
                phaseacc = buffer.Read_int();
                freq = buffer.Read_int();
                output_vol = buffer.Read_int();
                adder = buffer.Read_byte();
                accum = buffer.Read_byte();
                phaseaccum = buffer.Read_byte();
            }
        }
    }
}