/*
	Copyright 2021 flyinghead

	This file is part of Flycast.

    Flycast is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 2 of the License, or
    (at your option) any later version.

    Flycast is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with Flycast.  If not, see <https://www.gnu.org/licenses/>.
*/
#pragma once
#include "types.h"
#include <array>
#include "hw/pvr/Renderer_if.h"
#include <d3d9.h>
#include "dxcontext.h"
#include "rend/transform_matrix.h"
#include "d3d_texture.h"
#include "d3d_shaders.h"
#include "ui/imgui_driver.h"
#include "rend/tileclip.h"

class RenderStateCache
{
	IDirect3DDevice9 *device = nullptr;
	std::array<DWORD, 210> renderState;
	std::array<DWORD, 14> sampler0State;
	IDirect3DVertexShader9 *vertexShader = nullptr;
	IDirect3DPixelShader9 *pixelShader = nullptr;
	IDirect3DBaseTexture9 *texture = nullptr;

public:
	void setDevice(IDirect3DDevice9 *device) {
		this->device = device;
		reset();
	}

	void reset() {
		renderState.fill(0xfefefefe);
		sampler0State.fill(0xfefefefe);
		vertexShader = nullptr;
		pixelShader = nullptr;
		texture = nullptr;
	}

	HRESULT SetRenderState(D3DRENDERSTATETYPE state, DWORD value)
	{
		if ((u32)state < renderState.size())
		{
			if (renderState[state] == value)
				return S_OK;
			renderState[state] = value;
		}
		return device->SetRenderState(state, value);
	}
	HRESULT SetSamplerState(DWORD sampler, D3DSAMPLERSTATETYPE type, DWORD value)
	{
		if (sampler == 0 && (u32)type < sampler0State.size())
		{
			if (sampler0State[type] == value)
				return S_OK;
			sampler0State[type] = value;
		}
		return device->SetSamplerState(sampler, type, value);
	}
	HRESULT SetVertexShader(IDirect3DVertexShader9 *pShader)
	{
		if (pShader == vertexShader)
			return S_OK;
		vertexShader = pShader;
		return device->SetVertexShader(pShader);
	}
	HRESULT SetPixelShader(IDirect3DPixelShader9 *pShader)
	{
		if (pShader == pixelShader)
			return S_OK;
		pixelShader = pShader;
		return device->SetPixelShader(pShader);
	}
	HRESULT SetTexture(DWORD stage, IDirect3DBaseTexture9 *pTexture)
	{
		if (stage == 0)
		{
			if (pTexture == texture)
				return S_OK;
			texture = pTexture;
		}
		return device->SetTexture(stage, pTexture);
	}
};

struct D3DRenderer : public Renderer
{
	bool Init() override;
	void Term() override;
	void Process(TA_context* ctx) override;
	bool Render() override;
	bool RenderLastFrame() override;
	bool Present() override
	{
		if (!frameRendered || clearLastFrame)
			return false;
		imguiDriver->setFrameRendered();
		frameRendered = false;
		return true;
	}
	BaseTextureCacheData *GetTexture(TSP tsp, TCW tcw, int area) override;
	void preReset();
	void postReset();
	void RenderFramebuffer(const FramebufferInfo& info) override;
	bool GetLastFrame(std::vector<u8>& data, int& width, int& height) override;

private:
	enum ModifierVolumeMode { Xor, Or, Inclusion, Exclusion, ModeCount };

	void resize(int w, int h);
	void drawStrips();
	template <u32 Type, bool SortingEnabled>
	void drawList(const std::vector<PolyParam>& gply, int first, int count);
	template <u32 Type, bool SortingEnabled>
	void setGPState(const PolyParam *gp);
	bool ensureVertexBufferSize(ComPtr<IDirect3DVertexBuffer9>& buffer, u32& currentSize, u32 minSize);
	bool ensureIndexBufferSize(ComPtr<IDirect3DIndexBuffer9>& buffer, u32& currentSize, u32 minSize);
	void updatePaletteTexture();
	void updateFogTexture();
	void displayFramebuffer();
	void drawSorted(int first, int count, bool multipass);
	void setMVS_Mode(ModifierVolumeMode mv_mode, ISP_Modvol ispc);
	void drawModVols(int first, int count);
	void setTexMode(D3DSAMPLERSTATETYPE state, u32 clamp, u32 mirror);
	void setBaseScissor();
	void prepareRttRenderTarget(u32 texAddress, int& vpWidth, int& vpHeight);
	void readRttRenderTarget(u32 texAddress);
	void writeFramebufferToVRAM();
	void drawOSD();
	TileClipping setTileClip(u32 tileclip, int rect[4]);

	RenderStateCache devCache;
	ComPtr<IDirect3DDevice9> device;
	ComPtr<IDirect3DVertexBuffer9> vertexBuffer;
	u32 vertexBufferSize = 0;
	ComPtr<IDirect3DVertexBuffer9> modvolBuffer;
	u32 modvolBufferSize = 0;
	ComPtr<IDirect3DIndexBuffer9> indexBuffer;
	u32 indexBufferSize = 0;
	ComPtr<IDirect3DVertexDeclaration9> mainVtxDecl;
	ComPtr<IDirect3DVertexDeclaration9> modVolVtxDecl;

	ComPtr<IDirect3DTexture9> framebufferTexture;
	ComPtr<IDirect3DSurface9> framebufferSurface;
	ComPtr<IDirect3DSurface9> backbuffer;
	ComPtr<IDirect3DTexture9> paletteTexture;
	ComPtr<IDirect3DTexture9> fogTexture;
	ComPtr<IDirect3DTexture9> dcfbTexture;
	ComPtr<IDirect3DSurface9> dcfbSurface;
	ComPtr<IDirect3DTexture9> rttTexture;
	ComPtr<IDirect3DSurface9> rttSurface;
	ComPtr<IDirect3DSurface9> depthSurface;
	ComPtr<IDirect3DTexture9> fbScaledTexture;
	ComPtr<IDirect3DSurface9> fbScaledSurface;

	u32 width = 0;
	u32 height = 0;
	TransformMatrix<COORD_DIRECTX> matrices;
	D3DTextureCache texCache;
	D3DShaders shaders;
	RECT scissorRect{};
	bool scissorEnable = false;
	bool frameRendered = false;
	bool frameRenderedOnce = false;
	int maxAnisotropy = 1;
	float aspectRatio = 4.f / 3.f;
	bool dithering = false;
};

