//
// Created by cbihan on 1/27/20.
//

#ifndef COMSQUARE_PPU_HPP
#define COMSQUARE_PPU_HPP

#include <stdint-gcc.h>
#include "../Memory/IMemory.hpp"
#include "../Memory/MemoryBus.hpp"
#include "../Renderer/IRenderer.hpp"

//#define max2BitTiles		4096
//#define max4BitTiles		2048
//#define max8BitTiles		1024



namespace ComSquare::PPU
{

	enum ppuRegisters {
	//! @brief INIDISP Register (F-blank and Brightness)
	inidisp = 0x00,
	//! @brief OBSEL Register (Object Size and Character Address)
	obsel = 0x01,
	//! @brief OAMADDL (OAM Address low byte)
	oamaddl = 0x02,
	//! @brief OAMADDH (OAM Address high bit and Obj Priority)
	oamaddh = 0x03,
	//! @brief OAMDATA (Data for OAM write)
	oamdata = 0x04,
	//! @brief BGMODE (BG Mode and Character Size)
	bgmode = 0x05,
	//! @brief MOSAIC (Screen Pixelation)
	mosaic = 0x06,
	//! @brief BG1SC (BG1 Tilemap Address and Size)
	bg1sc = 0x07,
	//! @brief BG2SC (BG2 Tilemap Address and Size)
	bg2sc = 0x08,
	//! @brief BG3SC (BG3 Tilemap Address and Size)
	bg3sc = 0x09,
	//! @brief BG4SC (BG4 Tilemap Address and Size)
	bg4sc = 0x0A,
	//! @brief BG12NBA (BG1 and 2 Chr Address)
	bg12nba = 0x0B,
	//! @brief BG34NBA (BG3 and 4 Chr Address)
	bg34nba = 0x0C,
	//! @brief BG1HOFS (BG1 Horizontal Scroll)
	//! @brief M7HOFS (Mode 7 BG Horizontal Scroll)
	//! @info When bg mode is 7 the register is used as M7HOFS
	bg1hofs = 0x0D,
	//! @brief BG1VOFS (BG1 Vertical Scroll)
	//! @brief M7VOFS (Mode 7 BG Vertical Scroll)
	//! @info When bg mode is 7 the register is used as M7VOFS
	bg1vofs = 0x0E,
	//! @brief BG2HOFS (BG2 Horizontal Scroll)
	bg2hofs = 0x0F,
	//! @brief BG2VOFS (BG2 Vertical Scroll)
	bg2vofs = 0x10,
	//! @brief BG3HOFS (BG3 Horizontal Scroll)
	bg3hofs = 0x11,
	//! @brief BG3VOFS (BG3 Vertical Scroll)
	bg3vofs = 0x12,
	//! @brief BG4HOFS (BG4 Horizontal Scroll)
	bg4hofs = 0x13,
	//! @brief BG4VOFS (BG4 Vertical Scroll)
	bg4vofs = 0x14,
	//! @brief VMAIN (Video Port Control)
	vmain = 0x15,
	//! @brief VMADDL (VRAM Address low byte)
	vmaddl = 0x16,
	//! @brief VMADDH (VRAM Address high byte)
	vmaddh = 0x17,
	//! @brief VMDATAL (VRAM Data Write low byte)
	vmdatal = 0x18,
	//! @brief VMDATAH (VRAM Data Write high byte)
	vmdatah = 0x19,
	//! @brief M7SEL (Mode 7 Settings)
	m7sel = 0x1A,
	//! @brief M7A (Mode 7 Matrix A) also used with $2134/6
	m7a = 0x1B,
	//! @brief M7B (Mode 7 Matrix B) also used with $2134/6
	m7b = 0x1C,
	//! @brief M7C (Mode 7 Matrix C)
	m7c = 0x1D,
	//! @brief M7D (Mode 7 Matrix D)
	m7d = 0x1E,
	//! @brief M7X (Mode 7 Center X)
	m7x = 0x1F,
	//! @brief M7Y (Mode 7 Center Y)
	m7y = 0x20,
	//! @brief CGADD (CGRAM Address)
	cgadd = 0x21,
	//! @brief CGDATA (CGRAM Data write)
	cgdata = 0x22,
	//! @brief W12SEL (Window Mask Settings for BG1 and BG2)
	w12sel = 0x23,
	//! @brief W34SEL (Window Mask Settings for BG3 and BG4)
	w34sel = 0x24,
	//! @brief WOBJSEL (Window Mask Settings for OBJ and Color Window)
	wobjsel = 0x25,
	//! @brief WH0 (Window 1 Left Position)
	wh0 = 0x26,
	//! @brief WH1 (Window 1 Right Position)
	wh1 = 0x27,
	//! @brief WH2 (Window 2 Left Position)
	wh2 = 0x28,
	//! @brief WH3 (Window 2 Right Position)
	wh3 = 0x29,
	//! @brief WBGLOG (Window mask logic for BGs)
	wbjlog = 0x2A,
	//! @brief WOBJLOG (Window mask logic for OBJs and Color Window)
	wobjlog = 0x2B,
	//! @brief TM (Main Screen Designation)
	tm = 0x2C,
	//! @brief TS (Subscreen Designation)
	ts = 0x2D,
	//! @brief TMW (Window Mask Designation for the Main Screen)
	tmw = 0x2E,
	//! @brief TSW (Window Mask Designation for the Subscreen)
	tsw = 0x2F,
	//! @brief CGWSEL (Color Addition Select)
	cgwsel = 0x30,
	//! @brief CGADSUB (Color math designation)
	cgadsub = 0x31,
	//! @brief COLDATA (Fixed Color Data)
	coldata = 0x32,
	//! @brief SETINI (Screen Mode/Video Select)
	setini = 0x33,
	//! @brief MPYL (Multiplication Result low byte)
	mpyl = 0x34,
	//! @brief MPYM (Multiplication Result middle byte)
	mpum = 0x35,
	//! @brief MPYH (Multiplication Result high byte)
	mpyh = 0x36,
	//! @brief SLHV (Software Latch for H/V Counter)
	slhv = 0x37,
	//! @brief OAMDATAREAD (Data for OAM read)
	oamdataread = 0x38,
	//! @brief VMDATALREAD (VRAM Data Read low byte)
	vmdatalread = 0x39,
	//! @brief VMDATAHREAD (VRAM Data Read high byte)
	vmdatahread = 0x3A,
	//! @brief CGDATAREAD (CGRAM Data read)
	cgdataread = 0x3B,
	//! @brief OPHCT (Horizontal Scanline Location)
	ophct = 0x3C,
	//! @brief OPVCT (Vertical Scanline Location)
	opcvt = 0x3D,
	//! @brief STAT77 (PPU Status Flag and Version)
	stat77 = 0x3E,
	//! @brief STAT78 (PPU Status Flag and Version)
	stat78 = 0x3F
	};

