using System.Collections.Generic;

namespace VirtualNes.Core
{
    public class PAD
    {
        private NES nes;
        private int excontroller_select;
        private EXPAD expad;
        private bool bStrobe;
        private bool bSwapButton;
        private bool bSwapPlayer;
        private bool bZapperMode;
        private VSType nVSSwapType;
        private byte[] padbit = new byte[4];
        private byte micbit;
        private byte[] padbitsync = new byte[4];
        private byte micbitsync;
        private bool bBarcodeWorld;
        private int[][] padcnt = new int[4][]
        {
           new int[2],new int[2],new int[2],new int[2],
        };

        public uint pad1bit, pad2bit, pad3bit, pad4bit;

        private static int[] ren10fps = new int[6] { 1, 1, 1, 0, 0, 0 };
        private static int[] ren15fps = new int[4] { 1, 1, 0, 0 };
        private static int[] ren20fps = new int[3] { 1, 1, 0 };
        private static int[] ren30fps = new int[2] { 1, 0 };
        private static int[] renmask = new int[4] { 6, 4, 3, 2 };
        public static Dictionary<int, int[]> rentbl = new Dictionary<int, int[]>()
        {
            {0,ren10fps },
            {1,ren15fps },
            {2,ren20fps },
            {3,ren30fps },
        };

        public PAD(NES parent)
        {
            nes = parent;
            excontroller_select = 0;
            expad = null;
            bStrobe = false;
            bSwapButton = false;
            bSwapPlayer = false;
            bZapperMode = false;
            nVSSwapType = VSType.VS_TYPE0;

            padbit[0] = padbit[1] = padbit[2] = padbit[3] = 0;
            micbit = 0;

            padbitsync[0] = padbitsync[1] = padbitsync[2] = padbitsync[3] = 0;
            micbitsync = 0;
        }

        internal byte Read(ushort addr)
        {
            byte data = 0x00;

            if (addr == 0x4016)
            {
                data = (byte)(pad1bit & 1);
                pad1bit >>= 1;
                data |= (byte)(((pad3bit & 1)) << 1);
                pad3bit >>= 1;
                // Mic
                if (!nes.rom.IsVSUNISYSTEM())
                {
                    data |= micbitsync;
                }
                if (expad != null)
                {
                    data |= expad.Read4016();
                }
            }
            if (addr == 0x4017)
            {
                data = (byte)(pad2bit & 1);
                pad2bit >>= 1;
                data |= (byte)((pad4bit & 1) << 1);
                pad4bit >>= 1;

                if (expad != null)
                {
                    data |= expad.Read4017();
                }

                if (bBarcodeWorld)
                {
                    data |= nes.Barcode2();
                }
            }

            return data;
        }
        public void Dispose() { }

        internal void Write(ushort addr, byte data)
        {
            if (addr == 0x4016)
            {
                if ((data & 0x01) != 0)
                {
                    bStrobe = true;
                }
                else if (bStrobe)
                {
                    bStrobe = false;

                    Strobe();
                    if (expad != null)
                    {
                        expad.Strobe();
                    }
                }

                if (expad != null)
                {
                    expad.Write4016(data);
                }
            }
            if (addr == 0x4017)
            {
                if (expad != null)
                {
                    expad.Write4017(data);
                }
            }
        }

