// cynes - C/C++ NES emulator with Python bindings
// Copyright (C) 2021  Combey Theo <https://www.gnu.org/licenses/>

#ifndef __NES_EMULATOR_H__
#define __NES_EMULATOR_H__

#include <cstdint>
#include <cstring>

#include <thread>


namespace nes {
    class Dumpable {
    public:
        virtual unsigned int size() = 0;

        virtual void dump(uint8_t*& buffer) = 0;
        virtual void load(uint8_t*& buffer) = 0;
    };

    template<class T> void write(uint8_t*& buffer, T value) {
        memcpy(buffer, &value, sizeof(T)); buffer += sizeof(T);
    }

    template<class T> void write(uint8_t*& buffer, T* values, unsigned int size) {
        memcpy(buffer, values, sizeof(T) * size); buffer += sizeof(T) * size;
    }

    template<class T> void read(uint8_t*& buffer, T& value) {
        memcpy(&value, buffer, sizeof(T)); buffer += sizeof(T);
    }

    template<class T> void read(uint8_t*& buffer, T* values, unsigned int size) {
        memcpy(values, buffer, sizeof(T) * size); buffer += sizeof(T) * size;
    }

    enum class MirroringMode {
        ONE_SCREEN_LOW, ONE_SCREEN_HIGH, HORIZONTAL, VERTICAL
    };

    class Mapper : public virtual Dumpable {
    public:
        Mapper(uint8_t* programMemory, uint8_t* characterMemory, MirroringMode mode);
        virtual ~Mapper();

        virtual uint8_t readCPU(uint16_t address) = 0;
        virtual uint8_t readPPU(uint16_t address) = 0;

        virtual void writeCPU(uint16_t address, uint8_t value) = 0;
        virtual void writePPU(uint16_t address, uint8_t value) = 0;

        virtual unsigned int size();

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

        virtual void notify(uint16_t address, uint32_t cycle);

        virtual bool shouldIRQ();

        uint16_t getMirroredAddress(uint16_t address);

    protected:
        uint8_t* programMemory;
        uint8_t* characterMemory;

        MirroringMode mode;
    };

    class Mapper000 : public virtual Mapper {
    public:
        Mapper000(uint8_t* programMemory, uint8_t* characterMemory, uint8_t programBanks, uint8_t characterBanks, MirroringMode mode);
        ~Mapper000();

        virtual uint8_t readCPU(uint16_t address);
        virtual uint8_t readPPU(uint16_t address);

        virtual void writeCPU(uint16_t address, uint8_t value);
        virtual void writePPU(uint16_t address, uint8_t value);

        virtual unsigned int size();

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

    private:
        uint8_t ram[0x2000] = { 0 };

        uint8_t programBanks;
        uint8_t characterBanks;
    };

    class Mapper001 : public virtual Mapper {
    public:
        Mapper001(uint8_t* programMemory, uint8_t* characterMemory, uint8_t programBanks, uint8_t characterBanks, MirroringMode mode);
        ~Mapper001();

        virtual uint8_t readCPU(uint16_t address);
        virtual uint8_t readPPU(uint16_t address);

        virtual void writeCPU(uint16_t address, uint8_t value);
        virtual void writePPU(uint16_t address, uint8_t value);

        virtual unsigned int size();

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

    private:
        uint8_t ram[0x2000] = { 0 };

        uint8_t counter = 0;

        uint8_t registerControl = 0;
        uint8_t registerLoad = 0;

        uint8_t programBankSelected = 0;

        uint16_t characterBankSelected = 0;

        uint8_t programBanks;
        uint8_t characterBanks;
    };

    class Mapper003 : public virtual Mapper {
    public:
        Mapper003(uint8_t* programMemory, uint8_t* characterMemory, uint8_t programBanks, uint8_t characterBanks, MirroringMode mode);
        ~Mapper003();

        virtual uint8_t readCPU(uint16_t address);
        virtual uint8_t readPPU(uint16_t address);

        virtual void writeCPU(uint16_t address, uint8_t value);
        virtual void writePPU(uint16_t address, uint8_t value);

        virtual unsigned int size();

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

    private:
        uint8_t programBanks;
        uint8_t characterBanks;

        uint8_t selectedCharacterBank;
    };

    class Mapper004 : public virtual Mapper {
    public:
        Mapper004(uint8_t* programMemory, uint8_t* characterMemory, uint8_t programBanks, uint8_t characterBanks, MirroringMode mode);
        ~Mapper004();

        virtual uint8_t readCPU(uint16_t address);
        virtual uint8_t readPPU(uint16_t address);

        virtual void writeCPU(uint16_t address, uint8_t value);
        virtual void writePPU(uint16_t address, uint8_t value);

        virtual unsigned int size();

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

        virtual void notify(uint16_t address, uint32_t cycle);

        virtual bool shouldIRQ();

    private:
        uint8_t ram[0x2000] = { 0 };

        uint8_t programBanks;
        uint8_t characterBanks;

        uint8_t targetRegister = 0x00;

        bool programMode = false;
        bool characterMode = false;

        uint32_t registers[8] = { 0 };
        uint32_t programBankPointers[4] = { 0 };
        uint32_t characterBankPointers[8] = { 0 };

        bool sendIRQ = false;
        bool enableIRQ = false;
        bool shouldReloadIRQ = false;

