////////////////////////////////
// NOTE(allen): D3D11 Function Implementations

global W32_D3D11Window w32_d3d11_window_slots[GFX_MAX_WINDOW_COUNT];

#define w32_d3d11_slot_from_handle(h) (w32_d3d11_window_slots + ((U64)(h) - 1))

link_function B32
d3d11_init(void){
  B32 error = 0;
  
  //- one proceed if not already initialized
  if (w32_d3d11.dxgi == 0){
    
    //- set up window slots
    if (!error){
      MemoryZeroArray(w32_d3d11_window_slots);
    }
    
    //- load d3d11 modules
    if (!error){
      w32_d3d11.dxgi = LoadLibraryA("dxgi.dll");
      w32_d3d11.d3d11 = LoadLibraryA("d3d11.dll");
      w32_d3d11.d3dcompiler = LoadLibraryA("d3dcompiler_47.dll");
      
      if (w32_d3d11.dxgi == 0 ||
          w32_d3d11.d3d11 == 0 ||
          w32_d3d11.d3dcompiler == 0){
        er_emit(str8_lit("failed to initialize d3d11 dll(s)"));
        error = 1;
      }
    }
    
    //- load & check d3d11 functions
    if (!error){
#define X(M,N,R,P) W32_PROC_ADDR(w32_##N, w32_d3d11.M, #N);
#include "d3d11_api_funcs.h"
#undef X
      
      B32 missing_d3d_func = 0;
#define X(M,N,R,P) if (w32_##N == 0){ missing_d3d_func = 1; }
#include "d3d11_api_funcs.h"
#undef X
      
      if (missing_d3d_func){
        er_emit(str8_lit("failed to load d3d11 function(s)"));
        error = 1;
      }
    }
    
    //- device & context
    if (!error){
      UINT flags = D3D11_CREATE_DEVICE_DEBUG;
      HRESULT r = w32_D3D11CreateDevice(0, D3D_DRIVER_TYPE_HARDWARE, 0,
                                        flags, 0, 0,
                                        D3D11_SDK_VERSION,
                                        &w32_d3d11.device, 0, &w32_d3d11.ctx);
      
      if (r != S_OK){
        er_emit(str8_lit("failed to create d3d11 device"));
        error = 1;
      }
    }
    
    //- debug
    if (!error){
      HRESULT r = w32_d3d11.device->QueryInterface(__uuidof(ID3D11Debug),
                                                   (void**)(&w32_d3d11.dbg));
      (void)(r);
    }
    
    //- on error clean up "non-temps"
    if (error){
      if (w32_d3d11.dxgi != 0){
        FreeLibrary(w32_d3d11.dxgi);
      }
      if (w32_d3d11.d3d11 != 0){
        FreeLibrary(w32_d3d11.d3d11);
      }
      if (w32_d3d11.d3dcompiler != 0){
        FreeLibrary(w32_d3d11.d3dcompiler);
      }
      
      if (w32_d3d11.device != 0){
        w32_d3d11.device->Release();
      }
      if (w32_d3d11.ctx != 0){
        w32_d3d11.ctx->Release();
      }
      if (w32_d3d11.dbg != 0){
        w32_d3d11.dbg->Release();
      }
      
#define X(M,N,R,P) w32_##N = 0;
#include "d3d11_api_funcs.h"
#undef X
      MemoryZeroStruct(&w32_d3d11);
    }
  }
  
  //- fill result
  B32 result = (!error);
  return(result);
}

link_function void
d3d11_window_release(GFX_Window *window){
  if (gfx_window_is_valid(window)){
    W32_D3D11Window *slot = w32_d3d11_slot_from_handle(window);
    if (slot->swap_chain != 0){
      slot->swap_chain->Release();
    }
    
    // NOTE(allen): Annoying. We have to do this to ensure
    // immediate release of the swap chain. The API does some
    // defering of freeing on these things for some reason, and
    // the freeing won't kick in no matter how many frees are
    // queued up, even managing to crash *the entire computer*
    // when I was testing it. In practice it might be annoying
    // to have this clear state changing the state of the
    // system? And if only a few releases like this are likely
    // to be queued up between natural calls to these, maybe
    // it's just fine. Keep an eye on this issue.
    w32_d3d11.ctx->ClearState();
    w32_d3d11.ctx->Flush();
    
    MemoryZeroStruct(slot);
  }
}

