#pragma once

#include "Bildschirm.h"
#include "Ebene3D.h"
#include "Mat4.h"
#include "Vec2.h"

//! DirectX 12 Types

struct ID3D12Debug;
struct ID3D12Device;
struct ID3D12InfoQueue;
struct ID3D12CommandQueue;
struct IDXGISwapChain4;
struct ID3D12DescriptorHeap;
struct ID3D12Resource;
struct ID3D12CommandAllocator;
struct ID3D12GraphicsCommandList;
struct ID3D12Fence;
struct D3D12_VIEWPORT;
struct D3D12_VERTEX_BUFFER_VIEW;
struct D3D12_INDEX_BUFFER_VIEW;
struct ID3D12RootSignature;
struct ID3D12PipelineState;

//! DirectX 11 Types

struct ID3D11Device;
struct ID3D11DeviceContext;
struct IDXGISwapChain;
struct ID3D11Texture2D;
struct ID3D11SamplerState;
struct ID3D11ShaderResourceView;
struct ID3D11RenderTargetView;
struct ID3D11DepthStencilView;
struct ID3D11DepthStencilState;
struct ID3D11RasterizerState;
struct ID3D11BlendState;
struct D3D11_VIEWPORT;

//! DirectX 9 Types

struct IDirect3D9;
struct IDirect3DDevice9;
struct IDirect3DSurface9;
struct _D3DLOCKED_RECT;
struct tagRECT;

namespace Framework
{
    class WFenster;
    class Bild;
    class Textur;
    class DX11PixelShader;
    class DX11VertexShader;
    class TexturModel;
    class Render3D;
    class TexturList;
    class Kam3D;
    class Model3D;
    class DX11Buffer;
    class DX11StructuredBuffer;
    class DX12Buffer;
    class DX12DirectCommandQueue;
    class DX12CopyCommandQueue;
    class DX12ComputeCommandQueue;
    class DX12PixelShader;
    class DX12VertexShader;
    class DX12VertexBuffer;
    class DX12IndexBuffer;
    class Model3DList;
    class DXBuffer;
    class Model3DData;

    enum GraphicApiType;

    struct DiffuseLight
    {
        Vec3<float> direction;
        Vec3<float> color;
    };

    struct PointLight
    {
        Vec3<float> position;
        Vec3<float> color;
        float radius;
    };

    class GraphicsApi : public virtual ReferenceCounter
    {
    protected:
        GraphicApiType typ;
        WFenster* fenster;
        Vec2<int> backBufferSize;
        Model3DList* modelList;
        int nextModelId;
        Critical cs;

        bool fullScreen;

        DLLEXPORT virtual DXBuffer* createIndexBuffer() = 0;
        DLLEXPORT virtual DXBuffer* createVertexBuffer() = 0;

    public:
        DLLEXPORT GraphicsApi(GraphicApiType typ);
        DLLEXPORT virtual ~GraphicsApi();
        DLLEXPORT virtual void initialize(
            WFenster* fenster, Vec2<int> backBufferSize, bool fullScreen);
        DLLEXPORT virtual void setBackBufferSize(Vec2<int> size);
        DLLEXPORT virtual void setFullScreen(bool fullScreen);
        DLLEXPORT virtual void update() = 0;
        DLLEXPORT virtual void beginFrame(
            bool fill2D = 0, bool fill3D = 0, int fillColor = 0);
        DLLEXPORT virtual void renderKamera(Kam3D* zKamera);
        DLLEXPORT virtual void renderKamera(Kam3D* zKamera, Textur* zTarget);
        DLLEXPORT virtual void presentFrame() = 0;
        DLLEXPORT virtual Textur* createOrGetTextur(
            const char* name, Bild* b = 0);
        DLLEXPORT GraphicApiType getTyp() const;
        DLLEXPORT Vec2<int> getBackBufferSize() const;
        DLLEXPORT bool isFullScreen() const;
        DLLEXPORT virtual Bild* zUIRenderBild() const = 0;
        // returns the specified model without increased reference counter
        DLLEXPORT virtual Model3DData* zModel(const char* name);
        // returns the specified model with increased reference counter
        DLLEXPORT virtual Model3DData* getModel(const char* name);
        // creates a new empty Model3DData object if the model does not exist
        // yet
        DLLEXPORT virtual Model3DData* createModel(const char* name);
        // check if a model exists
        DLLEXPORT virtual bool hasModel(const char* name);
    };

