#pragma once
#include "Graphics.h"
#include <iostream>

/************************************
* 构造函数Graphics
************************************/
Graphics::Graphics(HWND hWnd, int width, int height)
    :
    width(width),
    height(height)
{
    // 初始化D3D
    InitD3D(hWnd, width, height);
}

/************************************
* 1 初始化D3D
************************************/
void Graphics::InitD3D(HWND hWnd, int width, int height)
{
    /************************************
    * 1.1 CreateDeviceAndSwapChain
    ************************************/
    // create a struct to hold information about the swap chain
    DXGI_SWAP_CHAIN_DESC scd;

    // clear out the struct for use
    ZeroMemory(&scd, sizeof(DXGI_SWAP_CHAIN_DESC));

    // fill the swap chain description struct
    scd.BufferCount = 1;                                    // one back buffer
    scd.BufferDesc.Format = DXGI_FORMAT_B8G8R8A8_UNORM;     // use 32-bit color
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;      // how swap chain is to be used
    scd.OutputWindow = hWnd;                                // the window to be used
    scd.SampleDesc.Count = 1;                               // how many multisamples
    scd.Windowed = TRUE;                                    // windowed/full-screen mode

    // create a device, device context and swap chain using the information in the scd struct
    D3D11CreateDeviceAndSwapChain(
        NULL,                         // IDXGIAdapter *pAdapter - Let DXGI take care of what adapter to use (default adapter)
        D3D_DRIVER_TYPE_HARDWARE,     // D3D_DRIVER_TYPE DriverType - hardware or software, or others
        NULL,                         // HMODULE Software
        NULL,                         // UINT Flags
        NULL,                         // D3D_FEATURE_LEVEL *pFeatureLevels
        NULL,                         // UINT FeatureLevels
        D3D11_SDK_VERSION,            // UINT SDKVersion
        &scd,                         // DXGI_SWAP_CHAIN_DESC *pSwapChainDesc
        &pSwapChain,                   // IDXGISwapChain **ppSwapChain
        &pDevice,                         // ID3D11Device **ppDevice
        NULL,                         // D3D_FEATURE_LEVEL *FeatureLevel
        &pContext);                     // ID3D11DeviceContext **ppImmediateContext

    /************************************
    * 1.2 DefineRenderTargetView
    ************************************/
    Microsoft::WRL::ComPtr<ID3D11Texture2D> pBackBuffer;

    // 从SwapChain中取得back buffer
    pSwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&pBackBuffer);

    // 根据back buffer，创建新的View Target
    pDevice->CreateRenderTargetView(pBackBuffer.Get(), NULL, &pTarget);

    /************************************
    * 1.3 DefineDepthStencilView
    ************************************/
    // Define depth stencil buffer
    Microsoft::WRL::ComPtr<ID3D11DepthStencilState> pDepthStencilState;

    // Define depth stencil texture
    Microsoft::WRL::ComPtr<ID3D11Texture2D> depthStencilTexture;

    // Create depth stencil state
    D3D11_DEPTH_STENCIL_DESC depthStencilDesc = {};
    depthStencilDesc.DepthEnable = TRUE;
    depthStencilDesc.DepthWriteMask = D3D11_DEPTH_WRITE_MASK_ALL;
    depthStencilDesc.DepthFunc = D3D11_COMPARISON_LESS;

    // Create depth stencil state
    pDevice->CreateDepthStencilState(&depthStencilDesc, &pDepthStencilState);

    // bind depth state
    pContext->OMSetDepthStencilState(pDepthStencilState.Get(), 1u);

    // Create the depth stencil texture
    D3D11_TEXTURE2D_DESC depthStencilTextureDesc = {};
    depthStencilTextureDesc.Width = width;
    depthStencilTextureDesc.Height = height;
    depthStencilTextureDesc.MipLevels = 1u;
    depthStencilTextureDesc.ArraySize = 1u;
    depthStencilTextureDesc.Format = DXGI_FORMAT_D32_FLOAT;
    depthStencilTextureDesc.SampleDesc.Count = 1u;
    depthStencilTextureDesc.SampleDesc.Quality = 0u;
    depthStencilTextureDesc.Usage = D3D11_USAGE_DEFAULT;
    depthStencilTextureDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;

    // Create texture
    pDevice->CreateTexture2D(&depthStencilTextureDesc, nullptr, &depthStencilTexture);

    // Create the depth stencil view
    D3D11_DEPTH_STENCIL_VIEW_DESC depthStencilViewDesc = {};
    depthStencilViewDesc.Format = DXGI_FORMAT_D32_FLOAT;
    depthStencilViewDesc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2D;
    depthStencilViewDesc.Texture2D.MipSlice = 0u;
    pDevice->CreateDepthStencilView(depthStencilTexture.Get(), &depthStencilViewDesc, &pDSView);

    /************************************
    * 1.4 DefineRenderTarget
    ************************************/
    // 将渲染目标设置成我们新创建的View Target
    pContext->OMSetRenderTargets(1u, pTarget.GetAddressOf(), pDSView.Get());
}

/************************************
* 2 渲染单帧
************************************/
void Graphics::RendorFrame(void)
{
    /************************************
    * 2.1 ClearRenderTargetView
    ************************************/
    // clear the back buffer to a deep blue
    const float color[] = { 0.0f, 0.2f, 0.4f, 1.0f };
    pContext->ClearRenderTargetView(pTarget.Get(), color);

    /************************************
    * 2.2 ClearDepthStencilViews
    ************************************/
    // clear the depth stencil view
    pContext->ClearDepthStencilView(pDSView.Get(), D3D11_CLEAR_DEPTH, 1.0f, 0u);

    /************************************
    * 2.3 - 2.12 Repeat graphic objects
    ************************************/
    // do 3D rendering on the back buffer here
    //drawObjects();
}

/************************************
* 3 切换显示
************************************/
void Graphics::EndFrame(void)
{
    // 指定View Port
    D3D11_VIEWPORT viewport;
    ZeroMemory(&viewport, sizeof(D3D11_VIEWPORT));
    // 控制View port的位置
    viewport.TopLeftX = 0;
    viewport.TopLeftY = 0;
    viewport.Width = width;
    viewport.Height = height;
    viewport.MinDepth = 0;
    viewport.MaxDepth = 1;

    pContext->RSSetViewports(1, &viewport);

    // switch the back buffer and the front buffer
    pSwapChain->Present(0, 0);
}

void Graphics::SetProjection(DirectX::FXMMATRIX proj) noexcept
{
    projection = proj;
}

DirectX::XMMATRIX Graphics::GetProjection() const noexcept
{
    return projection;
}

void Graphics::DrawIndexed(UINT count) noexcept
{
    pContext->DrawIndexed(count, 0u, 0u);
}

// for debug
wrl::ComPtr<ID3D11Device> Graphics::getDevice()
{
    return pDevice;
};

wrl::ComPtr<IDXGISwapChain> Graphics::getSwapChain()
{
    return pSwapChain;
};
wrl::ComPtr<ID3D11DeviceContext> Graphics::getContext()
{
    return pContext;
};

void Graphics::SetCamera(
    Logger& logger,
    DirectX::XMVECTOR eyePosition,
    DirectX::XMVECTOR focusPosition,
    DirectX::XMVECTOR upDirection) noexcept
{
    using namespace DirectX;
    this->eyePosition = eyePosition;
    this->focusPosition = focusPosition;
    this->upDirection = upDirection;
};

DirectX::XMMATRIX Graphics::getLookAtMatrix() noexcept
{
    return DirectX::XMMatrixLookAtLH(this->eyePosition, this->focusPosition, this->upDirection);
};