#ifndef __GL_SWFRAMEBUFFER
#define __GL_SWFRAMEBUFFER

#ifdef _WIN32
#include "win32iface.h"
#include "win32gliface.h"
#endif

#include "SkylineBinPack.h"

#include <memory>

class FGLDebug;

#ifdef _WIN32
class OpenGLSWFrameBuffer : public Win32GLFrameBuffer
{
	typedef Win32GLFrameBuffer Super;
	DECLARE_CLASS(OpenGLSWFrameBuffer, Win32GLFrameBuffer)
#else
#include "sdlglvideo.h"
class OpenGLSWFrameBuffer : public SDLGLFB
{
//	typedef SDLGLFB Super;	//[C]commented, DECLARE_CLASS defines this in linux
	DECLARE_CLASS(OpenGLSWFrameBuffer, SDLGLFB)
#endif


public:

	explicit OpenGLSWFrameBuffer() {}
	OpenGLSWFrameBuffer(void *hMonitor, int width, int height, int bits, int refreshHz, bool fullscreen, bool bgra);
	~OpenGLSWFrameBuffer();


	bool IsValid() override;
	bool Lock(bool buffered) override;
	void Unlock() override;
	void Update() override;
	PalEntry *GetPalette() override;
	void GetFlashedPalette(PalEntry palette[256]) override;
	void UpdatePalette() override;
	bool SetGamma(float gamma) override;
	bool SetFlash(PalEntry rgb, int amount) override;
	void GetFlash(PalEntry &rgb, int &amount) override;
	int GetPageCount() override;
	void SetVSync(bool vsync) override;
	void NewRefreshRate() override;
	void GetScreenshotBuffer(const uint8_t *&buffer, int &pitch, ESSType &color_type) override;
	void ReleaseScreenshotBuffer() override;
	void SetBlendingRect(int x1, int y1, int x2, int y2) override;
	bool Begin2D(bool copy3d) override;
	void DrawBlendingRect() override;
	FNativeTexture *CreateTexture(FTexture *gametex, bool wrapping) override;
	FNativePalette *CreatePalette(FRemapTable *remap) override;
	void DrawTextureParms(FTexture *img, DrawParms &parms) override;
	void Clear(int left, int top, int right, int bottom, int palcolor, uint32 color) override;
	void Dim(PalEntry color, float amount, int x1, int y1, int w, int h) override;
	void FlatFill(int left, int top, int right, int bottom, FTexture *src, bool local_origin) override;
	void DrawLine(int x0, int y0, int x1, int y1, int palColor, uint32 realcolor) override;
	void DrawPixel(int x, int y, int palcolor, uint32 rgbcolor) override;
	void FillSimplePoly(FTexture *tex, FVector2 *points, int npoints, double originx, double originy, double scalex, double scaley, DAngle rotation, FDynamicColormap *colormap, int lightlevel, int bottomclip) override;
	bool WipeStartScreen(int type) override;
	void WipeEndScreen() override;
	bool WipeDo(int ticks) override;
	void WipeCleanup() override;

#ifdef WIN32
	void PaletteChanged() override { }
	int QueryNewPalette() override { return 0; }
	void Blank() override { }
	bool PaintToWindow() override;
	bool Is8BitMode() override { return false; }
	int GetTrueHeight() override { return TrueHeight; }
#endif

private:
	struct FBVERTEX
	{
		float x, y, z, rhw;
		uint32_t color0, color1;
		float tu, tv;
	};

	struct BURNVERTEX
	{
		float x, y, z, rhw;
		float tu0, tv0;
		float tu1, tv1;
	};

	enum
	{
		PSCONST_Desaturation,
		PSCONST_PaletteMod,
		PSCONST_Weights,
		PSCONST_Gamma,
		PSCONST_ScreenSize,
		NumPSCONST
	};

	struct GammaRamp
	{
		uint16_t red[256], green[256], blue[256];
	};

	struct LTRBRect
	{
		int left, top, right, bottom;
	};

	class HWTexture
	{
	public:
		HWTexture() { Buffers[0] = 0; Buffers[1] = 0; }
		~HWTexture();

		int Texture = 0;
		int Buffers[2];
		int CurrentBuffer = 0;
		int WrapS = 0;
		int WrapT = 0;
		int Format = 0;
	};