	//! @brief The class containing all the registers the PPU
	class PPU : public Memory::IMemory {
	private:
	/*	struct _layerInfo {
			bool _characterSize;
		};

		struct {
			unsigned int height;
			unsigned int width;
			bool verticalMirroring;
			bool horizontalMirroring;
			int verticalOffset;
			int horizontalOffset;
			//! @brief A Character is the base unit of the background it can be 16x16 or 8x8 (16x8 under certain circumstances)
			unsigned char characterHeight;
			unsigned char characterWidth;
		} _BG[4];

		struct object {
			bool verticalMirroring;
			bool horizontalMirroring;
			bool priority;
			unsigned short graphicAddress;
		};

		//! @brief INIDISP variables (F-blank and Brightness)
		struct {
			bool _fBlank;
			//! @brief F=max, 0="off".
			unsigned short _brightness;
		} _inidisp;

		//! @brief OBSEL variables (Object Size and Character Address)
		struct {
			//! @brief "OamMode" this contains the size of the Objects (ex: 8x8 and 16x16)
			unsigned char _objectSize;
			//! @brief "OamBaseAddress"
			unsigned char _baseSelect;
			//! @brief "OamAddressOffset"
			unsigned char _nameSelect;
		} _obsel;

		//! @brief OAMADD variables (OAM Address and Obj Priority)
		struct {
			uint16_t _oamAddress;
			bool _objPriority;
		} _oamadd;

		//! @brief BGMODE (BG Mode and Character Size)
		struct {
			unsigned char _bgMode;
			bool _mode1Bg3Priority;
			_layerInfo layers[4];
		} _bgmode;*/