    class DirectX9 : public GraphicsApi
    {
    private:
        IDirect3D9* pDirect3D;
        IDirect3DDevice9* pDevice;
        IDirect3DSurface9* pBackBuffer;
        _D3DLOCKED_RECT* backRect;
        Bild* uiBild;

        DLLEXPORT virtual DXBuffer* createIndexBuffer() override;
        DLLEXPORT virtual DXBuffer* createVertexBuffer() override;

    public:
        DLLEXPORT DirectX9();
        DLLEXPORT ~DirectX9();
        DLLEXPORT void initialize(WFenster* fenster,
            Vec2<int> backBufferSize,
            bool fullScreen) override;
        DLLEXPORT void update() override;
        DLLEXPORT void beginFrame(
            bool fill2D, bool fill3D, int fillColor) override;
        DLLEXPORT void renderKamera(Kam3D* zKamera) override;
        DLLEXPORT void presentFrame() override;
        DLLEXPORT Textur* createOrGetTextur(const char* name, Bild* b) override;
        DLLEXPORT Bild* zUIRenderBild() const override;
    };

    class DirectX11 : public GraphicsApi
    {
    private:
        ID3D11Device* d3d11Device;
        ID3D11DeviceContext* d3d11Context;
        IDXGISwapChain* d3d11SpawChain;
        Textur* uiTextur;
        DX11VertexShader* vertexShader;
        DX11PixelShader* pixelShader;
        ID3D11SamplerState* sampleState;
        ID3D11RenderTargetView* rtview;
        ID3D11DepthStencilView* dsView;
        ID3D11Texture2D* depthStencilBuffer;
        ID3D11DepthStencilState* depthStencilState;
        ID3D11DepthStencilState* depthDisabledStencilState;
        ID3D11BlendState* blendStateAlphaBlend;
        D3D11_VIEWPORT* vp;
        TexturModel* texturModel;
        TexturList* texturRegister;
        ID3D11RasterizerState* texturRS;
        ID3D11RasterizerState* meshRS;
        Textur* defaultTextur;
        DX11StructuredBuffer* diffuseLights;
        DX11StructuredBuffer* pointLights;
        Mat4<float> matrixBuffer[MAX_KNOCHEN_ANZ];
        Mat4<float> viewAndProj[2];
        Vec3<float> kamPos;
        Ebene3D<float> frustrum[6];
        int lastModelId = -1;

        void renderObject(Model3D* zObj);
        //! Überprüft, ob eine Kugel in dem Sichtbaren Raum der Welt liegt und
        //! gezeichnet werden muss \param pos Der Mittelpunkt der Kugel \param
        //! radius Der Radius der Kugel \param dist Einen Zeiger auf einen
        //! float, in dem das quadrat des Abstands zur Kammeraposition
        //! gespeichert wird, falls diese Funktion true zurückgiebt und der
        //! Zeiger nicht 0 ist
        bool isInFrustrum(
            const Vec3<float>& pos, float radius, float* dist = 0) const;
        DLLEXPORT virtual DXBuffer* createIndexBuffer() override;
        DLLEXPORT virtual DXBuffer* createVertexBuffer() override;

    protected:
        DLLEXPORT virtual DX11VertexShader* initializeVertexShader(
            unsigned char* byteCode, int size);
        DLLEXPORT virtual DX11PixelShader* initializePixelShader(
            unsigned char* byteCode, int size);