	class HWFrameBuffer
	{
	public:
		~HWFrameBuffer();

		int Framebuffer = 0;
		HWTexture *Texture = nullptr;
	};


	class HWVertexBuffer
	{
	public:
		~HWVertexBuffer();

		FBVERTEX *Lock();
		void Unlock();

		int VertexArray = 0;
		int Buffer = 0;
		int Size = 0;
	};

	class HWIndexBuffer
	{
	public:
		~HWIndexBuffer();

		uint16_t *Lock();
		void Unlock();

		int Buffer = 0;
		int Size = 0;

	private:
		int LockedOldBinding = 0;
	};

	class HWPixelShader
	{
	public:
		~HWPixelShader();

		int Program = 0;
		int VertexShader = 0;
		int FragmentShader = 0;

		int ConstantLocations[NumPSCONST];
		int ImageLocation = -1;
		int PaletteLocation = -1;
		int NewScreenLocation = -1;
		int BurnLocation = -1;
	};

	bool CreateFrameBuffer(const FString &name, int width, int height, HWFrameBuffer **outFramebuffer);
	bool CreatePixelShader(FString vertexsrc, FString fragmentsrc, const FString &defines, HWPixelShader **outShader);
	bool CreateVertexBuffer(int size, HWVertexBuffer **outVertexBuffer);
	bool CreateIndexBuffer(int size, HWIndexBuffer **outIndexBuffer);
	bool CreateTexture(const FString &name, int width, int height, int levels, int format, HWTexture **outTexture);
	void SetGammaRamp(const GammaRamp *ramp);
	void SetPixelShaderConstantF(int uniformIndex, const float *data, int vec4fcount);
	void SetHWPixelShader(HWPixelShader *shader);
	void SetStreamSource(HWVertexBuffer *vertexBuffer);
	void SetIndices(HWIndexBuffer *indexBuffer);
	void DrawTriangleFans(int count, const FBVERTEX *vertices);
	void DrawTriangleFans(int count, const BURNVERTEX *vertices);
	void DrawPoints(int count, const FBVERTEX *vertices);
	void DrawLineList(int count);
	void DrawTriangleList(int minIndex, int numVertices, int startIndex, int primitiveCount);
	void Present();

	void BindFBBuffer();
	void *MappedMemBuffer = nullptr;
	bool UseMappedMemBuffer = true;