link_function B32
d3d11_equip_window(GFX_Window *window){
  String8 error = {};
  
  if (!gfx_window_is_valid(window)){
    error = str8_lit("error invalid window handle");
  }
  if (gfx_window_get_equipped_data(window) != 0){
    error = str8_lit("error window is already equipped");
  }
  
  //- swap chain
  IDXGISwapChain *swap_chain = 0;
  if (error.str == 0){
    W32_Window *window_slot = gfx_w32_window_from_handle(window);
    W32_D3D11Window *slot = w32_d3d11_slot_from_handle(window);
    
    IDXGIFactory *factory = 0;
    HRESULT factory_result = w32_CreateDXGIFactory(__uuidof(IDXGIFactory),
                                                   (void**)&factory);
    if (factory_result == S_OK){
      DXGI_SWAP_CHAIN_DESC desc = {};
      desc.BufferDesc.Width = 1024;
      desc.BufferDesc.Height = 768;
      desc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
      desc.SampleDesc.Count = 1;
      desc.SampleDesc.Quality = 0;
      desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
      desc.BufferCount = 1;
      desc.OutputWindow = window_slot->wnd;
      desc.Windowed = 1;
      factory->CreateSwapChain(w32_d3d11.device, &desc, &swap_chain);
      factory->Release();
    }
    
    if (swap_chain == 0){
      error = str8_lit("error w/ swap chain");
    }
  }
  
  //- fill equipment
  if (error.str == 0){
    W32_D3D11Window *slot = w32_d3d11_slot_from_handle(window);
    slot->swap_chain = swap_chain;
    
    gfx_window_set_equipped_data(window, slot, d3d11_window_release);
  }
  
  //- on error cleanup "non-temps"
  if (error.str != 0){
    if (swap_chain != 0){
      swap_chain->Release();
    }
    
    // NOTE(allen): Annoying. Need this for swap chain
    // to release properly. More thoughts in d3d11_window_release.
    w32_d3d11.ctx->ClearState();
    w32_d3d11.ctx->Flush();
  }
  
  //- fill result
  B32 result = (error.str == 0);
  return(result);
}

global IDXGISwapChain *w32_d3d11_swap_chain = 0;

link_function ID3D11RenderTargetView*
d3d11_begin_render(GFX_Window *window){
  ID3D11RenderTargetView *result = 0;
  // TODO(allen): Check equipment here
  if (gfx_window_is_valid(window) &&
      w32_d3d11_swap_chain == 0){
    W32_D3D11Window *slot = w32_d3d11_slot_from_handle(window);
    IDXGISwapChain *swap_chain = slot->swap_chain;
    
    // get view
    ID3D11Texture2D *buffer = 0;
    DWORD buffer_result =
      swap_chain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)(&buffer));
    if (buffer_result == S_OK){
      ID3D11RenderTargetView *view = 0;
      DWORD view_result =
        w32_d3d11.device->CreateRenderTargetView(buffer, 0, &view);
      if (view_result == S_OK){
        result = view;
        w32_d3d11_swap_chain = swap_chain;
      }
    }
    
    // release buffer
    if (buffer != 0){
      buffer->Release();
    }
  }
  return(result);
}

link_function void
d3d11_end_render(ID3D11RenderTargetView *view){
  if (w32_d3d11_swap_chain != 0){
    w32_d3d11_swap_chain->Present(0, 0);
    view->Release();
    w32_d3d11_swap_chain = 0;
  }
}

link_function W32_D3D11Window*
d3d11_window_from_gfx_window(GFX_Window *w){
  W32_D3D11Window *result = 0;
  // TODO(allen): Check equipment here
  if (gfx_window_is_valid(w)){
    result = w32_d3d11_slot_from_handle(w);
  }
  return(result);
}