		//! @brief INIDISP Register (F-blank and Brightness)
		union {
			struct {
				uint8_t brightness: 4;
				bool _: 3;
				bool fblank: 1;
			};
			uint8_t raw;
		} _inidisp;
		//! @brief OBSEL Register (Object Size and Character Address)
		union {
			struct {
				uint8_t baseSelect: 3;
				bool nameSelect: 2;
				uint8_t objectSize: 3;
			};
			uint8_t raw;
		} _obsel;
		//! @brief OAMADD Register (OAM Address and Obj Priority)
		union {
			struct {
				uint32_t oamAddress: 9;
				uint8_t _: 6;
				bool objPriorityActivationBit: 1;
			};
			struct {
				uint8_t oamaddl;
				uint8_t oamaddh;
			};
			uint32_t raw;
		} _oamadd;
		//! @brief OAMDATA Register (Data for OAM write)
		uint8_t _oamdata;
		//! @brief BGMODE Register (OAM Address and Obj Priority)
		union {
			struct {
				uint8_t bgMode: 3;
				bool mode1Bg3PriorityBit: 1;
				bool characterSizeBg1: 1;
				bool characterSizeBg2: 1;
				bool characterSizeBg3: 1;
				bool characterSizeBg4: 1;
			};
			uint8_t raw;
		} _bgmode;
		//! @brief MOSAIC Register (Screen Pixelation)
		union {
			struct {
				bool affectBg1: 1;
				bool affectBg2: 1;
				bool affectBg3: 1;
				bool affectBg4: 1;
				uint8_t pixelSize: 4;
			};
			uint8_t raw;
		} _mosaic;
		//! @brief BGSC Registers (BG Tilemap Address and Size)
		union {
			struct {
				bool tilemapHorizontalMirroring: 1;
				bool tilemapVerticalMirroring: 1;
				uint8_t tilemapAddress: 6;
			};
			uint8_t raw;
		} _bgsc[4];
		//! @brief BGNBA Registers (BG1/2/3/4 Chr Address)
		union {
			struct {
				uint8_t baseAddressBg1a3: 4;
				uint8_t baseAddressBg2a4: 4;
			};
			uint8_t raw;
		} _bgnba[2];
		//! @brief BGXXOFS Register (BG1/2/3/4 Horizontal and Vertical Scrolls)
		union {
			struct {
				uint16_t offsetBg: 10;
				uint8_t _ : 6;
			};
			uint16_t raw;
		} _bgofs[8];
		//! @brief M7HOFS Register (Mode 7 BG Horizontal Scroll)
		//! @brief M7VOFS Register (Mode 7 BG Vertical Scroll)
		union {
			struct {
				uint16_t offsetBg : 13;
				uint8_t _ : 3;
			};
			uint8_t raw;
		} _m7ofs[2];
		//! @brief VMAIN Register (Video Port Control)
		union {
			struct {
				uint8_t incrementAmount: 2;
				uint8_t addressRemapping: 2;
				uint8_t _ : 3;
				bool incrementMode: 1;
			};
			uint8_t raw;
		} _vmain;
		//! @brief VMADD Register (VRAM Address)
		union {
			struct {
				uint8_t vmaddl;
				uint8_t vmaddh;
			};
			uint16_t vmadd;
		} _vmadd;
		//! @brief VMDATA Register (VRAM Data Write)
		union {
			struct {
				uint8_t vmdatal;
				uint8_t vmdatah;
			};
			uint16_t vmdata;
		} _vmdata;
		//! @brief TODO M7SEL Register (Mode 7 Settings)
		union {
			struct {
				bool horizontalMirroring: 1;
				bool verticalMirroring: 1;
				uint8_t _: 4;
				bool emptySpaceFill: 1;
				bool playingFieldSize: 1;
			};
			uint8_t raw;
		} _m7sel;
		//! M7A M7B M7C M7D i didn't understand how they works so they will be added later.