        private void Strobe()
        {
            // For VS-Unisystem
            if (nes.rom.IsVSUNISYSTEM())
            {
                uint pad1 = (uint)(padbitsync[0] & 0xF3);
                uint pad2 = (uint)(padbitsync[1] & 0xF3);
                uint st1 = (uint)(padbitsync[0] & 0x08) >> 3;
                uint st2 = (uint)(padbitsync[1] & 0x08) >> 3;

                switch (nVSSwapType)
                {
                    case VSType.VS_TYPE0:
                        pad1bit = pad1 | (st1 << 2);
                        pad2bit = pad2 | (st2 << 2);
                        break;
                    case VSType.VS_TYPE1:
                        pad1bit = pad2 | (st1 << 2);
                        pad2bit = pad1 | (st2 << 2);
                        break;
                    case VSType.VS_TYPE2:
                        pad1bit = pad1 | (st1 << 2) | (st2 << 3);
                        pad2bit = pad2;
                        break;
                    case VSType.VS_TYPE3:
                        pad1bit = pad2 | (st1 << 2) | (st2 << 3);
                        pad2bit = pad1;
                        break;
                    case VSType.VS_TYPE4:
                        pad1bit = pad1 | (st1 << 2) | 0x08; // 0x08=Start Protect
                        pad2bit = pad2 | (st2 << 2) | 0x08; // 0x08=Start Protect
                        break;
                    case VSType.VS_TYPE5:
                        pad1bit = pad2 | (st1 << 2) | 0x08; // 0x08=Start Protect
                        pad2bit = pad1 | (st2 << 2) | 0x08; // 0x08=Start Protect
                        break;
                    case VSType.VS_TYPE6:
                        pad1bit = pad1 | (st1 << 2) | (((uint)padbitsync[0] & 0x04) << 1);
                        pad2bit = pad2 | (st2 << 2) | (((uint)padbitsync[1] & 0x04) << 1);
                        break;
                    case VSType.VS_TYPEZ:
                        pad1bit = 0;
                        pad2bit = 0;
                        break;
                }

                // Coin 2偲旐傞堊偵徚偡
                micbit = 0;
            }
            else
            {
                if (Supporter.Config.emulator.bFourPlayer)
                {
                    // NES type
                    pad1bit = padbitsync[0] | ((uint)padbitsync[2] << 8) | 0x00080000;
                    pad2bit = padbitsync[1] | ((uint)padbitsync[3] << 8) | 0x00040000;
                }
                else
                {
                    // Famicom type
                    pad1bit = padbitsync[0];
                    pad2bit = padbitsync[1];
                }
            }
            pad3bit = padbitsync[2];
            pad4bit = padbitsync[3];
        }