        uint16_t counter = 0x0000;
        uint16_t reloadValue = 0x0000;

        uint32_t lastCycle = 0;
        uint32_t cyclesDown = 0;

        void updateProgramMapping();
        void updateCharacterMapping();

        void clockIRQ();
    };

    Mapper* load(const char* path);

    class PPU : public virtual Dumpable {
    public:
        PPU(Mapper& mapper);
        ~PPU();

        void tick();

        void write(uint8_t address, uint8_t value);
        void writeDMA(uint8_t value);

        uint8_t read(uint8_t address);

        bool shouldNMI();
        bool shouldRender();

        uint8_t* getFrameBuffer();

        virtual unsigned int size() { return 0x1188; }

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

    private:
        Mapper& mapper;

    private:
        uint8_t frameBuffer[256 * 240 * 3] = { 0 };

        uint8_t memoryVideo[0x1000];
        uint8_t memoryPalette[0x0020];
        uint8_t memorySprites[0x0100];

        uint16_t pixelX;
        uint16_t pixelY;

        bool sendNMI = false;
        bool preventNMI = false;

        bool addressLatch = false;
        bool cycleLatch = false;

        bool frameReady = false;

        uint8_t bufferData = 0;

    private:
        enum Register : uint8_t {
            PPU_CTRL = 0x00, PPU_MASK = 0x01, PPU_STATUS = 0x02, OAM_ADDR = 0x03,
            OAM_DATA = 0x04, PPU_SCROLL = 0x05, PPU_ADDR = 0x06, PPU_DATA = 0x07
        };

        void internalWrite(uint16_t address, uint8_t value);

        uint8_t internalRead(uint16_t address);

    private:
        bool controlIncrementMode = false;
        bool controlForegroundTable = false;
        bool controlBackgroundTable = false;
        bool controlForegroundLarge = false;
        bool controlInteruptOnVBL = false;

    private:
        bool maskGreyscaleMode = false;
        bool maskRenderBackgroundLeft = false;
        bool maskRenderForegroundLeft = false;
        bool maskRenderBackground = false;
        bool maskRenderForeground = false;

        uint8_t maskColorEmphasize = 0;

    private:
        bool statusSpriteOverflow = false;
        bool statusSprite0Hit = false;
        bool statusVerticalBlank = false;

    private:
        uint8_t clockDecays[3] = { 0 };

        uint8_t registerDecay = 0;

        uint16_t registerVDelayed = 0;

        uint8_t delayDataRead = 0;
        uint8_t delayDataWrite = 0;

        void updateDecay(uint8_t mask);

    private:
        uint16_t registerT = 0;
        uint16_t registerV = 0;

        uint8_t offsetX = 0;

        void incrementScrollX();
        void incrementScrollY();

        void resetScrollX();
        void resetScrollY();

    private:
        uint8_t backgroundData[0x4] = { 0 };
        uint16_t backgroundShifter[0x4] = { 0 };

        void fetchBackgroundData();
        void updateBackgroundShifter();

    private:
        uint8_t foregroundData[0x20] = { 0 };
        uint8_t foregroundShifter[0x10] = { 0 };
        uint8_t foregroundAttributes[0x8] = { 0 };
        uint8_t foregroundPositions[0x8] = { 0 };

        uint8_t foregroundDataPointer = 0;
        uint8_t foregroundSpriteCount = 0;
        uint8_t foregroundSpriteCountNext = 0;
        uint8_t foregroundSpritePointer = 0;
        uint8_t foregroundReadDelay = 0;
        uint8_t foregroundOffset = 0;

        bool foregroundSprite0Line = false;
        bool foregroundSprite0Should = false;
        bool foregroundSprite0Hit = false;

        uint8_t foregroundEvaluationStep = 0x0;

        void resetForegroundData();
        void clearForegroundData();
        void fetchForegroundData();
        void loadForegroundShifter();
        void updateForegroundShifter();

        uint8_t blend();

