#include "ceedee.h"
#include "vec.hpp"
#include "SpinShaderVarients.h"
#include "CdTex.hpp"

#include <imgui.h>
#include <d3d11.h>
#include <directxmath.h>
#include <d3dcompiler.h>

#include "D3DSpin.hpp"

namespace Li::Gui::DvdSpin {



    // dvd spin



    void D3DSpin::setupSpinningDVD() {
        this->createCdVertexShader();
        this->createCdVertexLayout();
        this->setCdVertexLayout();
        this->createCdPixelShader();

        this->createCdVertexBuffer();
        this->setCdVertexBuffer();
        this->createCdIndexBuffer();
        this->setCdIndexBuffer();
        this->createCamBuffer();
        this->updateCamBuffer();

        this->createCdTexture();
        this->createCdResourceView();
        this->createCdSampler();
    }

    HRESULT D3DSpin::createCdPixelShader() {
        ID3DBlob* pixelShaderBlob;
        d3d->CompileShader(std::string(spinShaderSrc), "pixelShaderMain", "ps_4_0", (void**)&pixelShaderBlob);
        HRESULT hres = d3d->D3dDevice()->CreatePixelShader(pixelShaderBlob->GetBufferPointer(), pixelShaderBlob->GetBufferSize(), nullptr, &this->cdPixelShader);
        pixelShaderBlob->Release();
        return hres;
    }

    HRESULT D3DSpin::createCdVertexShader() {
        d3d->CompileShader(std::string(spinShaderSrc), "vertexShaderMain", "vs_4_0", (void**)&this->cdVertexShaderBlob);
        return d3d->D3dDevice()->CreateVertexShader(this->cdVertexShaderBlob->GetBufferPointer(), this->cdVertexShaderBlob->GetBufferSize(), nullptr, &this->cdVertexShader);
    }

    HRESULT D3DSpin::createCdVertexLayout() {
        // define the layout
        D3D11_INPUT_ELEMENT_DESC layout[] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "UV", 0, DXGI_FORMAT_R32G32_FLOAT, 0, D3D11_APPEND_ALIGNED_ELEMENT, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        };

