// Flipper GFX Engine

/*

Very limited Flipper GFXEngine emulation. Basic OpenGL 1.2 is used as a backend.

This code is out-of-phase because the graphical subsystem has been substantially redesigned. So there is now a mixture of new developments and old code.

What's supported:
- Software implementation of transform unit (XF). Very inaccurate - lighting is partially supported. The transformation of texture coordinates is not completed yet.
- All texture formats are supported, but TLUT versions may be buggy

What's not supported:
- The main drawback is the lack of TEV support. Because of this, complex scenes with effects are drawn with bugs or not drawn at all.
- Emulation of Advanced GX features such as Bump-mapping, indirect texturing, Z-textures not supported
- No texture caching
- There is no emulation of direct access to the EFB

*/

#pragma once

#include "cp.h"
#include "pe.h"
#include "xf.h"
#include "su.h"
#include "ras.h"
#include "tev.h"
#include "tx.h"

#define GFX_BLACKJACK_AND_SHADERS 0			// 1: Use modern OpenGL (VBO + Shaders). Under development, do not enable

// 1: Use SDL_Window as a render target; the appropriate SDL API calls are invoked to service it
#ifdef _LINUX
#define GFX_USE_SDL_WINDOW 1
#endif
#if defined(_WINDOWS) && !defined(GFX_USE_SDL_WINDOW)
#define GFX_USE_SDL_WINDOW 0
#endif

namespace GX
{
	class GXCore
	{
		void DONE_INT();
		void TOKEN_INT();
		void CP_BREAK();
		void CP_OVF();
		void CP_UVF();

		static void CPThread(void* Param);

		FifoProcessor * fifo = nullptr;	// Internal CP FIFO

		bool gxOpened = false;
		bool frame_done = true;
		bool disableDraw = false;
		bool frameReady = false;
		bool backend_started = false;

		// logging
		bool logOpcode = false;
		bool logDrawCommands = false;
		bool GpRegsLog = false;

#if GFX_USE_SDL_WINDOW
		SDL_Window* render_window = nullptr;
		SDL_GLContext context{};
#else
		// Windows OpenGL stuff
		HWND hwndMain;
		HGLRC hglrc = 0;
		HDC hdcgl = 0;
		PAINTSTRUCT psFrame{};
#endif

		bool make_shot = false;
		FILE* snap_file = nullptr;
		uint32_t snap_w, snap_h;

		// optionable
		uint32_t scr_w = 640, scr_h = 480;

	public:
		GXCore();
		~GXCore();

		void Open(HWConfig* config);
		void Close();

		bool GL_LazyOpenSubsystem();
		bool GL_OpenSubsystem();
		void GL_CloseSubsystem();
		void GL_BeginFrame();
		void GL_EndFrame();
		void GPFrameDone();

		void ResizeRenderTarget(size_t width, size_t height);

		// Debug
		void DumpPIFIFO();
		void DumpCPFIFO();
		
		GLuint vert_shader;
		GLuint frag_shader;
		GLuint shader_prog;

		void UploadShaders(const char* vert_source, const char* frag_source);
		void DisposeShaders();
		void InitVBO();
		void DisposeVBO();
		void BindShadersWithVBO();

		// You probably don't need to reset the internal state of GFX because GXInit from Dolphin SDK is working hard on it

#pragma region "Interface to Flipper"

		// PI FIFO
		volatile uint32_t pi_cp_base;
		volatile uint32_t pi_cp_top;
		volatile uint32_t pi_cp_wrptr;          // also WRAP bit
		volatile uint32_t wrap_bit = 0;		// When the CPU writes to the CPWRT register, this bit is cleared. When the FIFO writes by the CPWRT address, this bit remains asserted

		// CP Registers
		uint16_t CpReadReg(CPMappedRegister id);
		void CpWriteReg(CPMappedRegister id, uint16_t value);

		// PI->CP Registers
		uint32_t PiCpReadReg(PI_CPMappedRegister id);
		void PiCpWriteReg(PI_CPMappedRegister id, uint32_t value);

		// Streaming FIFO (32-byte burst-only)
		void FifoWriteBurst(uint8_t data[32]);

#pragma endregion "Interface to Flipper"


#pragma region "Gfx Common"

		GenMode genmode;
		GenMsloc msloc[4];

#pragma endregion "Gfx Common"


#pragma region "Command Processor"

		CPHostRegs cpregs;		// Mapped command processor registers
		CPState cp;				// Internal registers (for setting VCD/VAT, etc.)

		Thread* cp_thread;     // CP FIFO thread
		size_t	tickPerFifo;
		int64_t	updateTbrValue;

		// Stats
		size_t cpLoads;
		size_t xfLoads;
		size_t bpLoads;

		GLuint vao;
		GLuint vbo;
		size_t vbo_size = 0x10000;		// Maximum number of vertices that can be used in Draw primitive parameters
		Vertex* vertex_data = nullptr;

