#include "base/base_inc.h"
#include "os/os_inc.h"

#include <d3d11.h>
#include <dxgi.h>
#include <DirectXColors.h>
#include <D3Dcompiler.h>

#include "base/base_inc.c"
#include "base/base_big_functions.c"
#include "os/os_inc.c"

#include <stdio.h>

////////////////////////////////
// NOTE(allen): Functions to Load

//- d3d11 funcs x-list

#define D3D11_FUNCS_X_LIST() \
X(dxgi,  CreateDXGIFactory, HRESULT, (REFIID,void**))  \
X(d3d11, D3D11CreateDevice, HRESULT,                   \
(IDXGIAdapter*,D3D_DRIVER_TYPE,HMODULE,UINT,         \
D3D_FEATURE_LEVEL*,UINT,UINT,ID3D11Device**,        \
D3D_FEATURE_LEVEL*,ID3D11DeviceContext**))          \
X(d3dcompiler, D3DCompile, HRESULT,                    \
(LPCVOID,SIZE_T,LPCSTR,D3D_SHADER_MACRO*,            \
ID3DInclude*,LPCSTR,LPCSTR,UINT,UINT,ID3DBlob**,    \
ID3DBlob**))


//- d3d function types

#define X(M,N,R,P) typedef R W32_##N P;
D3D11_FUNCS_X_LIST()
#undef X

//- d3d function pointers

#define X(M,N,R,P) global W32_##N * w32_##N = 0;
D3D11_FUNCS_X_LIST()
#undef X

////////////////////////////////
// NOTE(allen): Renderer

ID3D11VertexShader     *vshader = 0;
ID3D11InputLayout      *in_layout = 0;
ID3D11PixelShader      *pshader = 0;
ID3D11Buffer           *vbuffer = 0;
ID3D11Buffer           *ibuffer = 0;

global char vshader_text[] =
"float4 main( float2 pos : POSITION ) : SV_POSITION\n"
"{\n"
"return float4(pos, 0, 1);\n"
"}\n";

global char pshader_text[] =
"float4 main() : SV_TARGET\n"
"{\n"
"return float4(1.0f, 1.0f, 1.0f, 1.0f);\n"
"}\n";

static F32 vertices[] = {
  0.0f,  0.5f,
  -0.5f, -0.5f,
  0.5f, -0.5f
};

static U32 indices[] = {
  0, 1, 2
};


////////////////////////////////
// NOTE(allen): Window Procedure

ID3D11Device           *w32_d3d_device = 0;
ID3D11DeviceContext    *w32_d3d_ctx = 0;

IDXGISwapChain         *gr_swap_chain = 0;
//ID3D11RenderTargetView *gr_view = 0;

static void error_message(char *msg);

function LRESULT
w32_graphics_window_proc(HWND   wnd,
                         UINT   msg,
                         WPARAM wparam,
                         LPARAM lparam){
  LRESULT result = 0;
  switch (msg){
    case WM_CLOSE:
    {
      ExitProcess(0);
    }break;
    
    case WM_SIZE:
    {
      //- get new dimensions
      U16 width = (lparam & 0xFFFF);
      U16 height = (lparam >> 16);
      
      
      //- resize the swap chain
      gr_swap_chain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
      
#if 0
      //- resize the swap chain
      {
        gr_view->Release();
        gr_swap_chain->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0);
        
        ID3D11Texture2D *buffer = 0;
        DWORD buffer_result =
          gr_swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)(&buffer));
        if (buffer_result != S_OK){
          error_message("error w/ texture buffer");
        }
        
        DWORD view_result =
          w32_d3d_device->CreateRenderTargetView(buffer, 0, &gr_view);
        if (view_result != S_OK){
          error_message("error w/ render target view");
        }
        
        buffer->Release();
      }