        internal void Reset()
        {
            pad1bit = pad2bit = 0;
            bStrobe = false;

            bBarcodeWorld = false;

            for (int x = 0; x < 4; x++)
            {
                for (int y = 0; y < 2; y++)
                {
                    padcnt[x][y] = 0;
                }
            }

            // Select Extension Devices
            uint crc = nes.rom.GetPROM_CRC();

            if (crc == 0xfbfc6a6c       // Adventures of Bayou Billy, The(E)
             || crc == 0xcb275051       // Adventures of Bayou Billy, The(U)
             || crc == 0xfb69c131       // Baby Boomer(Unl)(U)
             || crc == 0xf2641ad0       // Barker Bill's Trick Shooting(U)
             || crc == 0xbc1dce96       // Chiller (Unl)(U)
             || crc == 0x90ca616d       // Duck Hunt(JUE)
             || crc == 0x59e3343f       // Freedom Force(U)
             || crc == 0x242a270c       // Gotcha!(U)
             || crc == 0x7b5bd2de       // Gumshoe(UE)
             || crc == 0x255b129c       // Gun Sight(J)
             || crc == 0x8963ae6e       // Hogan's Alley(JU)
             || crc == 0x51d2112f       // Laser Invasion(U)
             || crc == 0x0a866c94       // Lone Ranger, The(U)
                                        //	 || crc == 0xe4c04eea		// Mad City(J)
             || crc == 0x9eef47aa       // Mechanized Attack(U)
             || crc == 0xc2db7551       // Shooting Range(U)
             || crc == 0x163e86c0       // To The Earth(U)
             || crc == 0x42d893e4       // Operation Wolf(J)
             || crc == 0x1388aeb9       // Operation Wolf(U)
             || crc == 0x0d3cf705       // Wild Gunman(J)
             || crc == 0x389960db)
            {   // Wild Gunman(JUE)
                SetExController(EXCONTROLLER.EXCONTROLLER_ZAPPER);
            }
            if (crc == 0x35893b67       // Arkanoid(J)
             || crc == 0x6267fbd1)
            {   // Arkanoid 2(J)
                SetExController(EXCONTROLLER.EXCONTROLLER_PADDLE);
            }
            if (crc == 0xff6621ce       // Hyper Olympic(J)
             || crc == 0xdb9418e8       // Hyper Olympic(Tonosama Ban)(J)
             || crc == 0xac98cd70)
            {   // Hyper Sports(J)
                SetExController(EXCONTROLLER.EXCONTROLLER_HYPERSHOT);
            }
            if (crc == 0xf9def527       // Family BASIC(Ver2.0)
             || crc == 0xde34526e       // Family BASIC(Ver2.1a)
             || crc == 0xf050b611       // Family BASIC(Ver3)
             || crc == 0x3aaeed3f       // Family BASIC(Ver3)(Alt)
             || crc == 0x868FCD89       // Family BASIC(Ver1.0)
             || crc == 0x2D6B7E5A       // PLAYBOX BASIC(J) (Prototype_v0.0)
             || crc == 0xDA03D908)
            {   // PLAYBOX BASIC (J)
                SetExController(EXCONTROLLER.EXCONTROLLER_KEYBOARD);
            }
            if (crc == 0x589b6b0d       // Supor Computer V3.0
             || crc == 0x8b265862       // Supor English
             || crc == 0x41401c6d       // Supor Computer V4.0
             || crc == 0x82F1Fb96       // Supor Computer(Russia) V1.0
             || crc == 0xd5d6eac4)
            {   // EDU(C) Computer
                SetExController(EXCONTROLLER.EXCONTROLLER_SUPOR_KEYBOARD);
                nes.SetVideoMode(true);
            }
            if (crc == 0xc68363f6       // Crazy Climber(J)
             || crc == 0x2989ead6       // Smash TV(U) [!]
             || crc == 0x0b8f8128)
            {   // Smash TV(E) [!]
                SetExController(EXCONTROLLER.EXCONTROLLER_CRAZYCLIMBER);
            }
            if (crc == 0x20d22251)
            {   // Top rider(J)
                SetExController(EXCONTROLLER.EXCONTROLLER_TOPRIDER);
            }
            if (crc == 0x0cd00488)
            {   // Space Shadow(J)
                SetExController(EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN);
            }

            if (crc == 0x8c8fa83b       // Family Trainer - Athletic World (J)
             || crc == 0x7e704a14       // Family Trainer - Jogging Race (J)
             || crc == 0x2330a5d3)
            {   // Family Trainer - Rairai Kyonshiizu (J)
                SetExController(EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_A);
            }
            if (crc == 0xf8da2506       // Family Trainer - Aerobics Studio (J)
             || crc == 0xca26a0f1       // Family Trainer - Dai Undoukai (J)
             || crc == 0x28068b8c       // Family Trainer - Fuuun Takeshi Jou 2 (J)
             || crc == 0x10bb8f9a       // Family Trainer - Manhattan Police (J)
             || crc == 0xad3df455       // Family Trainer - Meiro Dai Sakusen (J)
             || crc == 0x8a5b72c0       // Family Trainer - Running Stadium (J)
             || crc == 0x59794f2d)
            {   // Family Trainer - Totsugeki Fuuun Takeshi Jou (J)
                SetExController(EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_B);
            }
            if (crc == 0x9fae4d46       // Ide Yousuke Meijin no Jissen Mahjong (J)
             || crc == 0x7b44fb2a)
            {   // Ide Yousuke Meijin no Jissen Mahjong 2 (J)
                SetExController(EXCONTROLLER.EXCONTROLLER_MAHJANG);
            }
            if (crc == 0x786148b6)
            {   // Exciting Boxing (J)
                SetExController(EXCONTROLLER.EXCONTROLLER_EXCITINGBOXING);
            }
            if (crc == 0xc3c0811d       // Oeka Kids - Anpanman no Hiragana Daisuki (J)
             || crc == 0x9d048ea4)
            {   // Oeka Kids - Anpanman to Oekaki Shiyou!! (J)
                SetExController(EXCONTROLLER.EXCONTROLLER_OEKAKIDS_TABLET);
            }

            if (crc == 0x67898319)
            {   // Barcode World (J)
                bBarcodeWorld = true;
            }

            // VS-Unisystem
            if (nes.rom.IsVSUNISYSTEM())
            {
                if (crc == 0xff5135a3       // VS Hogan's Alley
                 || crc == 0xed588f00       // VS Duck Hunt
                 || crc == 0x17ae56be)
                {   // VS Freedom Force
                    SetExController(EXCONTROLLER.EXCONTROLLER_VSZAPPER);
                }
                else
                {
                    SetExController(EXCONTROLLER.EXCONTROLLER_VSUNISYSTEM);
                }
            }

            if (crc == 0x21b099f3)
            {   // Gyromite (JUE)
                SetExController(EXCONTROLLER.EXCONTROLLER_GYROMITE);
            }
        }

