#include "pch.h"
#include "Renderer.h"
#include "Mesh.h"
#include "Utils.h"
#include "Texture.h"
#include "Effects/Effect.h"
#include "Effects/FireEffect.h"

namespace dae {

    Renderer::Renderer(SDL_Window *pWindow) :
            m_pWindow(pWindow) {
        //Initialize
        SDL_GetWindowSize(pWindow, &m_Width, &m_Height);

        //Initialize DirectX pipeline
        const HRESULT result = InitializeDirectX();
        if (result == S_OK) {
            m_IsInitialized = true;
            std::cout << "DirectX is initialized and ready!\n";
        } else {
            std::cout << "DirectX initialization failed!\n";
        }

        InitializeSDLRasterizer();

        std::vector<VertexIn> vertices{};
        std::vector<uint32_t> indices{};

        if (Utils::ParseOBJNew("resources/vehicle.obj", vertices, indices, false)) {
            std::cout << "Model Loaded" << std::endl;

        } else {
            std::cout << "Model failed to load" << std::endl;
            assert(true && "Model failed to load");
        }

        std::cout << "Vertices: " << vertices.size() << " Indices: " << indices.size() << std::endl;

        std::shared_ptr<Material> vehicleMaterial = std::make_shared<Material>();
        vehicleMaterial->diffuseTexturePtr = Texture::LoadFromFile("resources/vehicle_diffuse.png", m_DevicePtr);
        vehicleMaterial->normalTexturePtr = Texture::LoadFromFile("resources/vehicle_normal.png", m_DevicePtr);
        vehicleMaterial->specularTexturePtr = Texture::LoadFromFile("resources/vehicle_specular.png", m_DevicePtr);
        vehicleMaterial->glossTexturePtr = Texture::LoadFromFile("resources/vehicle_gloss.png", m_DevicePtr);

        Effect* effect = new Effect(m_DevicePtr, L"resources/PosCol3D.fx");
        m_meshes.push_back(new Mesh(m_DevicePtr, vertices, indices, vehicleMaterial, effect));



        indices.clear();
        vertices.clear();
        if(Utils::ParseOBJNew("resources/fireFX.obj", vertices, indices, false)){
            std::cout << "Model Loaded" << std::endl;
        } else {
            std::cout << "Model failed to load" << std::endl;
            assert(true && "Model failed to load");
        }

        std::shared_ptr<Material> FireMaterial = std::make_shared<Material>();
        FireMaterial->diffuseTexturePtr = Texture::LoadFromFile("resources/fireFX_diffuse.png", m_DevicePtr);

        FireEffect* fireEffect = new FireEffect(m_DevicePtr, L"resources/Fire.fx");
        m_meshes.push_back(new Mesh(m_DevicePtr, vertices, indices, FireMaterial, fireEffect));


        float aspectRatio = static_cast<float>(m_Width) / static_cast<float>(m_Height);
        m_Camera = Camera({.0f, 5.0f, -64.f}, 45.f);
        m_Camera.Initialize(45.f, {.0f, 5.0f, -64.f}, aspectRatio);
    }

    Renderer::~Renderer() {
        m_RenderTargetViewPtr->Release();
        m_RenderTargetBufferPtr->Release();

        m_DepthStencilViewPtr->Release();
        m_DepthStencilBufferPtr->Release();

        m_SwapChainPtr->Release();

        if (m_DeviceContextPtr) {
            m_DeviceContextPtr->ClearState();
            m_DeviceContextPtr->Flush();
            m_DeviceContextPtr->Release();
        }

        m_DevicePtr->Release();

        for (auto mesh: m_meshes) {
            delete mesh;
        }


    }

    void Renderer::Update(const Timer *pTimer) {
        m_Camera.Update(pTimer);

        for (auto mesh: m_meshes) {
            mesh->SetCameraPos(m_Camera.GetPosition());
        }
        if (m_Rotating) {
            m_currentRotation += pTimer->GetElapsed();
            Matrix world = Matrix::CreateRotationY(m_currentRotation);
            for (auto mesh: m_meshes) {
                mesh->SetWorldMatrix(world);
            }
        }
    }

    void Renderer::Render() const {
        if (!m_IsInitialized)
            return;
        if (m_backendType == Backendtype::DirectX) {

            //Clear back buffer
            const float clearColor[] = {1.f / 255.f, 0.f / 255.f, 76.f / 255.f, 1.f};
            m_DeviceContextPtr->ClearRenderTargetView(m_RenderTargetViewPtr, clearColor);
            m_DeviceContextPtr->ClearDepthStencilView(m_DepthStencilViewPtr, D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);

            Matrix viewProjMatrix = m_Camera.GetViewProjectionMatrix();
            for (auto mesh: m_meshes) {
                Matrix modelMatrix = mesh->GetWorldMatrix();
                Matrix worldViewProjMatrix = modelMatrix * viewProjMatrix;

                mesh->Render(m_DeviceContextPtr, worldViewProjMatrix);
            }

            //Present
            m_SwapChainPtr->Present(0, 0);
        } else {
            SDL_FillRect(m_pBackBuffer, nullptr, Uint32(255 << 24) + Uint32(100 << 16) + Uint32(100 << 8) + Uint32(100));

            //Lock BackBuffer
            for (int x = 0; x < 100; ++x) {
                for (int y = 0; y < 100; ++y) {
                    m_pBackBufferPixels[(x + 100) + (y + 150) * m_Width] = SDL_MapRGB(m_pBackBuffer->format, 255, 0, 0);
                }
            }

            SDL_BlitSurface(m_pBackBuffer, nullptr, m_pFrontBuffer, nullptr);
            SDL_UpdateWindowSurface(m_pWindow);
        }

    }