        // actually create it
        return d3d->D3dDevice()->CreateInputLayout(layout, ARRAYSIZE(layout), this->cdVertexShaderBlob->GetBufferPointer(), this->cdVertexShaderBlob->GetBufferSize(), &this->cdVertexLayout);
    }

    void D3DSpin::setCdVertexLayout() {
        this->cdVertexShaderBlob->Release();
        d3d->D3dDeviceContext()->IASetInputLayout(this->cdVertexLayout);
    }

    void D3DSpin::updateCamBuffer() {
        this->camWorld = DirectX::XMMatrixIdentity();
        this->camView = DirectX::XMMatrixLookAtLH(this->eye, this->at, this->up);
        this->camProjection = DirectX::XMMatrixPerspectiveFovLH(DirectX::XM_PIDIV2, this->d3d->WindowWidth() / (FLOAT)d3d->WindowHeight(), 0.01f, 100.0f);
    }


    HRESULT D3DSpin::createCamBuffer() {
        D3D11_BUFFER_DESC camBufferDesc;
        memset(&camBufferDesc, 0, sizeof(D3D11_BUFFER_DESC));
        camBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        camBufferDesc.ByteWidth = (sizeof(DirectX::XMMATRIX) * 3);
        camBufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
        camBufferDesc.CPUAccessFlags = 0;
        return d3d->D3dDevice()->CreateBuffer(&camBufferDesc, nullptr, &this->camBuffer);
    }

    HRESULT D3DSpin::createCdIndexBuffer() {
        D3D11_BUFFER_DESC indexBufferDesc;
        memset(&indexBufferDesc, 0, sizeof(D3D11_BUFFER_DESC));

        indexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        indexBufferDesc.ByteWidth = (sizeof(uint16_t) * ceedeeModelTotalIndices);
        indexBufferDesc.BindFlags = D3D11_BIND_INDEX_BUFFER;
        indexBufferDesc.CPUAccessFlags = 0;

        D3D11_SUBRESOURCE_DATA indexBufferInitData;
        memset(&indexBufferInitData, 0, sizeof(D3D11_SUBRESOURCE_DATA));
        indexBufferInitData.pSysMem = ceedeeModelIndexBuffer;

        return d3d->D3dDevice()->CreateBuffer(&indexBufferDesc, &indexBufferInitData, &this->cdIndexBuffer);
    }

    void D3DSpin::setCdIndexBuffer() {
        d3d->D3dDeviceContext()->IASetIndexBuffer(this->cdIndexBuffer, DXGI_FORMAT_R16_UINT, 0);
    }
    void D3DSpin::setCdVertexBuffer() {
        uint32_t stride = sizeof(ModelVertex);
        uint32_t offset = 0;
        return d3d->D3dDeviceContext()->IASetVertexBuffers(0, 1, &this->cdVertexBuffer, &stride, &offset);
    }

    HRESULT D3DSpin::createCdVertexBuffer() {
        D3D11_BUFFER_DESC vertexBufferDesc;
        memset(&vertexBufferDesc, 0, sizeof(D3D11_BUFFER_DESC));
        vertexBufferDesc.Usage = D3D11_USAGE_DEFAULT;
        vertexBufferDesc.ByteWidth = sizeof(ModelVertex) * ceedeeModelTotalVertices;
        vertexBufferDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
        vertexBufferDesc.CPUAccessFlags = 0;

        D3D11_SUBRESOURCE_DATA vertexBufferInitData;
        memset(&vertexBufferInitData, 0, sizeof(D3D11_SUBRESOURCE_DATA));
        vertexBufferInitData.pSysMem = ceedeeModelVertexBuffer;
        return d3d->D3dDevice()->CreateBuffer(&vertexBufferDesc, &vertexBufferInitData, &this->cdVertexBuffer);

    }

    void D3DSpin::updateCdTexture() {
        this->d3d->D3dDeviceContext()->UpdateSubresource(this->cdTexture,
            NULL,
            nullptr,
            this->cdTextureProvider->CurrentTexture(),
            this->cdTextureProvider->Pitch(),
            NULL);
    }

    HRESULT D3DSpin::createCdTexture() {
        D3D11_TEXTURE2D_DESC tdesc;
        tdesc.Width = this->cdTextureProvider->Width();
        tdesc.Height = this->cdTextureProvider->Height();
        tdesc.MipLevels = 1;
        tdesc.ArraySize = 1;
        tdesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
        tdesc.SampleDesc.Count = 1;
        tdesc.SampleDesc.Quality = 0;
        tdesc.Usage = D3D11_USAGE_DEFAULT;
        tdesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        tdesc.CPUAccessFlags = 0;
        tdesc.MiscFlags = 0;

        D3D11_SUBRESOURCE_DATA tres = {};


        tres.pSysMem = this->cdTextureProvider->CurrentTexture();
        tres.SysMemPitch = this->cdTextureProvider->Pitch();
        tres.SysMemSlicePitch = 0;

        return d3d->D3dDevice()->CreateTexture2D(&tdesc, &tres, &this->cdTexture);
    }

    HRESULT D3DSpin::createCdResourceView() {
        return d3d->D3dDevice()->CreateShaderResourceView(this->cdTexture, NULL, &this->cdResourceView);
    }

    HRESULT D3DSpin::createCdSampler() {
        D3D11_SAMPLER_DESC sdesc;
        sdesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        sdesc.AddressU = D3D11_TEXTURE_ADDRESS_CLAMP;
        sdesc.AddressV = D3D11_TEXTURE_ADDRESS_CLAMP;
        sdesc.AddressW = D3D11_TEXTURE_ADDRESS_CLAMP;
        sdesc.MipLODBias = 1.0f;
        sdesc.MaxAnisotropy = 1;
        sdesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
        sdesc.BorderColor[0] = 1.0f;
        sdesc.BorderColor[1] = 1.0f;
        sdesc.BorderColor[2] = 1.0f;
        sdesc.BorderColor[3] = 1.0f;
        sdesc.MinLOD = -FLT_MAX;
        sdesc.MaxLOD = FLT_MAX;


        return d3d->D3dDevice()->CreateSamplerState(&sdesc, &this->cdSampler);
    }


    void D3DSpin::RenderDVD() {
        this->counter++;
        this->camWorld = DirectX::XMMatrixRotationY((float)((float)this->counter / 10.0f));

        DirectX::XMMATRIX camBufData[3];
        camBufData[0] = XMMatrixTranspose(this->camWorld);
        camBufData[1] = XMMatrixTranspose(this->camView);
        camBufData[2] = XMMatrixTranspose(this->camProjection);
        d3d->D3dDeviceContext()->UpdateSubresource(this->camBuffer, 0, nullptr, &camBufData, 0, 0);

        d3d->D3dDeviceContext()->VSSetShader(this->cdVertexShader, nullptr, 0);
        d3d->D3dDeviceContext()->VSSetConstantBuffers(0, 1, &this->camBuffer);
        d3d->D3dDeviceContext()->PSSetShader(this->cdPixelShader, nullptr, 0);

        d3d->D3dDeviceContext()->PSSetShaderResources(0, 1, &this->cdResourceView);
        d3d->D3dDeviceContext()->PSSetSamplers(0, 1, &this->cdSampler);
        d3d->D3dDeviceContext()->DrawIndexed(ceedeeModelTotalIndices, 0, 0);

        this->updateCamBuffer();
#ifdef _DEBUG
        ImGui::Begin("Debug Position");
        ImGui::DragFloat4("Eye", (float*)this->eye.m128_f32);
        ImGui::DragFloat4("At", (float*)this->at.m128_f32);
        ImGui::DragFloat4("Up", (float*)this->up.m128_f32);
        ImGui::End();

        ImGui::Begin("Change Tex");
        if (ImGui::Button("Unknown Disc")) {
            this->UpdateTexture(CdType::UNKNOWN_DISC);
        }
        if(ImGui::Button("Blu-Ray")){
            this->UpdateTexture(CdType::BLU_RAY);
        }
        if (ImGui::Button("Hd Dvd")) {
            this->UpdateTexture(CdType::HIGH_DEFINITION_DIGITAL_VERSITLE_DISC);
        }
        if (ImGui::Button("Dvd")) {
            this->UpdateTexture(CdType::DIGITAL_VERSITLE_DISC);
        }
        if (ImGui::Button("Cd")) {
            this->UpdateTexture(CdType::COMPACT_DISC);
        }

        ImGui::End();
#endif

    }

    void D3DSpin::UpdateTexture(CdType type) {
        if (this->cdTextureProvider->CurrentCdType() == type) return;

        switch (type) {
            case CdType::COMPACT_DISC:
                this->cdTextureProvider->SetCdTexture();
                break;
            case CdType::DIGITAL_VERSITLE_DISC:
                this->cdTextureProvider->SetDvdTexture();
                break;
            case CdType::HIGH_DEFINITION_DIGITAL_VERSITLE_DISC:
                this->cdTextureProvider->SetHdDvdTexture();
                break;
            case CdType::BLU_RAY:
                this->cdTextureProvider->SetBdTexture();
                break;
            case CdType::UNKNOWN_DISC:
            default:
                this->cdTextureProvider->SetUnknownTexture();
                break;
        }
        this->updateCdTexture();
    }

    D3DSpin::D3DSpin(D3D* d3d) {
        this->eye = DirectX::XMVectorSet(0.0f, 2.0f, 3.0f, 0.0f);
        this->at = DirectX::XMVectorSet(0.0f, 2.0f, 0.0f, 0.0f);
        this->up = DirectX::XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f);

        this->d3d = d3d;
        this->cdVertexShader = nullptr;
        this->cdPixelShader = nullptr;
        this->cdVertexLayout = nullptr;

        this->cdVertexBuffer = nullptr;
        this->cdIndexBuffer = nullptr;
        this->camBuffer = nullptr;

        this->cdTexture = nullptr;
        this->cdSampler = nullptr;
        this->cdResourceView = nullptr;

        this->cdTextureProvider = new CdTex();

        this->setupSpinningDVD();
    }
    D3DSpin::~D3DSpin() {
        this->cdVertexShader->Release();
        this->cdPixelShader->Release();
        this->cdVertexLayout->Release();

        this->cdVertexBuffer->Release();
        this->cdIndexBuffer->Release();
        this->camBuffer->Release();

        this->cdTexture->Release();
        this->cdSampler->Release();
        this->cdResourceView->Release();

        delete this->cdTextureProvider;
    }


}