		// <to work>

		//! @brief M7X Register (Mode 7 Center X)
		union { // Not sure if it is done correctly
			struct {
				uint8_t _: 3;
				uint8_t value;
			};
			uint32_t center;
		} _m7x;
		//! @brief M7Y Register (Mode 7 Center Y)
		union { // Not sure if it is done correctly
			struct {
				uint8_t _: 3;
				uint8_t value;
			};
			uint32_t center;
		} _m7y;
		//! @brief CGADD Register (CGRAM Address)
		uint8_t _cgadd;
		//! @brief CGDATA Register (CGRAM Data write)
		union {
			struct {
				uint8_t red: 5;
				uint8_t green: 5;
				uint8_t blue: 5;
				bool _: 1;
			};
			struct {
				uint8_t cgdatal;
				uint8_t cgdatah;
			};
			uint16_t raw;
		} _cgdata;
		//! @brief This bool is used for writing either the low byte of the data (first call) or the high byte of the data (second call)
		//! @info This bool is set to True when writing to $2121 (CGADD)
		bool _isLowByte;
		//! @brief W12SEL - W34SEL Registers (Window Mask Settings for BGs) and WOBJSEL Register (Window Mask Settings for OBJ and Color Window)
		union {
			struct {
				bool enableWindow2ForBg2Bg4Color: 1;
				bool window2InversionForBg2Bg4Color: 1;
				bool enableWindow1ForBg2Bg4Color: 1;
				bool window1InversionForBg2Bg4Color: 1;
				bool enableWindow2ForBg1Bg3Obj: 1;
				bool window2InversionForBg1Bg3Obj: 1;
				bool enableWindow1ForBg1Bg2Obj: 1;
				bool window1InversionForBg1Bg2Obj: 1;
			};
			uint8_t raw;
		} _wsel[3];
		//! @brief WH0 Register (CWindow 1 Left Position)
		uint8_t _wh0;
		//! @brief WH1 Register (CWindow 1 Right Position)
		uint8_t _wh1;
		//! @brief WH2 Register (CWindow 2 Left Position)
		uint8_t _wh2;
		//! @brief WH3 Register (CWindow 2 Right Position)
		uint8_t _wh3;
		//! @brief WBGLOG Register (Window mask logic for BGs)
		union {
			struct {
				uint8_t maskLogicBg4: 2;
				uint8_t maskLogicBg3: 2;
				uint8_t maskLogicBg2: 2;
				uint8_t maskLogicBg1: 2;
			};
			uint8_t raw;
		} _wbglog;
		//! @brief WOBJLOG Register (Window mask logic for OBJs and Color Window)
		union {
			struct {
				uint8_t maskLogicObj: 2;
				uint8_t maskLogicColor: 2;
				uint8_t _: 4;
			};
			uint8_t raw;
		} _wobjlog;
		//! @brief TM - TS Registers (Main & Sub Screen Designation)
		union {
			struct {
				bool enableWindowDisplayBg1: 1;
				bool enableWindowDisplayBg2: 1;
				bool enableWindowDisplayBg3: 1;
				bool enableWindowDisplayBg4: 1;
				bool enableWindowDisplayObj: 1;
				uint8_t _: 3;
			};
			uint8_t raw;
		} _t[2];
		//! @brief TMW - TSW Registers (Window Mask Designation for the Main/Sub Screen)
		union {
			struct {
				bool enableWindowMaskingBg1: 1;
				bool enableWindowMaskingBg2: 1;
				bool enableWindowMaskingBg3: 1;
				bool enableWindowMaskingBg4: 1;
				bool enableWindowMaskingObj: 1;
				uint8_t _: 3;
			};
			uint8_t raw;
		} _tw[2];
		//! @brief TSW Register (Window Mask Designation for the Sub Screen)
		union {
			struct {
				uint8_t _: 3;
				bool enableWindowMaskingObj: 1;
				bool enableWindowMaskingBg4: 1;
				bool enableWindowMaskingBg3: 1;
				bool enableWindowMaskingBg2: 1;
				bool enableWindowMaskingBg1: 1;
			};
			uint8_t raw;
		} _tsw;
		//! @brief CGWSEL Register (Color Addition Select)
		union {
			struct {
				bool directColorMode: 1;
				bool addSubscreen: 1;
				uint8_t _: 2;
				uint8_t preventColorMath: 2;
				uint8_t clipColorToBlackBeforeMath: 2;
			};
			uint8_t raw;
		} _cgwsel;
		//! @brief CGADSUB Register (Color Math designation)
		union {
			struct {
				bool enableColorMathBg1: 1;
				bool enableColorMathBg2: 1;
				bool enableColorMathBg3: 1;
				bool enableColorMathBg4: 1;
				bool enableColorMathObj: 1;
				bool enableColorMathBackdrop: 1;
				bool halfColorMath: 1;
				bool addSubtractSelect: 1;
			};
			uint8_t raw;
		} _cgadsub;
		//! @brief COLDATA Register (Fixed Color Data)
		union {
			struct {
				uint8_t colorIntensity: 5;
				bool red: 1;
				bool green: 1;
				bool blue: 1;
			};
			uint8_t raw;
		} _coldata;
		//! @brief SETINI Register (Screen Mode/Video Select)
		union {
			struct {
				bool screenInterlace: 1;
				bool objInterlace: 1;
				bool overscanMode: 1;
				bool enablePseudoHiresMode: 1;
				uint8_t _: 2;
				bool mode7ExtBg: 1;
				bool externalSync: 1;
			};
			uint8_t raw;
		} _setini;