    private:
        const uint8_t PALETTE_COLORS[0x8][0x40][3] = {
            0x54, 0x54, 0x54, 0x00, 0x1E, 0x74, 0x08, 0x10, 0x90, 0x30, 0x00, 0x88, 0x44, 0x00, 0x64, 0x5C,
            0x00, 0x30, 0x54, 0x04, 0x00, 0x3C, 0x18, 0x00, 0x20, 0x2A, 0x00, 0x08, 0x3A, 0x00, 0x00, 0x40,
            0x00, 0x00, 0x3C, 0x00, 0x00, 0x32, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x98, 0x96, 0x98, 0x08, 0x4C, 0xC4, 0x30, 0x32, 0xEC, 0x5C, 0x1E, 0xE4, 0x88, 0x14, 0xB0, 0xA0,
            0x14, 0x64, 0x98, 0x22, 0x20, 0x78, 0x3C, 0x00, 0x54, 0x5A, 0x00, 0x28, 0x72, 0x00, 0x08, 0x7C,
            0x00, 0x00, 0x76, 0x28, 0x00, 0x66, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xEC, 0xEE, 0xEC, 0x4C, 0x9A, 0xEC, 0x78, 0x7C, 0xEC, 0xB0, 0x62, 0xEC, 0xE4, 0x54, 0xEC, 0xEC,
            0x58, 0xB4, 0xEC, 0x6A, 0x64, 0xD4, 0x88, 0x20, 0xA0, 0xAA, 0x00, 0x74, 0xC4, 0x00, 0x4C, 0xD0,
            0x20, 0x38, 0xCC, 0x6C, 0x38, 0xB4, 0xCC, 0x3C, 0x3C, 0x3C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xEC, 0xEE, 0xEC, 0xA8, 0xCC, 0xEC, 0xBC, 0xBC, 0xEC, 0xD4, 0xB2, 0xEC, 0xEC, 0xAE, 0xEC, 0xEC,
            0xAE, 0xD4, 0xEC, 0xB4, 0xB0, 0xE4, 0xC4, 0x90, 0xCC, 0xD2, 0x78, 0xB4, 0xDE, 0x78, 0xA8, 0xE2,
            0x90, 0x98, 0xE2, 0xB4, 0xA0, 0xD6, 0xE4, 0xA0, 0xA2, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x5C, 0x4B, 0x4B, 0x00, 0x1B, 0x68, 0x08, 0x0E, 0x81, 0x34, 0x00, 0x7A, 0x4A, 0x00, 0x5A, 0x65,
            0x00, 0x2B, 0x5C, 0x03, 0x00, 0x42, 0x15, 0x00, 0x23, 0x25, 0x00, 0x08, 0x34, 0x00, 0x00, 0x39,
            0x00, 0x00, 0x36, 0x00, 0x00, 0x2D, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xA7, 0x87, 0x88, 0x08, 0x44, 0xB0, 0x34, 0x2D, 0xD4, 0x65, 0x1B, 0xCD, 0x95, 0x12, 0x9E, 0xB0,
            0x12, 0x5A, 0xA7, 0x1E, 0x1C, 0x84, 0x36, 0x00, 0x5C, 0x51, 0x00, 0x2C, 0x66, 0x00, 0x08, 0x6F,
            0x00, 0x00, 0x6A, 0x24, 0x00, 0x5B, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xFF, 0xD6, 0xD4, 0x53, 0x8A, 0xD4, 0x84, 0x6F, 0xD4, 0xC1, 0x58, 0xD4, 0xFA, 0x4B, 0xD4, 0xFF,
            0x4F, 0xA2, 0xFF, 0x5F, 0x5A, 0xE9, 0x7A, 0x1C, 0xB0, 0x99, 0x00, 0x7F, 0xB0, 0x00, 0x53, 0xBB,
            0x1C, 0x3D, 0xB7, 0x61, 0x3D, 0xA2, 0xB7, 0x42, 0x36, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xFF, 0xD6, 0xD4, 0xB8, 0xB7, 0xD4, 0xCE, 0xA9, 0xD4, 0xE9, 0xA0, 0xD4, 0xFF, 0x9C, 0xD4, 0xFF,
            0x9C, 0xBE, 0xFF, 0xA2, 0x9E, 0xFA, 0xB0, 0x81, 0xE0, 0xBD, 0x6C, 0xC6, 0xC7, 0x6C, 0xB8, 0xCB,
            0x81, 0xA7, 0xCB, 0xA2, 0xB0, 0xC0, 0xCD, 0xB0, 0x91, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x4B, 0x5C, 0x4B, 0x00, 0x21, 0x68, 0x07, 0x11, 0x81, 0x2B, 0x00, 0x7A, 0x3D, 0x00, 0x5A, 0x52,
            0x00, 0x2B, 0x4B, 0x04, 0x00, 0x36, 0x1A, 0x00, 0x1C, 0x2E, 0x00, 0x07, 0x3F, 0x00, 0x00, 0x46,
            0x00, 0x00, 0x42, 0x00, 0x00, 0x37, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x88, 0xA5, 0x88, 0x07, 0x53, 0xB0, 0x2B, 0x37, 0xD4, 0x52, 0x21, 0xCD, 0x7A, 0x16, 0x9E, 0x90,
            0x16, 0x5A, 0x88, 0x25, 0x1C, 0x6C, 0x42, 0x00, 0x4B, 0x63, 0x00, 0x24, 0x7D, 0x00, 0x07, 0x88,
            0x00, 0x00, 0x81, 0x24, 0x00, 0x70, 0x6C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xD4, 0xFF, 0xD4, 0x44, 0xA9, 0xD4, 0x6C, 0x88, 0xD4, 0x9E, 0x6B, 0xD4, 0xCD, 0x5C, 0xD4, 0xD4,
            0x60, 0xA2, 0xD4, 0x74, 0x5A, 0xBE, 0x95, 0x1C, 0x90, 0xBB, 0x00, 0x68, 0xD7, 0x00, 0x44, 0xE4,
            0x1C, 0x32, 0xE0, 0x61, 0x32, 0xC6, 0xB7, 0x36, 0x42, 0x36, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xD4, 0xFF, 0xD4, 0x97, 0xE0, 0xD4, 0xA9, 0xCE, 0xD4, 0xBE, 0xC3, 0xD4, 0xD4, 0xBF, 0xD4, 0xD4,
            0xBF, 0xBE, 0xD4, 0xC6, 0x9E, 0xCD, 0xD7, 0x81, 0xB7, 0xE7, 0x6C, 0xA2, 0xF4, 0x6C, 0x97, 0xF8,
            0x81, 0x88, 0xF8, 0xA2, 0x90, 0xEB, 0xCD, 0x90, 0xB2, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x53, 0x53, 0x44, 0x00, 0x1D, 0x5D, 0x07, 0x0F, 0x74, 0x2F, 0x00, 0x6E, 0x43, 0x00, 0x51, 0x5B,
            0x00, 0x26, 0x53, 0x03, 0x00, 0x3B, 0x17, 0x00, 0x1F, 0x29, 0x00, 0x07, 0x39, 0x00, 0x00, 0x3F,
            0x00, 0x00, 0x3B, 0x00, 0x00, 0x31, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x96, 0x94, 0x7B, 0x07, 0x4B, 0x9E, 0x2F, 0x31, 0xBF, 0x5B, 0x1D, 0xB8, 0x86, 0x13, 0x8E, 0x9E,
            0x13, 0x51, 0x96, 0x21, 0x19, 0x76, 0x3B, 0x00, 0x53, 0x59, 0x00, 0x27, 0x70, 0x00, 0x07, 0x7A,
            0x00, 0x00, 0x74, 0x20, 0x00, 0x64, 0x61, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xE9, 0xEB, 0xBF, 0x4B, 0x98, 0xBF, 0x76, 0x7A, 0xBF, 0xAE, 0x61, 0xBF, 0xE1, 0x53, 0xBF, 0xE9,
            0x57, 0x91, 0xE9, 0x68, 0x51, 0xD1, 0x86, 0x19, 0x9E, 0xA8, 0x00, 0x72, 0xC2, 0x00, 0x4B, 0xCD,
            0x19, 0x37, 0xC9, 0x57, 0x37, 0xB2, 0xA5, 0x3B, 0x3B, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xE9, 0xEB, 0xBF, 0xA6, 0xC9, 0xBF, 0xBA, 0xBA, 0xBF, 0xD1, 0xB0, 0xBF, 0xE9, 0xAC, 0xBF, 0xE9,
            0xAC, 0xAB, 0xE9, 0xB2, 0x8E, 0xE1, 0xC2, 0x74, 0xC9, 0xCF, 0x61, 0xB2, 0xDB, 0x61, 0xA6, 0xDF,
            0x74, 0x96, 0xDF, 0x91, 0x9E, 0xD3, 0xB8, 0x9E, 0xA0, 0x81, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x4B, 0x4B, 0x5C, 0x00, 0x1B, 0x7F, 0x07, 0x0E, 0x9E, 0x2B, 0x00, 0x95, 0x3D, 0x00, 0x6E, 0x52,
            0x00, 0x34, 0x4B, 0x03, 0x00, 0x36, 0x15, 0x00, 0x1C, 0x25, 0x00, 0x07, 0x34, 0x00, 0x00, 0x39,
            0x00, 0x00, 0x36, 0x00, 0x00, 0x2D, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x88, 0x87, 0xA7, 0x07, 0x44, 0xD7, 0x2B, 0x2D, 0xFF, 0x52, 0x1B, 0xFA, 0x7A, 0x12, 0xC1, 0x90,
            0x12, 0x6E, 0x88, 0x1E, 0x23, 0x6C, 0x36, 0x00, 0x4B, 0x51, 0x00, 0x24, 0x66, 0x00, 0x07, 0x6F,
            0x00, 0x00, 0x6A, 0x2C, 0x00, 0x5B, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xD4, 0xD6, 0xFF, 0x44, 0x8A, 0xFF, 0x6C, 0x6F, 0xFF, 0x9E, 0x58, 0xFF, 0xCD, 0x4B, 0xFF, 0xD4,
            0x4F, 0xC6, 0xD4, 0x5F, 0x6E, 0xBE, 0x7A, 0x23, 0x90, 0x99, 0x00, 0x68, 0xB0, 0x00, 0x44, 0xBB,
            0x23, 0x32, 0xB7, 0x76, 0x32, 0xA2, 0xE0, 0x36, 0x36, 0x42, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xD4, 0xD6, 0xFF, 0x97, 0xB7, 0xFF, 0xA9, 0xA9, 0xFF, 0xBE, 0xA0, 0xFF, 0xD4, 0x9C, 0xFF, 0xD4,
            0x9C, 0xE9, 0xD4, 0xA2, 0xC1, 0xCD, 0xB0, 0x9E, 0xB7, 0xBD, 0x84, 0xA2, 0xC7, 0x84, 0x97, 0xCB,
            0x9E, 0x88, 0xCB, 0xC6, 0x90, 0xC0, 0xFA, 0x90, 0x91, 0xB0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x53, 0x44, 0x53, 0x00, 0x18, 0x72, 0x07, 0x0C, 0x8E, 0x2F, 0x00, 0x86, 0x43, 0x00, 0x63, 0x5B,
            0x00, 0x2F, 0x53, 0x03, 0x00, 0x3B, 0x13, 0x00, 0x1F, 0x22, 0x00, 0x07, 0x2E, 0x00, 0x00, 0x33,
            0x00, 0x00, 0x30, 0x00, 0x00, 0x28, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x96, 0x79, 0x96, 0x07, 0x3D, 0xC2, 0x2F, 0x28, 0xE9, 0x5B, 0x18, 0xE1, 0x86, 0x10, 0xAE, 0x9E,
            0x10, 0x63, 0x96, 0x1B, 0x1F, 0x76, 0x30, 0x00, 0x53, 0x48, 0x00, 0x27, 0x5C, 0x00, 0x07, 0x64,
            0x00, 0x00, 0x5F, 0x27, 0x00, 0x52, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xE9, 0xC0, 0xE9, 0x4B, 0x7C, 0xE9, 0x76, 0x64, 0xE9, 0xAE, 0x4F, 0xE9, 0xE1, 0x44, 0xE9, 0xE9,
            0x47, 0xB2, 0xE9, 0x55, 0x63, 0xD1, 0x6E, 0x1F, 0x9E, 0x89, 0x00, 0x72, 0x9E, 0x00, 0x4B, 0xA8,
            0x1F, 0x37, 0xA5, 0x6A, 0x37, 0x91, 0xC9, 0x3B, 0x30, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xE9, 0xC0, 0xE9, 0xA6, 0xA5, 0xE9, 0xBA, 0x98, 0xE9, 0xD1, 0x90, 0xE9, 0xE9, 0x8C, 0xE9, 0xE9,
            0x8C, 0xD1, 0xE9, 0x91, 0xAE, 0xE1, 0x9E, 0x8E, 0xC9, 0xAA, 0x76, 0xB2, 0xB3, 0x76, 0xA6, 0xB7,
            0x8E, 0x96, 0xB7, 0xB2, 0x9E, 0xAD, 0xE1, 0x9E, 0x83, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x44, 0x53, 0x53, 0x00, 0x1D, 0x72, 0x06, 0x0F, 0x8E, 0x26, 0x00, 0x86, 0x37, 0x00, 0x63, 0x4A,
            0x00, 0x2F, 0x44, 0x03, 0x00, 0x30, 0x17, 0x00, 0x19, 0x29, 0x00, 0x06, 0x39, 0x00, 0x00, 0x3F,
            0x00, 0x00, 0x3B, 0x00, 0x00, 0x31, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x7B, 0x94, 0x96, 0x06, 0x4B, 0xC2, 0x26, 0x31, 0xE9, 0x4A, 0x1D, 0xE1, 0x6E, 0x13, 0xAE, 0x81,
            0x13, 0x63, 0x7B, 0x21, 0x1F, 0x61, 0x3B, 0x00, 0x44, 0x59, 0x00, 0x20, 0x70, 0x00, 0x06, 0x7A,
            0x00, 0x00, 0x74, 0x27, 0x00, 0x64, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xBF, 0xEB, 0xE9, 0x3D, 0x98, 0xE9, 0x61, 0x7A, 0xE9, 0x8E, 0x61, 0xE9, 0xB8, 0x53, 0xE9, 0xBF,
            0x57, 0xB2, 0xBF, 0x68, 0x63, 0xAB, 0x86, 0x1F, 0x81, 0xA8, 0x00, 0x5D, 0xC2, 0x00, 0x3D, 0xCD,
            0x1F, 0x2D, 0xC9, 0x6A, 0x2D, 0xB2, 0xC9, 0x30, 0x3B, 0x3B, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xBF, 0xEB, 0xE9, 0x88, 0xC9, 0xE9, 0x98, 0xBA, 0xE9, 0xAB, 0xB0, 0xE9, 0xBF, 0xAC, 0xE9, 0xBF,
            0xAC, 0xD1, 0xBF, 0xB2, 0xAE, 0xB8, 0xC2, 0x8E, 0xA5, 0xCF, 0x76, 0x91, 0xDB, 0x76, 0x88, 0xDF,
            0x8E, 0x7B, 0xDF, 0xB2, 0x81, 0xD3, 0xE1, 0x81, 0xA0, 0x9E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x4A, 0x4A, 0x4A, 0x00, 0x1A, 0x67, 0x07, 0x0E, 0x80, 0x2A, 0x00, 0x79, 0x3C, 0x00, 0x59, 0x51,
            0x00, 0x2A, 0x4A, 0x03, 0x00, 0x35, 0x15, 0x00, 0x1C, 0x25, 0x00, 0x07, 0x33, 0x00, 0x00, 0x39,
            0x00, 0x00, 0x35, 0x00, 0x00, 0x2C, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0x87, 0x85, 0x87, 0x07, 0x43, 0xAE, 0x2A, 0x2C, 0xD2, 0x51, 0x1A, 0xCB, 0x79, 0x11, 0x9C, 0x8E,
            0x11, 0x59, 0x87, 0x1E, 0x1C, 0x6A, 0x35, 0x00, 0x4A, 0x50, 0x00, 0x23, 0x65, 0x00, 0x07, 0x6E,
            0x00, 0x00, 0x69, 0x23, 0x00, 0x5A, 0x6A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xD2, 0xD4, 0xD2, 0x43, 0x89, 0xD2, 0x6A, 0x6E, 0xD2, 0x9C, 0x57, 0xD2, 0xCB, 0x4A, 0xD2, 0xD2,
            0x4E, 0xA0, 0xD2, 0x5E, 0x59, 0xBC, 0x79, 0x1C, 0x8E, 0x97, 0x00, 0x67, 0xAE, 0x00, 0x43, 0xB9,
            0x1C, 0x31, 0xB5, 0x60, 0x31, 0xA0, 0xB5, 0x35, 0x35, 0x35, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
            0xD2, 0xD4, 0xD2, 0x95, 0xB5, 0xD2, 0xA7, 0xA7, 0xD2, 0xBC, 0x9E, 0xD2, 0xD2, 0x9B, 0xD2, 0xD2,
            0x9B, 0xBC, 0xD2, 0xA0, 0x9C, 0xCB, 0xAE, 0x80, 0xB5, 0xBB, 0x6A, 0xA0, 0xC5, 0x6A, 0x95, 0xC9,
            0x80, 0x87, 0xC9, 0xA0, 0x8E, 0xBE, 0xCB, 0x8E, 0x90, 0x8E, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
        };
    };
    