    HRESULT Renderer::InitializeDirectX() {
        //1. Create device & deviceContext
        //=====
        D3D_FEATURE_LEVEL featureLevel = D3D_FEATURE_LEVEL_11_1;
        uint32_t createDeviceFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)
        createDeviceFlags |= D3D11_CREATE_DEVICE_DEBUG;
#endif

        HRESULT result = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, createDeviceFlags, &featureLevel,
                                           1, D3D11_SDK_VERSION, &m_DevicePtr, nullptr, &m_DeviceContextPtr);
        if (FAILED(result))
            return result;

        //Create DXGI factory
        IDXGIFactory1 *DxgiFactoryPtr{};
        result = CreateDXGIFactory1(__uuidof(IDXGIFactory1), reinterpret_cast<void **>(&DxgiFactoryPtr));
        if (FAILED(result))
            return result;

        //2. Create swap chain
        //=====
        DXGI_SWAP_CHAIN_DESC swapChainDesc{};
        swapChainDesc.BufferDesc.Width = m_Width;
        swapChainDesc.BufferDesc.Height = m_Height;
        swapChainDesc.BufferDesc.RefreshRate.Numerator = 1;
        swapChainDesc.BufferDesc.RefreshRate.Denominator = 60;
        swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        swapChainDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
        swapChainDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
        swapChainDesc.SampleDesc.Count = 1;
        swapChainDesc.SampleDesc.Quality = 0;
        swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
        swapChainDesc.BufferCount = 1;
        swapChainDesc.Windowed = true;
        swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_DISCARD;
        swapChainDesc.Flags = 0;

        //Get the handle (HWND) from the SDL back buffer
        SDL_SysWMinfo sysWMInfo{};
        SDL_GetVersion(&sysWMInfo.version);
        SDL_GetWindowWMInfo(m_pWindow, &sysWMInfo);
        swapChainDesc.OutputWindow = sysWMInfo.info.win.window;

        //Create SwapChain
        result = DxgiFactoryPtr->CreateSwapChain(m_DevicePtr, &swapChainDesc, &m_SwapChainPtr);
        if (FAILED(result))
            return result;

        //3. Create depthStencil (DS) & DepthStencilView (DSV)
        //=====
        //Resource
        D3D11_TEXTURE2D_DESC depthStencilDesc{};
        depthStencilDesc.Width = m_Width;
        depthStencilDesc.Height = m_Height;
        depthStencilDesc.MipLevels = 1;
        depthStencilDesc.ArraySize = 1;
        depthStencilDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
        depthStencilDesc.SampleDesc.Count = 1;
        depthStencilDesc.SampleDesc.Quality = 0;
        depthStencilDesc.Usage = D3D11_USAGE_DEFAULT;
        depthStencilDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
        depthStencilDesc.CPUAccessFlags = 0;
        depthStencilDesc.MiscFlags = 0;

        //View
        D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc{};
        depthStencilViewDesc.Format = depthStencilDesc.Format;
        depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
        depthStencilViewDesc.Texture2D.MipSlice = 0;

        result = m_DevicePtr->CreateTexture2D(&depthStencilDesc, nullptr, &m_DepthStencilBufferPtr);
        if (FAILED(result))
            return result;

        result = m_DevicePtr->CreateDepthStencilView(m_DepthStencilBufferPtr, &depthStencilViewDesc, &m_DepthStencilViewPtr);
        if (FAILED(result))
            return result;

        //.4 Create RenderTarget (RT) & RenderTargetView (RTV)

        //Resource
        result = m_SwapChainPtr->GetBuffer(0, __uuidof(ID3D11Texture2D), reinterpret_cast<void **>(&m_RenderTargetBufferPtr));
        if (FAILED(result))
            return result;

        //View
        result = m_DevicePtr->CreateRenderTargetView(m_RenderTargetBufferPtr, nullptr, &m_RenderTargetViewPtr);
        if (FAILED(result))
            return result;

        //5. Bind RTV & DSV to output Merger stage
        //=====
        m_DeviceContextPtr->OMSetRenderTargets(1, &m_RenderTargetViewPtr, m_DepthStencilViewPtr);

        //6. Set Viewport
        //=====
        D3D11_VIEWPORT viewport{};
        viewport.Width = static_cast<float>(m_Width);
        viewport.Height = static_cast<float>(m_Height);
        viewport.TopLeftX = 0.f;
        viewport.TopLeftY = 0.f;
        viewport.MinDepth = 0.f;
        viewport.MaxDepth = 1.f;
        m_DeviceContextPtr->RSSetViewports(1, &viewport);

        return S_OK;
    }


    void Renderer::InitializeSDLRasterizer() {
        m_pFrontBuffer = SDL_GetWindowSurface(m_pWindow);
        m_pBackBuffer = SDL_CreateRGBSurface(0, m_Width, m_Height, 32, 0, 0, 0, 0);
        m_pBackBufferPixels = (uint32_t *) m_pBackBuffer->pixels;

//        m_pDepthBufferPixels = new float[m_Width * m_Height];

    }

    void Renderer::SwitchBackend() {
        if (m_backendType == Backendtype::DirectX) {
            m_backendType = Backendtype::SDL;
        } else {
            m_backendType = Backendtype::DirectX;
        }
    }

    void Renderer::NextSamplingState() {
        for(auto mesh: m_meshes){
            mesh->NextSamplingState();
        }
    }

    void Renderer::ToggleNormals() {
        for(auto mesh: m_meshes){
            mesh->ToggleNormals();
        }
    }

    void Renderer::ToggleRotation() {
        m_Rotating = !m_Rotating;
    }

    void Renderer::ToggleUniformClearColor() {
        m_UniformClearColor = !m_UniformClearColor;
    }
}