	static uint32_t ColorARGB(uint32_t a, uint32_t r, uint32_t g, uint32_t b) { return ((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | ((b) & 0xff); }
	static uint32_t ColorRGBA(uint32_t r, uint32_t g, uint32_t b, uint32_t a) { return ColorARGB(a, r, g, b); }
	static uint32_t ColorXRGB(uint32_t r, uint32_t g, uint32_t b) { return ColorARGB(0xff, r, g, b); }
	static uint32_t ColorValue(float r, float g, float b, float a) { return ColorRGBA((uint32_t)(r * 255.0f), (uint32_t)(g * 255.0f), (uint32_t)(b * 255.0f), (uint32_t)(a * 255.0f)); }
	
	static void *MapBuffer(int target, int size);

	// The number of points for the vertex buffer.
	enum { NUM_VERTS = 10240 };

	// The number of indices for the index buffer.
	enum { NUM_INDEXES = ((NUM_VERTS * 6) / 4) };

	// The number of quads we can batch together.
	enum { MAX_QUAD_BATCH = (NUM_INDEXES / 6) };

	// The default size for a texture atlas.
	enum { DEF_ATLAS_WIDTH = 512 };
	enum { DEF_ATLAS_HEIGHT = 512 };

	// TYPES -------------------------------------------------------------------

	struct Atlas;

	struct PackedTexture
	{
		Atlas *Owner;

		PackedTexture **Prev, *Next;

		// Pixels this image covers
		LTRBRect Area;

		// Texture coordinates for this image
		float Left, Top, Right, Bottom;

		// Texture has extra space on the border?
		bool Padded;
	};

	struct Atlas
	{
		Atlas(OpenGLSWFrameBuffer *fb, int width, int height, int format);
		~Atlas();

		PackedTexture *AllocateImage(const Rect &rect, bool padded);
		void FreeBox(PackedTexture *box);

		SkylineBinPack Packer;
		Atlas *Next;
		HWTexture *Tex;
		int Format;
		PackedTexture *UsedList;	// Boxes that contain images
		int Width, Height;
		bool OneUse;
	};

	class OpenGLTex : public FNativeTexture
	{
	public:
		OpenGLTex(FTexture *tex, OpenGLSWFrameBuffer *fb, bool wrapping);
		~OpenGLTex();

		FTexture *GameTex;
		PackedTexture *Box;

		OpenGLTex **Prev;
		OpenGLTex *Next;

		bool IsGray;

		bool Create(OpenGLSWFrameBuffer *fb, bool wrapping);
		bool Update();
		bool CheckWrapping(bool wrapping);
		int GetTexFormat();
		FTextureFormat ToTexFmt(int fmt);
	};

	class OpenGLPal : public FNativePalette
	{
	public:
		OpenGLPal(FRemapTable *remap, OpenGLSWFrameBuffer *fb);
		~OpenGLPal();

		OpenGLPal **Prev;
		OpenGLPal *Next;

		HWTexture *Tex;
		uint32_t BorderColor;
		bool DoColorSkip;

		bool Update();

		FRemapTable *Remap;
		int RoundedPaletteSize;
	};

	// Flags for a buffered quad
	enum
	{
		BQF_GamePalette = 1,
		BQF_CustomPalette = 7,
		BQF_Paletted = 7,
		BQF_Bilinear = 8,
		BQF_WrapUV = 16,
		BQF_InvertSource = 32,
		BQF_DisableAlphaTest = 64,
		BQF_Desaturated = 128,
	};

	// Shaders for a buffered quad
	enum
	{
		BQS_PalTex,
		BQS_Plain,
		BQS_RedToAlpha,
		BQS_ColorOnly,
		BQS_SpecialColormap,
		BQS_InGameColormap,
	};

	struct BufferedTris
	{
		uint8_t Flags;
		uint8_t ShaderNum;
		int BlendOp;
		int SrcBlend;
		int DestBlend;

		uint8_t Desat;
		OpenGLPal *Palette;
		HWTexture *Texture;
		uint16_t NumVerts;		// Number of _unique_ vertices used by this set.
		uint16_t NumTris;		// Number of triangles used by this set.

		void ClearSetup()
		{
			Flags = 0;
			ShaderNum = 0;
			BlendOp = 0;
			SrcBlend = 0;
			DestBlend = 0;
		}

		bool IsSameSetup(const BufferedTris &other) const
		{
			return Flags == other.Flags && ShaderNum == other.ShaderNum && BlendOp == other.BlendOp && SrcBlend == other.SrcBlend && DestBlend == other.DestBlend;
		}
	};

	enum
	{
		SHADER_NormalColor,
		SHADER_NormalColorPal,
		SHADER_NormalColorInv,
		SHADER_NormalColorPalInv,

		SHADER_RedToAlpha,
		SHADER_RedToAlphaInv,

		SHADER_VertexColor,

		SHADER_SpecialColormap,
		SHADER_SpecialColormapPal,

		SHADER_InGameColormap,
		SHADER_InGameColormapDesat,
		SHADER_InGameColormapInv,
		SHADER_InGameColormapInvDesat,
		SHADER_InGameColormapPal,
		SHADER_InGameColormapPalDesat,
		SHADER_InGameColormapPalInv,
		SHADER_InGameColormapPalInvDesat,

		SHADER_BurnWipe,
		SHADER_GammaCorrection,

		NUM_SHADERS
	};
	static const char *const ShaderDefines[NUM_SHADERS];

	void Flip();
	void SetInitialState();
	bool CreateResources();
	void ReleaseResources();
	bool LoadShaders();
	bool CreateFBTexture();
	bool CreatePaletteTexture();
	bool CreateVertexes();
	void UploadPalette();
	void CalcFullscreenCoords(FBVERTEX verts[4], bool viewarea_only, uint32_t color0, uint32_t color1) const;
	bool Reset();
	HWTexture *CopyCurrentScreen();
	void ReleaseDefaultPoolItems();
	void KillNativePals();
	void KillNativeTexs();
	PackedTexture *AllocPackedTexture(int width, int height, bool wrapping, int format);
	void DrawPackedTextures(int packnum);
	void DrawLetterbox(int x, int y, int width, int height);
	void Draw3DPart(bool copy3d);
	bool SetStyle(OpenGLTex *tex, DrawParms &parms, uint32_t &color0, uint32_t &color1, BufferedTris &quad);
	static int GetStyleAlpha(int type);
	static void SetColorOverlay(uint32_t color, float alpha, uint32_t &color0, uint32_t &color1);
	void AddColorOnlyQuad(int left, int top, int width, int height, uint32_t color);
	void AddColorOnlyRect(int left, int top, int width, int height, uint32_t color);
	void CheckQuadBatch(int numtris = 2, int numverts = 4);
	void BeginQuadBatch();
	void EndQuadBatch();
	void BeginLineBatch();
	void EndLineBatch();
	void EndBatch();

	// State
	void EnableAlphaTest(bool enabled);
	void SetAlphaBlend(int op, int srcblend = 0, int destblend = 0);
	void SetConstant(int cnum, float r, float g, float b, float a);
	void SetPixelShader(HWPixelShader *shader);
	void SetTexture(int tnum, HWTexture *texture);
	void SetSamplerWrapS(int tnum, int mode);
	void SetSamplerWrapT(int tnum, int mode);
	void SetPaletteTexture(HWTexture *texture, int count, uint32_t border_color);

	template<typename T> static void SafeRelease(T &x) { if (x != nullptr) { delete x; x = nullptr; } }

	std::shared_ptr<FGLDebug> Debug;

	std::unique_ptr<HWVertexBuffer> StreamVertexBuffer, StreamVertexBufferBurn;
	float ShaderConstants[NumPSCONST * 4];
	HWPixelShader *CurrentShader = nullptr;

	HWFrameBuffer *OutputFB = nullptr;

	bool AlphaTestEnabled = false;
	bool AlphaBlendEnabled = false;
	int AlphaBlendOp = 0;
	int AlphaSrcBlend = 0;
	int AlphaDestBlend = 0;
	float Constant[3][4];
	uint32_t CurBorderColor;
	HWPixelShader *CurPixelShader;
	HWTexture *Texture[5];
	int SamplerWrapS[5], SamplerWrapT[5];

	PalEntry SourcePalette[256];
	uint32_t BorderColor;
	uint32_t FlashColor0, FlashColor1;
	PalEntry FlashColor;
	int FlashAmount;
	int TrueHeight;
	int PixelDoubling;
	float Gamma;
	bool UpdatePending;
	bool NeedPalUpdate;
	bool NeedGammaUpdate;
	bool VSync;
	LTRBRect BlendingRect;
	int In2D;
	bool InScene;
	bool GatheringWipeScreen;
	bool AALines;
	uint8_t BlockNum;
	OpenGLPal *Palettes = nullptr;
	OpenGLTex *Textures = nullptr;
	Atlas *Atlases = nullptr;

	HWTexture *FBTexture = nullptr;
	HWTexture *PaletteTexture = nullptr;
	HWTexture *ScreenshotTexture = nullptr;

	HWVertexBuffer *VertexBuffer = nullptr;
	FBVERTEX *VertexData = nullptr;
	HWIndexBuffer *IndexBuffer = nullptr;
	uint16_t *IndexData = nullptr;
	BufferedTris *QuadExtra = nullptr;
	int VertexPos;
	int IndexPos;
	int QuadBatchPos;
	enum { BATCH_None, BATCH_Quads, BATCH_Lines } BatchType;

	HWPixelShader *Shaders[NUM_SHADERS];

	HWTexture *InitialWipeScreen = nullptr, *FinalWipeScreen = nullptr;

	class Wiper
	{
	public:
		virtual ~Wiper();
		virtual bool Run(int ticks, OpenGLSWFrameBuffer *fb) = 0;

		void DrawScreen(OpenGLSWFrameBuffer *fb, HWTexture *tex, int blendop = 0, uint32_t color0 = 0, uint32_t color1 = 0xFFFFFFF);
	};

	class Wiper_Melt;			friend class Wiper_Melt;
	class Wiper_Burn;			friend class Wiper_Burn;
	class Wiper_Crossfade;		friend class Wiper_Crossfade;

	Wiper *ScreenWipe;
};


#endif //__GL_SWFRAMEBUFFER