#endif
      
      //- render
      {
        ID3D11DeviceContext *ctx = w32_d3d_ctx;
        
        // get a view
        ID3D11Texture2D *buffer = 0;
        DWORD buffer_result =
          gr_swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)(&buffer));
        if (buffer_result != S_OK){
          error_message("error w/ texture buffer");
        }
        
        ID3D11RenderTargetView *view = 0;
        DWORD view_result =
          w32_d3d_device->CreateRenderTargetView(buffer, 0, &view);
        if (view_result != S_OK){
          error_message("error w/ render target view");
        }
        
        buffer->Release();
        
        // viewport
        D3D11_VIEWPORT viewport = {};
        viewport.Width    = (F32)width;
        viewport.Height   = (F32)height;
        viewport.MinDepth = 0.0f;
        viewport.MaxDepth = 1.0f;
        ctx->RSSetViewports(1, &viewport);
        
        // setup target
        ctx->ClearRenderTargetView(view, DirectX::Colors::MidnightBlue);
        ctx->OMSetRenderTargets(1, &view, 0);
        
        // draw triangle
        UINT stride = sizeof(F32);
        UINT offset = 0;
        ctx->IASetVertexBuffers(0, 1, &vbuffer, &stride, &offset);
        ctx->IASetIndexBuffer(ibuffer, DXGI_FORMAT_R32_UINT, 0);
        ctx->IASetInputLayout(in_layout);
        ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
        
        ctx->VSSetShader(vshader, 0, 0);
        ctx->PSSetShader(pshader, 0, 0);
        ctx->DrawIndexed(ArrayCount(indices), 0, 0);
        
        // present
        gr_swap_chain->Present(0, 0);
        
        // release
        view->Release();
      }
    }break;
    
    default:
    {
      result = DefWindowProcW(wnd, msg, wparam, lparam);
    }break;
  }
  return(result);
}

////////////////////////////////
// NOTE(allen): Main

#define GET_PROC_ADDR(v,m,s) (*(PROC*)(&(v))) = GetProcAddress((m),(s))
#define GRAPHICS_WINDOW_CLASS_NAME L"mr4th-d3d11-graphics"

static void
error_message(char *msg){
  MessageBoxA(0, msg, "Error", MB_OK);
  ExitProcess(1);
}