    class CPU : public virtual Dumpable {
    public:
        CPU(Mapper& mapper, PPU& ppu);
        ~CPU();

        void tick();

        void reset();
        void interrupt(bool nmi);

        void silentWrite(uint16_t address, uint8_t value);
        uint8_t silentRead(uint16_t address);

        void setControllerState(uint8_t state);

        bool isFrozen();
        bool isPollingController();

        virtual unsigned int size() { return 0x80C; }

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

    private:
        Mapper& mapper;
        PPU& ppu;

    private:
        uint8_t memory[0x800];

        uint8_t registerA;
        uint8_t registerX;
        uint8_t registerY;
        uint8_t registerM;

        uint8_t stackPointer;

        uint16_t programCounter;

        bool frozen = false;

    private:
        void internalTick();

        uint8_t fetch();

        void write(uint16_t addres, uint8_t value);

        uint8_t read(uint16_t address);

    private:
        uint8_t status;

        enum Flag : uint8_t {
            C = 0x01, Z = 0x02, I = 0x04, D = 0x08, B = 0x10, U = 0x20, V = 0x40, N = 0x80
        };

        void setStatus(uint8_t flag, bool value);
        bool getStatus(uint8_t flag);

    private:
        bool cycleLatch = false;

        bool spriteTransferActive = false;
        bool spriteTransferSynchronized = false;