        internal void SetExController(EXCONTROLLER type)
        {
            excontroller_select = (int)type;

            expad?.Dispose();
            expad = null;

            bZapperMode = false;

            // ExPad Instance create
            switch (type)
            {
                case EXCONTROLLER.EXCONTROLLER_ZAPPER:
                    expad = new EXPAD_Zapper(nes);
                    bZapperMode = true;
                    break;
                case EXCONTROLLER.EXCONTROLLER_PADDLE:
                    expad = new EXPAD_Paddle(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_HYPERSHOT:
                    expad = new EXPAD_HyperShot(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_KEYBOARD:
                    expad = new EXPAD_Keyboard(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_SUPOR_KEYBOARD:
                    expad = new EXPAD_Supor_Keyboard(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_CRAZYCLIMBER:
                    expad = new EXPAD_CrazyClimber(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_TOPRIDER:
                    expad = new EXPAD_Toprider(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN:
                    expad = new EXPAD_SpaceShadowGun(nes);
                    bZapperMode = true;
                    break;
                case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_A:
                case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_B:
                    expad = new EXPAD_FamlyTrainer(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_EXCITINGBOXING:
                    expad = new EXPAD_ExcitingBoxing(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_MAHJANG:
                    expad = new EXPAD_Mahjang(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_OEKAKIDS_TABLET:
                    expad = new EXPAD_OekakidsTablet(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_TURBOFILE:
                    expad = new EXPAD_TurboFile(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_VSUNISYSTEM:
                    expad = new EXPAD_VSUnisystem(nes);
                    break;
                case EXCONTROLLER.EXCONTROLLER_VSZAPPER:
                    expad = new EXPAD_VSZapper(nes);
                    bZapperMode = true;
                    break;

                case EXCONTROLLER.EXCONTROLLER_GYROMITE:
                    expad = new EXPAD_Gyromite(nes);
                    break;
                default:
                    break;
            }

            if (expad != null)
            {
                expad.Reset();
            }
        }

        public void Sync(ControllerState state)
        {
            padbit[0] = SyncSub(0, state);
            padbit[1] = SyncSub(1, state);
            padbit[2] = SyncSub(2, state);
            padbit[3] = SyncSub(3, state);

            // Mic
            micbit = 0;
            if (state.HasButton(1, EnumButtonType.MIC)) micbit |= 4;

            // For Excontroller
            if (expad != null)
            {
                expad.Sync();
            }
        }

        private byte SyncSub(int no, ControllerState state)
        {
            ushort bit = 0;

            // Up
            if (state.HasButton(no, EnumButtonType.UP))
                bit |= 1 << 4;
            // Down
            if (state.HasButton(no, EnumButtonType.DOWN))
                bit |= 1 << 5;
            // Left
            if (state.HasButton(no, EnumButtonType.LEFT))
                bit |= 1 << 6;
            // Right
            if (state.HasButton(no, EnumButtonType.RIGHT))
                bit |= 1 << 7;

            // 同時入力を禁止する
            //	if( (bit&((1<<4)|(1<<5))) == ((1<<4)|(1<<5)) )
            //		bit &= ~((1<<4)|(1<<5));
            if ((bit & ((1 << 6) | (1 << 7))) == ((1 << 6) | (1 << 7)))
                bit = (byte)(bit & ~((1 << 6) | (1 << 7)));

            // A
            if (state.HasButton(no, EnumButtonType.A)) bit |= 1 << 0;
            // B
            if (state.HasButton(no, EnumButtonType.B)) bit |= 1 << 1;

            // Select
            if (state.HasButton(no, EnumButtonType.SELECT)) bit |= 1 << 2;
            // Start
            if (state.HasButton(no, EnumButtonType.START)) bit |= 1 << 3;


            return (byte)(bit & 0xFF);
        }

        internal bool IsZapperMode()
        {
            return bZapperMode;
        }

        internal void VSync()
        {
            padbitsync[0] = padbit[0];
            padbitsync[1] = padbit[1];
            padbitsync[2] = padbit[2];
            padbitsync[3] = padbit[3];
            micbitsync = micbit;
        }

        internal uint GetSyncData()
        {
            uint ret;
            ret = (uint)(padbit[0] | (padbit[1] << 8) | (padbit[2] << 16) | (padbit[3] << 24));
            ret |= (uint)(micbit << 8);
            return ret;
        }

        internal void SetSyncData(uint data)
        {
            micbit = (byte)((data & 0x00000400) >> 8);
            padbit[0] = (byte)data;
            padbit[1] = (byte)(data >> 8);
            padbit[2] = (byte)(data >> 16);
            padbit[3] = (byte)(data >> 24);
        }

        internal int GetExController()
        {
            return excontroller_select;
        }

        internal bool GetStrobe()
        {
            return bStrobe;
        }

        internal void SetStrobe(bool v)
        {
            bStrobe = v;
        }

        internal uint GetSyncExData()
        {
            uint data = 0;

            switch ((EXCONTROLLER)excontroller_select)
            {
                case EXCONTROLLER.EXCONTROLLER_ZAPPER:
                case EXCONTROLLER.EXCONTROLLER_PADDLE:
                case EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN:
                case EXCONTROLLER.EXCONTROLLER_OEKAKIDS_TABLET:
                case EXCONTROLLER.EXCONTROLLER_VSZAPPER:
                    {
                        int x, y;
                        x = expad.GetSyncData(0);
                        y = expad.GetSyncData(1);
                        if (x == -1 || y == -1)
                        {
                            data = 0x80000000;
                        }
                        else
                        {
                            data = (uint)((x & 0xFF) | ((y & 0xFF) << 8));
                        }
                    }
                    if (excontroller_select != (int)EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN)
                    {
                        if (expad.GetSyncData(2) != 0)
                            data |= 0x0010000;
                    }
                    else
                    {
                        data |= (uint)(expad.GetSyncData(2) << 16);
                    }
                    break;
                case EXCONTROLLER.EXCONTROLLER_CRAZYCLIMBER:
                    data = (uint)expad.GetSyncData(0);
                    break;
                case EXCONTROLLER.EXCONTROLLER_TOPRIDER:
                    data = (uint)expad.GetSyncData(0);
                    break;
                case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_A:
                case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_B:
                    data = (uint)expad.GetSyncData(0);
                    break;
                case EXCONTROLLER.EXCONTROLLER_EXCITINGBOXING:
                    data = (uint)expad.GetSyncData(0);
                    break;
                case EXCONTROLLER.EXCONTROLLER_MAHJANG:
                    data = (uint)expad.GetSyncData(0);
                    break;

                default:
                    break;
            }
            return data;
        }
        internal void SetSyncExData(uint data)
        {
            switch ((EXCONTROLLER)excontroller_select)
            {
                case EXCONTROLLER.EXCONTROLLER_ZAPPER:
                case EXCONTROLLER.EXCONTROLLER_PADDLE:
                case EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN:
                case EXCONTROLLER.EXCONTROLLER_OEKAKIDS_TABLET:
                case EXCONTROLLER.EXCONTROLLER_VSZAPPER:
                    {
                        int x, y;
                        if ((data & 0x80000000) != 0)
                        {
                            x = -1;
                            y = -1;
                        }
                        else
                        {
                            x = (int)(data & 0xFF);
                            y = (int)((data & 0xFF00) >> 8);
                        }
                        expad.SetSyncData(0, x);
                        expad.SetSyncData(1, y);
                        nes.SetZapperPos(x, y);
                    }
                    if (excontroller_select != (int)EXCONTROLLER.EXCONTROLLER_SPACESHADOWGUN)
                    {
                        if ((data & 0x0010000) != 0)
                            expad.SetSyncData(2, 1);
                        else
                            expad.SetSyncData(2, 0);
                    }
                    else
                    {
                        expad.SetSyncData(2, (byte)(data >> 16));
                    }
                    break;
                case EXCONTROLLER.EXCONTROLLER_CRAZYCLIMBER:
                    expad.SetSyncData(0, (int)data);
                    break;
                case EXCONTROLLER.EXCONTROLLER_TOPRIDER:
                    expad.SetSyncData(0, (int)data);
                    break;
                case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_A:
                case EXCONTROLLER.EXCONTROLLER_FAMILYTRAINER_B:
                    expad.SetSyncData(0, (int)data);
                    break;
                case EXCONTROLLER.EXCONTROLLER_EXCITINGBOXING:
                    expad.SetSyncData(0, (int)data);
                    break;
                case EXCONTROLLER.EXCONTROLLER_MAHJANG:
                    expad.SetSyncData(0, (int)data);
                    break;
                default:
                    break;
            }
        }
    }

    public enum VSType
    {
        VS_TYPE0 = 0,   // SELECT1P=START1P/SELECT2P=START2P 1P/2P No reverse
        VS_TYPE1,   // SELECT1P=START1P/SELECT2P=START2P 1P/2P Reverse
        VS_TYPE2,   // SELECT1P=START1P/START1P =START2P 1P/2P No reverse
        VS_TYPE3,   // SELECT1P=START1P/START1P =START2P 1P/2P Reverse
        VS_TYPE4,   // SELECT1P=START1P/SELECT2P=START2P 1P/2P No reverse (Protection)
        VS_TYPE5,   // SELECT1P=START1P/SELECT2P=START2P 1P/2P Reverse    (Protection)
        VS_TYPE6,   // SELECT1P=START1P/SELECT2P=START2P 1P/2P Reverse	(For Golf)
        VS_TYPEZ,	// ZAPPER
    }

    public enum EXCONTROLLER
    {
        EXCONTROLLER_NONE = 0,
        EXCONTROLLER_PADDLE,
        EXCONTROLLER_HYPERSHOT,
        EXCONTROLLER_ZAPPER,
        EXCONTROLLER_KEYBOARD,
        EXCONTROLLER_CRAZYCLIMBER,
        EXCONTROLLER_TOPRIDER,
        EXCONTROLLER_SPACESHADOWGUN,

        EXCONTROLLER_FAMILYTRAINER_A,
        EXCONTROLLER_FAMILYTRAINER_B,
        EXCONTROLLER_EXCITINGBOXING,
        EXCONTROLLER_MAHJANG,
        EXCONTROLLER_OEKAKIDS_TABLET,
        EXCONTROLLER_TURBOFILE,

        EXCONTROLLER_VSUNISYSTEM,
        EXCONTROLLER_VSZAPPER,

        EXCONTROLLER_GYROMITE,
        EXCONTROLLER_STACKUP,

        EXCONTROLLER_SUPOR_KEYBOARD,
    }
}