		// <READ registers> not in priority

		//! @brief MPYL - MPYM - MPYH Registers (Multiplication Result)
		union {
			struct {
				uint8_t mpyl;
				uint8_t mpym;
				uint8_t mpyh;
			};
			uint32_t  mpy;
		} mpy;
		Renderer::IRenderer &_renderer;
		std::shared_ptr<Memory::MemoryBus> _bus;
		uint16_t *_vram;
	public:
		PPU(const std::shared_ptr<Memory::MemoryBus> &bus, Renderer::IRenderer &renderer);
		PPU(const PPU &) = default;
		PPU &operator=(const PPU &) = delete;
		~PPU() = default;

		//! @brief Read data from the component.
		//! @param addr The local address to read from (0x0 should refer to the first byte of this component).
		//! @throw This function should thrown an InvalidAddress for address that are not mapped to the component.
		//! @return Return the data at the address given as parameter.
		uint8_t read(uint24_t addr) override;
		//! @brief Write data to this component.
		//! @param addr The local address to write data (0x0 should refer to the first byte of this component).
		//! @param data The new data to write.
		//! @throw This function should thrown an InvalidAddress for address that are not mapped to the component.
		void write(uint24_t addr, uint8_t data) override;
		//! @brief Update the PPU of n cycles.
		//! @param The number of cycles to update.
		void update(unsigned cycles);
		//! @brief Give the Vram Address with the right Address remapping
		uint8_t getVramAddress();
	};
}
#endif //COMSQUARE_PPU_HPP