    public:
        DLLEXPORT DirectX11();
        DLLEXPORT ~DirectX11();
        DLLEXPORT void initialize(WFenster* fenster,
            Vec2<int> backBufferSize,
            bool fullScreen) override;
        DLLEXPORT void update() override;
        DLLEXPORT void beginFrame(
            bool fill2D, bool fill3D, int fillColor) override;
        DLLEXPORT void renderKamera(Kam3D* zKamera) override;
        DLLEXPORT void renderKamera(Kam3D* zKamera, Textur* zTarget) override;
        DLLEXPORT void presentFrame() override;
        DLLEXPORT Textur* createOrGetTextur(const char* name, Bild* b) override;
        DLLEXPORT Bild* zUIRenderBild() const override;

        DLLEXPORT static bool isAvailable();
    };

    class DirectX12 : public GraphicsApi
    {
    private:
        ID3D12Debug* debug;
        ID3D12Device* device;
        ID3D12InfoQueue* infoQueue;
        DX12DirectCommandQueue* directCommandQueue;
        DX12CopyCommandQueue* copyCommandQueue;
        DX12ComputeCommandQueue* computeCommandQueue;
        IDXGISwapChain4* swapChain;
        ID3D12DescriptorHeap* rtvHeap;
        ID3D12DescriptorHeap* dsvHeap;
        ID3D12DescriptorHeap* shaderBufferHeap;
        ID3D12Resource* depthBuffer;
        ID3D12Resource* backBuffer[2];
        int backBufferIndex;
        int tearing;
        D3D12_VIEWPORT* viewPort;
        tagRECT* allowedRenderArea;
        D3D12_VERTEX_BUFFER_VIEW* vertexBufferView;
        D3D12_INDEX_BUFFER_VIEW* indexBufferView;
        ID3D12RootSignature* signature;
        ID3D12PipelineState* pipeline;
        Mat4<float> matrixBuffer[MAX_KNOCHEN_ANZ];
        Mat4<float> viewAndProj[2];
        Vec3<float> kamPos;
        Ebene3D<float> frustrum[6];
        TexturModel* texturModel;
        Textur* uiTextur;
        TexturList* texturRegister;
        DX12VertexShader* vertexShader;
        DX12PixelShader* pixelShader;

        void renderObject(Model3D* zObj);
        //! Überprüft, ob eine Kugel in dem Sichtbaren Raum der Welt liegt und
        //! gezeichnet werden muss \param pos Der Mittelpunkt der Kugel \param
        //! radius Der Radius der Kugel \param dist Einen Zeiger auf einen
        //! float, in dem das quadrat des Abstands zur Kammeraposition
        //! gespeichert wird, falls diese Funktion true zurückgiebt und der
        //! Zeiger nicht 0 ist
        bool isInFrustrum(
            const Vec3<float>& pos, float radius, float* dist = 0) const;
        DLLEXPORT virtual DXBuffer* createIndexBuffer() override;
        DLLEXPORT virtual DXBuffer* createVertexBuffer() override;

    public:
        DLLEXPORT DirectX12();
        DLLEXPORT ~DirectX12();
        DLLEXPORT void initialize(WFenster* fenster,
            Vec2<int> backBufferSize,
            bool fullScreen) override;
        DLLEXPORT void update() override;
        DLLEXPORT void beginFrame(
            bool fill2D, bool fill3D, int fillColor) override;
        DLLEXPORT void renderKamera(Kam3D* zKamera) override;
        // TODO: DLLEXPORT void renderKamera( Kam3D* zKamera, Textur* zTarget )
        // override;
        DLLEXPORT void presentFrame() override;
        DLLEXPORT Textur* createOrGetTextur(const char* name, Bild* b) override;
        DLLEXPORT Bild* zUIRenderBild() const override;

        DLLEXPORT static bool isAvailable();
    };
} // namespace Framework