int
WinMain(HINSTANCE hInstance,
        HINSTANCE hPrevInstance,
        LPSTR lpCmdLine,
        int nShowCmd){
  w32_WinMain_init(hInstance, hPrevInstance, lpCmdLine, nShowCmd);
  
  ////////////////////////////////
  //- initialize d3d11
  
  //- load d3d11 modules
  HMODULE w32_dxgi_module = LoadLibraryA("dxgi.dll");
  HMODULE w32_d3d11_module = LoadLibraryA("d3d11.dll");
  HMODULE w32_d3dcompiler_module = LoadLibraryA("d3dcompiler_47.dll");
  
  if (w32_dxgi_module == 0 ||
      w32_d3d11_module == 0 ||
      w32_d3dcompiler_module == 0){
    error_message("failed to initialize d3d11 dll(s)");
  }
  
  
  //- load & check d3d11 functions
  {
#define X(M,N,R,P) GET_PROC_ADDR(w32_##N, w32_##M##_module, #N);
    D3D11_FUNCS_X_LIST()
#undef X
    
    B32 missing_d3d_func = false;
#define X(M,N,R,P) if (w32_##N == 0){ missing_d3d_func = true; }
    D3D11_FUNCS_X_LIST()
#undef X
    
    if (missing_d3d_func){
      error_message("failed to load d3d11 function(s)");
    }
  }
  
  //- device & context
  {
    UINT flags = D3D11_CREATE_DEVICE_DEBUG;
    D3D_FEATURE_LEVEL levels[] = {D3D_FEATURE_LEVEL_11_0};
    HRESULT r = w32_D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0,
                                      flags, levels, ArrayCount(levels),
                                      D3D11_SDK_VERSION,
                                      &w32_d3d_device, 0, &w32_d3d_ctx);
    
    if (r != S_OK){
      error_message("failed to create d3d11 device");
    }
  }
  
  //- debug
  ID3D11Debug            *w32_d3d_dbg = 0;
  {
    HRESULT r = w32_d3d_device->QueryInterface(__uuidof(ID3D11Debug),
                                               (void**)(&w32_d3d_dbg));
    (void)(r);
  }
  
  //- register window class
  {
    WNDCLASSW wnd_class = {0};
    wnd_class.style = CS_HREDRAW | CS_VREDRAW;
    wnd_class.lpfnWndProc = w32_graphics_window_proc;
    wnd_class.hInstance = hInstance;
    wnd_class.lpszClassName = GRAPHICS_WINDOW_CLASS_NAME;
    ATOM atom = RegisterClassW(&wnd_class);
    if (atom == 0){
      error_message("error registering window class");
    }
  }
  
  
  ////////////////////////////////
  //- initialize renderer
  
  {
    // vertex shader blob
    ID3DBlob *vshader_blob = 0;
    ID3DBlob *vshader_errors = 0;
    
    UINT vblob_flags = D3DCOMPILE_ENABLE_STRICTNESS|D3DCOMPILE_DEBUG;
    DWORD vblob_result =
      w32_D3DCompile(vshader_text, sizeof(vshader_text) - 1,
                     0, 0, 0, "main", "vs_5_0",
                     vblob_flags, 0,
                     &vshader_blob, &vshader_errors);
    
    if (vblob_result != 0){
      error_message("error w/ vertex shader blob");
    }
    
    // vertex shader
    void *vshader_ptr = vshader_blob->GetBufferPointer();
    U64 vshader_size = vshader_blob->GetBufferSize();
    DWORD vshader_result =
      w32_d3d_device->CreateVertexShader(vshader_ptr, vshader_size, 0, &vshader);
    if (vshader_result != S_OK){
      error_message("error w/ vertex shader");
    }
    
    // input layout
    D3D11_INPUT_ELEMENT_DESC layout[] = {
      { "POSITION", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
    };
    
    DWORD layout_result =
      w32_d3d_device->CreateInputLayout(layout, ArrayCount(layout),
                                        vshader_ptr, vshader_size, &in_layout);
    if (layout_result != S_OK){
      error_message("error w/ input layout");
    }
    
    // release vertex shader blob
    vshader_blob->Release();
    if (vshader_errors != 0){
      vshader_errors->Release();
    }
    
    // pixel shader blob
    ID3DBlob *pshader_blob = 0;
    ID3DBlob *pshader_errors = 0;
    
    UINT pblob_flags = D3DCOMPILE_ENABLE_STRICTNESS|D3DCOMPILE_DEBUG;
    DWORD pblob_result =
      w32_D3DCompile(pshader_text, sizeof(pshader_text) - 1,
                     0, 0, 0, "main", "ps_5_0",
                     pblob_flags, 0,
                     &pshader_blob, &pshader_errors);
    
    if (pblob_result != 0){
      error_message("error w/ pixel shader blob");
    }
    
    // pixel shader
    void *pshader_ptr = pshader_blob->GetBufferPointer();
    U64 pshader_size = pshader_blob->GetBufferSize();
    DWORD pshader_result =
      w32_d3d_device->CreatePixelShader(pshader_ptr, pshader_size, 0, &pshader);
    if (pshader_result != S_OK){
      error_message("error w/ pixel shader");
    }
    
    // release pixel shader blob
    pshader_blob->Release();
    if (pshader_errors != 0){
      pshader_errors->Release();
    }
    
    // vertex buffer
    D3D11_BUFFER_DESC vbuffer_desc = {};
    vbuffer_desc.Usage = D3D11_USAGE_DEFAULT;
    vbuffer_desc.ByteWidth = sizeof(vertices);
    vbuffer_desc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
    
    D3D11_SUBRESOURCE_DATA vbuffer_init_data = {};
    vbuffer_init_data.pSysMem = vertices;
    
    DWORD vbuffer_result =
      w32_d3d_device->CreateBuffer(&vbuffer_desc, &vbuffer_init_data, &vbuffer);
    if (vbuffer_result != S_OK){
      error_message("error w/ vertex buffer");
    }
    
    // index buffer
    D3D11_BUFFER_DESC ibuffer_desc = {};
    ibuffer_desc.Usage = D3D11_USAGE_DEFAULT;
    ibuffer_desc.ByteWidth = sizeof(indices);
    ibuffer_desc.BindFlags = D3D11_BIND_INDEX_BUFFER;
    
    D3D11_SUBRESOURCE_DATA ibuffer_init_data = {};
    ibuffer_init_data.pSysMem = indices;
    
    DWORD ibuffer_result =
      w32_d3d_device->CreateBuffer(&ibuffer_desc, &ibuffer_init_data, &ibuffer);
    if (ibuffer_result != S_OK){
      error_message("error w/ index buffer");
    }
  }
  
  ////////////////////////////////
  //- create d3d11 window
  
  //- create window
  HWND gr_wnd = CreateWindowW(GRAPHICS_WINDOW_CLASS_NAME, 
                              L"TITLE ME",
                              WS_TILEDWINDOW,
                              CW_USEDEFAULT, CW_USEDEFAULT,
                              CW_USEDEFAULT, CW_USEDEFAULT,
                              0, 0, hInstance, 0 // parent, menu, inst, param
                              );
  if (gr_wnd == 0){
    error_message("failed to create graphics window");
  }
  
  //- swap chain
  {
    IDXGIFactory *factory = 0;
    HRESULT factory_result = w32_CreateDXGIFactory(__uuidof(IDXGIFactory),
                                                   (void**)(&factory));
    if (factory_result != S_OK){
      error_message("error w/ factory");
    }
    
    RECT rect = {};
    GetClientRect(gr_wnd, &rect);
    
    DXGI_SWAP_CHAIN_DESC swap_chain_desc = {};
    swap_chain_desc.BufferDesc.Width = (rect.right - rect.left);
    swap_chain_desc.BufferDesc.Height = (rect.bottom - rect.top);
    swap_chain_desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
    swap_chain_desc.SampleDesc.Count = 1;
    swap_chain_desc.SampleDesc.Quality = 0;
    swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    swap_chain_desc.BufferCount = 2;
    swap_chain_desc.OutputWindow = gr_wnd;
    swap_chain_desc.Windowed = true;
    
    HRESULT r =
      factory->CreateSwapChain(w32_d3d_device, &swap_chain_desc, &gr_swap_chain);
    
    if (r != S_OK){
      error_message("error w/ swap chain");
    }
    
    factory->Release();
  }
  
  //- let's look at the new window in action!
  ShowWindow(gr_wnd, SW_SHOW);
  
  for (;;){
    //- wait for input
    MSG msg = {};
    BOOL get_message = GetMessage(&msg, 0, 0, 0);
    if (get_message <= 0){
      break;
    }
    DispatchMessage(&msg);
    
    // render
    {
      ID3D11DeviceContext *ctx = w32_d3d_ctx;
      
      // get a view
      ID3D11Texture2D *buffer = 0;
      DWORD buffer_result =
        gr_swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)(&buffer));
      if (buffer_result != S_OK){
        error_message("error w/ texture buffer");
      }
      
      ID3D11RenderTargetView *view = 0;
      DWORD view_result =
        w32_d3d_device->CreateRenderTargetView(buffer, 0, &view);
      if (view_result != S_OK){
        error_message("error w/ render target view");
      }
      
      buffer->Release();
      
      // get the window's size
      RECT rect = {};
      GetClientRect(gr_wnd, &rect);
      
      // viewport
      D3D11_VIEWPORT viewport = {};
      viewport.Width    = (F32)(rect.right - rect.left);
      viewport.Height   = (F32)(rect.bottom - rect.top);
      viewport.MinDepth = 0.0f;
      viewport.MaxDepth = 1.0f;
      ctx->RSSetViewports(1, &viewport);
      
      // setup target
      ctx->ClearRenderTargetView(view, DirectX::Colors::MidnightBlue);
      ctx->OMSetRenderTargets(1, &view, 0);
      
      // draw triangle
      UINT stride = sizeof(F32);
      UINT offset = 0;
      ctx->IASetVertexBuffers(0, 1, &vbuffer, &stride, &offset);
      ctx->IASetIndexBuffer(ibuffer, DXGI_FORMAT_R32_UINT, 0);
      ctx->IASetInputLayout(in_layout);
      ctx->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
      
      ctx->VSSetShader(vshader, 0, 0);
      ctx->PSSetShader(pshader, 0, 0);
      ctx->DrawIndexed(ArrayCount(indices), 0, 0);
      
      // present
      gr_swap_chain->Present(0, 0);
      
      // release
      view->Release();
    }
  }
  
  return(0);
}

//$ graphical //