        uint8_t spriteTransferValue;

        uint16_t spriteTransferAddress;

    private:
        uint8_t controllerShifter;

        bool pollingController = false;

    private:
        uint16_t targetAddress;

        void ABR(); void ABW(); void ACC(); void AXM(); void AXR(); void AXW(); void AYM(); void AYR();
        void AYW(); void IMM(); void IMP(); void IND(); void IXR(); void IXW(); void IYM(); void IYR();
        void IYW(); void REL(); void ZPR(); void ZPW(); void ZXR(); void ZXW(); void ZYR(); void ZYW();

    private:
        void AAL(); void ADC(); void ALR(); void ANC(); void AND(); void ANE(); void ARR(); void ASL();
        void BCC(); void BCS(); void BEQ(); void BIT(); void BMI(); void BNE(); void BPL(); void BRK();
        void BVC(); void BVS(); void CLC(); void CLD(); void CLI(); void CLV(); void CMP(); void CPX();
        void CPY(); void DCP(); void DEC(); void DEX(); void DEY(); void EOR(); void INC(); void INX();
        void INY(); void ISC(); void JAM(); void JMP(); void JSR(); void LAR(); void LAS(); void LAX();
        void LDA(); void LDX(); void LDY(); void LSR(); void LXA(); void NOP(); void ORA(); void PHA();
        void PHP(); void PLA(); void PLP(); void RAL(); void RAR(); void RLA(); void ROL(); void ROR();
        void RRA(); void RTI(); void RTS(); void SAX(); void SBC(); void SBX(); void SEC(); void SED();
        void SEI(); void SHA(); void SHX(); void SHY(); void SLO(); void SRE(); void STA(); void STX();
        void STY(); void TAS(); void TAX(); void TAY(); void TSX(); void TXA(); void TXS(); void TYA();
        void USB();