		void GXWriteFifo(uint8_t dataPtr[32]);
		void loadCPReg(size_t index, uint32_t value, FifoProcessor* gxfifo);
		std::string AttrToString(VertexAttr attr);
		int gx_vtxsize(unsigned v);
		void FifoReconfigure(FifoProcessor* gxfifo);
		void* GetArrayPtr(ArrayId arrayId, int idx, int compSize);
		void FetchComp(float* comp, int count, int type, int fmt, int shft, FifoProcessor* gxfifo, ArrayId arrayId);
		void FetchNorm(float* comp, int count, int type, int fmt, int shft, FifoProcessor* gxfifo, ArrayId arrayId, bool nrmidx3);
		Color FetchColor(int type, int fmt, FifoProcessor* gxfifo, ArrayId arrayId);
		void FifoWalk(unsigned vatnum, Vertex* vtx, FifoProcessor* gxfifo);
		void GxBadFifo(uint8_t command);
		void GxCommand(FifoProcessor* gxfifo);
		void CPAbortFifo();

#pragma endregion "Command Processor"


#pragma region "Transform Unit"

		XFState xf;

		TexGenOut tgout[8]{};
		Color colora[2]{};	// lighting stage output colors (COLOR0A0 / COLOR1A1)

		bool XF_LightColorEnabled(int chan, int light);
		bool XF_LightAlphaEnabled(int chan, int light);
		void XF_DoLights(const Vertex* v);
		void XF_DoTexGen(const Vertex* v);
		void VECNormalize(float vec[3]);
		void XF_ApplyModelview(const Vertex* v, float* out, const float* in);
		void NormalTransform(const Vertex* v, float* out, const float* in);
		void GL_SetProjection(float* mtx);
		void GL_SetViewport(int x, int y, int w, int h, float znear, float zfar);
		void loadXFRegs(size_t startIdx, size_t amount, FifoProcessor* gxfifo);

#pragma endregion "Transform Unit"


#pragma region "Setup Unit"

		SU_SCIS0 scis0;		// 0x20
		SU_SCIS1 scis1;		// 0x21
		SU_TS0 ssize[8];	// 0x3n
		SU_TS1 tsize[8];	// 0x3n

		void GL_SetScissor(int x, int y, int w, int h);
		void GL_SetCullMode(int mode);
		void loadBPReg(size_t index, uint32_t value);

#pragma endregion "Setup Unit"


#pragma region "Rasterizers"

		// perfomance counters
		size_t tris = 0, pts = 0, lines = 0;

		bool ras_wireframe = false;			// Enable wireframe drawing of primitives (DEBUG)
		bool ras_use_texture = false;

		void RAS_Begin(RAS_Primitive prim, size_t vtx_num);
		void RAS_End();
		void RAS_SendVertex(const Vertex* v);

#pragma endregion "Rasterizers"


#pragma region "Texture Engine"

		LoadTlut0 loadtlut0;		// 0x64
		LoadTlut1 loadtlut1;		// 0x65
		TexMode0 texmode0[8];		// 0x80-0x83, 0xA0-0xA3
		TexMode1 texmode1[8];		// 0x84-0x87, 0xA4-0xA7
		TexImage0 teximg0[8];		// 0x88-0x8B, 0xA8-0xAB
		TexImage1 teximg1[8];		// 0x8C-0x8F, 0xAC-0xAF
		TexImage2 teximg2[8];		// 0x90-0x93, 0xB0-0xB3
		TexImage3 teximg3[8];		// 0x94-0x97, 0xB4-0xB7
		SetTlut settlut[8];			// 0x98-0x9B, 0xB8-0xBB
		bool texvalid[4][8];

		#define GFX_MAX_TEXTURES 32

		TexEntry* tID[8];
		Color rgbabuf[1024 * 1024];
		TexEntry tcache[GFX_MAX_TEXTURES];
		unsigned tptr;
		GLuint texlist[GFX_MAX_TEXTURES + 1];     // gl texture list (0 entry reserved)
		uint8_t tlut[1024 * 1024];  // temporary TLUT buffer

		void TexInit();
		void TexFree();
		void DumpTexture(Color* rgbaBuf, uint32_t addr, int fmt, int width, int height);
		void tryLoadTex(int id);
		void GetTlutCol(Color* c, unsigned id, unsigned entry);
		void RebindTexture(unsigned id);
		void LoadTexture(uint32_t addr, int id, int fmt, int width, int height);
		void LoadTlut(uint32_t addr, uint32_t tmem, uint32_t cnt);

#pragma endregion "Texture Engine"


#pragma region "TEV"

		TEVState tev{};

		// TBD: Konst regs where to?

#pragma endregion "TEV"


#pragma region "Pixel Engine"

		size_t frames = 0;
		size_t pe_done_num = 0;   // number of drawdone (PE_FINISH) events

		PERegs peregs;		// PE PI regs

		PEState pe{};		// Internal PE state

		void GL_DoSnapshot(bool sel, FILE* f, uint8_t* dst, int width, int height);
		void GL_MakeSnapshot(char* path);
		void GL_SaveBitmap(uint8_t* buf);

		// Pixel Engine mapped regs
		uint16_t PeReadReg(PEMappedRegister id);
		void PeWriteReg(PEMappedRegister id, uint16_t value);
		uint32_t EfbPeek(uint32_t addr);
		void EfbPoke(uint32_t addr, uint32_t value);

#pragma endregion "Pixel Engine"

	};
}