    private:
        void (CPU::* INSTRUCTION_SET[256][2]) (void) = {
            &CPU::BRK,&CPU::IMP,&CPU::ORA,&CPU::IXR,&CPU::JAM,&CPU::ACC,&CPU::SLO,&CPU::IXR,
            &CPU::NOP,&CPU::ZPR,&CPU::ORA,&CPU::ZPR,&CPU::ASL,&CPU::ZPR,&CPU::SLO,&CPU::ZPR,
            &CPU::PHP,&CPU::ACC,&CPU::ORA,&CPU::IMM,&CPU::AAL,&CPU::ACC,&CPU::ANC,&CPU::IMM,
            &CPU::NOP,&CPU::ABR,&CPU::ORA,&CPU::ABR,&CPU::ASL,&CPU::ABR,&CPU::SLO,&CPU::ABR,
            &CPU::BPL,&CPU::REL,&CPU::ORA,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::SLO,&CPU::IYM,
            &CPU::NOP,&CPU::ZXR,&CPU::ORA,&CPU::ZXR,&CPU::ASL,&CPU::ZXR,&CPU::SLO,&CPU::ZXR,
            &CPU::CLC,&CPU::ACC,&CPU::ORA,&CPU::AYR,&CPU::NOP,&CPU::ACC,&CPU::SLO,&CPU::AYM,
            &CPU::NOP,&CPU::AXR,&CPU::ORA,&CPU::AXR,&CPU::ASL,&CPU::AXM,&CPU::SLO,&CPU::AXM,
            &CPU::JSR,&CPU::ZPW,&CPU::AND,&CPU::IXR,&CPU::JAM,&CPU::ACC,&CPU::RLA,&CPU::IXR,
            &CPU::BIT,&CPU::ZPR,&CPU::AND,&CPU::ZPR,&CPU::ROL,&CPU::ZPR,&CPU::RLA,&CPU::ZPR,
            &CPU::PLP,&CPU::ACC,&CPU::AND,&CPU::IMM,&CPU::RAL,&CPU::ACC,&CPU::ANC,&CPU::IMM,
            &CPU::BIT,&CPU::ABR,&CPU::AND,&CPU::ABR,&CPU::ROL,&CPU::ABR,&CPU::RLA,&CPU::ABR,
            &CPU::BMI,&CPU::REL,&CPU::AND,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::RLA,&CPU::IYM,
            &CPU::NOP,&CPU::ZXR,&CPU::AND,&CPU::ZXR,&CPU::ROL,&CPU::ZXR,&CPU::RLA,&CPU::ZXR,
            &CPU::SEC,&CPU::ACC,&CPU::AND,&CPU::AYR,&CPU::NOP,&CPU::ACC,&CPU::RLA,&CPU::AYM,
            &CPU::NOP,&CPU::AXR,&CPU::AND,&CPU::AXR,&CPU::ROL,&CPU::AXM,&CPU::RLA,&CPU::AXM,
            &CPU::RTI,&CPU::ACC,&CPU::EOR,&CPU::IXR,&CPU::JAM,&CPU::ACC,&CPU::SRE,&CPU::IXR,
            &CPU::NOP,&CPU::ZPR,&CPU::EOR,&CPU::ZPR,&CPU::LSR,&CPU::ZPR,&CPU::SRE,&CPU::ZPR,
            &CPU::PHA,&CPU::ACC,&CPU::EOR,&CPU::IMM,&CPU::LAR,&CPU::ACC,&CPU::ALR,&CPU::IMM,
            &CPU::JMP,&CPU::ABW,&CPU::EOR,&CPU::ABR,&CPU::LSR,&CPU::ABR,&CPU::SRE,&CPU::ABR,
            &CPU::BVC,&CPU::REL,&CPU::EOR,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::SRE,&CPU::IYM,
            &CPU::NOP,&CPU::ZXR,&CPU::EOR,&CPU::ZXR,&CPU::LSR,&CPU::ZXR,&CPU::SRE,&CPU::ZXR,
            &CPU::CLI,&CPU::ACC,&CPU::EOR,&CPU::AYR,&CPU::NOP,&CPU::ACC,&CPU::SRE,&CPU::AYM,
            &CPU::NOP,&CPU::AXR,&CPU::EOR,&CPU::AXR,&CPU::LSR,&CPU::AXM,&CPU::SRE,&CPU::AXM,
            &CPU::RTS,&CPU::ACC,&CPU::ADC,&CPU::IXR,&CPU::JAM,&CPU::ACC,&CPU::RRA,&CPU::IXR,
            &CPU::NOP,&CPU::ZPR,&CPU::ADC,&CPU::ZPR,&CPU::ROR,&CPU::ZPR,&CPU::RRA,&CPU::ZPR,
            &CPU::PLA,&CPU::ACC,&CPU::ADC,&CPU::IMM,&CPU::RAR,&CPU::ACC,&CPU::ARR,&CPU::IMM,
            &CPU::JMP,&CPU::IND,&CPU::ADC,&CPU::ABR,&CPU::ROR,&CPU::ABR,&CPU::RRA,&CPU::ABR,
            &CPU::BVS,&CPU::REL,&CPU::ADC,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::RRA,&CPU::IYM,
            &CPU::NOP,&CPU::ZXR,&CPU::ADC,&CPU::ZXR,&CPU::ROR,&CPU::ZXR,&CPU::RRA,&CPU::ZXR,
            &CPU::SEI,&CPU::ACC,&CPU::ADC,&CPU::AYR,&CPU::NOP,&CPU::ACC,&CPU::RRA,&CPU::AYM,
            &CPU::NOP,&CPU::AXR,&CPU::ADC,&CPU::AXR,&CPU::ROR,&CPU::AXM,&CPU::RRA,&CPU::AXM,
            &CPU::NOP,&CPU::IMM,&CPU::STA,&CPU::IXW,&CPU::NOP,&CPU::IMM,&CPU::SAX,&CPU::IXW,
            &CPU::STY,&CPU::ZPW,&CPU::STA,&CPU::ZPW,&CPU::STX,&CPU::ZPW,&CPU::SAX,&CPU::ZPW,
            &CPU::DEY,&CPU::ACC,&CPU::NOP,&CPU::IMM,&CPU::TXA,&CPU::ACC,&CPU::ANE,&CPU::IMM,
            &CPU::STY,&CPU::ABW,&CPU::STA,&CPU::ABW,&CPU::STX,&CPU::ABW,&CPU::SAX,&CPU::ABW,
            &CPU::BCC,&CPU::REL,&CPU::STA,&CPU::IYW,&CPU::JAM,&CPU::ACC,&CPU::SHA,&CPU::IYW,
            &CPU::STY,&CPU::ZXW,&CPU::STA,&CPU::ZXW,&CPU::STX,&CPU::ZYW,&CPU::SAX,&CPU::ZYW,
            &CPU::TYA,&CPU::ACC,&CPU::STA,&CPU::AYW,&CPU::TXS,&CPU::ACC,&CPU::TAS,&CPU::AYW,
            &CPU::SHY,&CPU::AXW,&CPU::STA,&CPU::AXW,&CPU::SHX,&CPU::AYW,&CPU::SHA,&CPU::AYW,
            &CPU::LDY,&CPU::IMM,&CPU::LDA,&CPU::IXR,&CPU::LDX,&CPU::IMM,&CPU::LAX,&CPU::IXR,
            &CPU::LDY,&CPU::ZPR,&CPU::LDA,&CPU::ZPR,&CPU::LDX,&CPU::ZPR,&CPU::LAX,&CPU::ZPR,
            &CPU::TAY,&CPU::ACC,&CPU::LDA,&CPU::IMM,&CPU::TAX,&CPU::ACC,&CPU::LXA,&CPU::IMM,
            &CPU::LDY,&CPU::ABR,&CPU::LDA,&CPU::ABR,&CPU::LDX,&CPU::ABR,&CPU::LAX,&CPU::ABR,
            &CPU::BCS,&CPU::REL,&CPU::LDA,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::LAX,&CPU::IYR,
            &CPU::LDY,&CPU::ZXR,&CPU::LDA,&CPU::ZXR,&CPU::LDX,&CPU::ZYR,&CPU::LAX,&CPU::ZYR,
            &CPU::CLV,&CPU::ACC,&CPU::LDA,&CPU::AYR,&CPU::TSX,&CPU::ACC,&CPU::LAS,&CPU::AYR,
            &CPU::LDY,&CPU::AXR,&CPU::LDA,&CPU::AXR,&CPU::LDX,&CPU::AYR,&CPU::LAX,&CPU::AYR,
            &CPU::CPY,&CPU::IMM,&CPU::CMP,&CPU::IXR,&CPU::NOP,&CPU::IMM,&CPU::DCP,&CPU::IXR,
            &CPU::CPY,&CPU::ZPR,&CPU::CMP,&CPU::ZPR,&CPU::DEC,&CPU::ZPR,&CPU::DCP,&CPU::ZPR,
            &CPU::INY,&CPU::ACC,&CPU::CMP,&CPU::IMM,&CPU::DEX,&CPU::ACC,&CPU::SBX,&CPU::IMM,
            &CPU::CPY,&CPU::ABR,&CPU::CMP,&CPU::ABR,&CPU::DEC,&CPU::ABR,&CPU::DCP,&CPU::ABR,
            &CPU::BNE,&CPU::REL,&CPU::CMP,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::DCP,&CPU::IYM,
            &CPU::NOP,&CPU::ZXR,&CPU::CMP,&CPU::ZXR,&CPU::DEC,&CPU::ZXR,&CPU::DCP,&CPU::ZXR,
            &CPU::CLD,&CPU::ACC,&CPU::CMP,&CPU::AYR,&CPU::NOP,&CPU::ACC,&CPU::DCP,&CPU::AYM,
            &CPU::NOP,&CPU::AXR,&CPU::CMP,&CPU::AXR,&CPU::DEC,&CPU::AXM,&CPU::DCP,&CPU::AXM,
            &CPU::CPX,&CPU::IMM,&CPU::SBC,&CPU::IXR,&CPU::NOP,&CPU::IMM,&CPU::ISC,&CPU::IXR,
            &CPU::CPX,&CPU::ZPR,&CPU::SBC,&CPU::ZPR,&CPU::INC,&CPU::ZPR,&CPU::ISC,&CPU::ZPR,
            &CPU::INX,&CPU::ACC,&CPU::SBC,&CPU::IMM,&CPU::NOP,&CPU::ACC,&CPU::USB,&CPU::IMM,
            &CPU::CPX,&CPU::ABR,&CPU::SBC,&CPU::ABR,&CPU::INC,&CPU::ABR,&CPU::ISC,&CPU::ABR,
            &CPU::BEQ,&CPU::REL,&CPU::SBC,&CPU::IYR,&CPU::JAM,&CPU::ACC,&CPU::ISC,&CPU::IYM,
            &CPU::NOP,&CPU::ZXR,&CPU::SBC,&CPU::ZXR,&CPU::INC,&CPU::ZXR,&CPU::ISC,&CPU::ZXR,
            &CPU::SED,&CPU::ACC,&CPU::SBC,&CPU::AYR,&CPU::NOP,&CPU::ACC,&CPU::ISC,&CPU::AYM,
            &CPU::NOP,&CPU::AXR,&CPU::SBC,&CPU::AXR,&CPU::INC,&CPU::AXM,&CPU::ISC,&CPU::AXM
        };
    };

    class NES : public virtual Dumpable {
    public:
        NES(Mapper* mapper);
        ~NES();

        void waitFrame();
        void nextFrame(unsigned int frames);
        void setControllerState(uint8_t controllerState);

        void write(uint16_t addres, uint8_t value);
        uint8_t read(uint16_t address);

        uint8_t* getFrameBuffer();

        bool isFrozen();

        virtual unsigned int size();

        virtual void dump(uint8_t*& buffer);
        virtual void load(uint8_t*& buffer);

    private:
        Mapper* mapper;

        CPU* cpu;
        PPU* ppu;

        std::thread runningFrame;

        bool running = false;

        uint8_t controllerState = 0x00;
    };
}

#endif