//#define ENABLE_LOG_PRINTF

#include "evas_direct3d_device.h"

#include "evas_direct3d_vertex_buffer_cache.h"

D3DDevice::D3DDevice()
{
   ResetParams();
}

bool D3DDevice::Init(HWND window, int depth, bool fullscreen)
{
   D3DPRESENT_PARAMETERS pp;
   D3DDISPLAYMODE dm;
   D3DCAPS9 caps;
   RECT rect;
   DWORD flag;
   HRESULT hr;

   if (window == NULL)
      return false;

   Destroy();

   _object = Direct3DCreate9(D3D_SDK_VERSION);
   if (_object == NULL)
     return false;

   if (FAILED(hr = _object->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &dm)))
     {
       ERR("GetAdapterDisplayMode failed: %x", hr);
       Destroy();
       return false;
     }

   if (FAILED(hr = _object->GetDeviceCaps(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, &caps)))
     {
     ERR("GetDeviceCaps failed: %x", hr);
     Destroy();
     return false;
     }

   if (!GetClientRect(window, &rect))
     {
     ERR("GetClientRect failed: %x", GetLastError());
     Destroy();
     return false;
     }

   if (SUCCEEDED(_object->CheckDeviceFormat(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
     dm.Format, 0, D3DRTYPE_TEXTURE, (depth == 16) ? D3DFMT_R5G6B5 : D3DFMT_A8R8G8B8)))
     {
     dm.Format = (depth == 16) ? D3DFMT_R5G6B5 : D3DFMT_A8R8G8B8;
     }

   flag = (caps.VertexProcessingCaps != 0) ?
     (D3DCREATE_HARDWARE_VERTEXPROCESSING | D3DCREATE_PUREDEVICE) :
     D3DCREATE_SOFTWARE_VERTEXPROCESSING;

   ZeroMemory(&pp, sizeof(pp));
   if (!fullscreen)
   {
      pp.BackBufferWidth = rect.right - rect.left;
      pp.BackBufferHeight = rect.bottom - rect.top;
   }
   else
   {
      pp.BackBufferWidth = ::GetSystemMetrics(SM_CXSCREEN);
      pp.BackBufferHeight = ::GetSystemMetrics(SM_CYSCREEN);
   }
   pp.BackBufferFormat = dm.Format;
   pp.BackBufferCount = 1;
   pp.MultiSampleType = D3DMULTISAMPLE_NONE;
   pp.MultiSampleQuality = 0;
   pp.SwapEffect = D3DSWAPEFFECT_DISCARD;
   pp.hDeviceWindow = window;
   pp.Windowed  = fullscreen ? FALSE : TRUE;
   //pp.EnableAutoDepthStencil = TRUE;
   //pp.AutoDepthStencilFormat = D3DFMT_D16;
   pp.FullScreen_RefreshRateInHz = 0;
   pp.PresentationInterval = D3DPRESENT_INTERVAL_IMMEDIATE;

   if (FAILED(hr = _object->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
     window, flag, &pp, &_device)))
     {
     WRN("CreateDevice failed: %x", hr);
     Destroy();
     return false;
     }

   LPDIRECT3DSURFACE9 backbuffer = NULL;
   _device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
   backbuffer->GetDesc(&_backbuffer_desc);
   backbuffer->Release();

   switch (dm.Format) {
   case D3DFMT_A8R8G8B8:
   case D3DFMT_X8R8G8B8:
     _depth = 32;
     break;
   case D3DFMT_R5G6B5:
     _depth = 16;
     break;
   default:
     WRN("No supported format found");
     Destroy();
     return false;
   }

   //_render_to_texture = false;

   _d3dpp = pp;
   _device_lost = FALSE;
   _scene_rendering = FALSE;
   _width = rect.right - rect.left;
   _height = rect.bottom - rect.top;
   _window = window;

   if (FAILED(CreateRenderTarget()))
   {
      ERR("Failed to create render target");
      Destroy();
      return false;
   }

   Log("initialized");
   return true;
}

bool D3DDevice::Reset(int width, int height, int fullscreen)
{
   D3DPRESENT_PARAMETERS pp = _d3dpp;
   _d3dpp.BackBufferWidth = (width > 0) ? width : _d3dpp.BackBufferWidth;
   _d3dpp.BackBufferHeight = (height > 0) ? height : _d3dpp.BackBufferHeight;
   _d3dpp.Windowed = (fullscreen == 1) ? FALSE : ((fullscreen == 0) ? TRUE : _d3dpp.Windowed);
   if (FAILED(ResetDevice()))
   {
      WRN("Couldnt restore device");
      _d3dpp = pp;
      return SUCCEEDED(ResetDevice());
   }
   _width = _d3dpp.BackBufferWidth;
   _height = _d3dpp.BackBufferHeight;
   return true;
}

void D3DDevice::Destroy()
{
   //if (_render_target != NULL)
   //{
   //   _render_target->Release();
   //   _render_target = NULL;
   //}
   if (_render_target_data != NULL)
   {
      _render_target_data->Release();
      _render_target_data = NULL;
   }
   if (_device != NULL)
   {
      int num = _device->Release();
      assert(num == 0);
   }
   if (_object != NULL)
      _object->Release();
   ResetParams();

   INF("uninitialized");
}

void D3DDevice::ResetParams()
{
   _window = NULL;
   _object = NULL;
   _device = NULL;
   _width = 0;
   _height = 0;
   _rot = 0;
   _depth = 0;
   _device_lost = false;
   _scene_rendering = false;
   ZeroMemory(&_d3dpp, sizeof(_d3dpp));
   ZeroMemory(&_backbuffer_desc, sizeof(_backbuffer_desc));
   //_render_target = NULL;
   _render_target_data = NULL;
   _render_data_updated = false;
   _render_data.Resize();
   //_original_render_target = NULL;
   //_render_to_texture = false;
}

HRESULT D3DDevice::RestoreDevice()
{
   Log("restore");
   assert(_device != NULL);

   HRESULT hr = S_OK;

   // Test the cooperative level to see if it's okay to render
   if (SUCCEEDED(hr = _device->TestCooperativeLevel()))
   {
      _device_lost = FALSE;
      DBG("render test ok");
      return S_OK;
   }

   // If the device was lost, do not render until we get it back
   if (hr == D3DERR_DEVICELOST)
      return E_FAIL;

   // Check if the device needs to be reset.
   if (hr == D3DERR_DEVICENOTRESET)
   {
      if (FAILED(hr = ResetDevice()))
         return hr;
   }
   return hr;
}

HRESULT D3DDevice::ResetDevice()
{
   DBG("reset");
   HRESULT hr = S_OK;

   _scene_rendering = FALSE;

   // Release all video memory objects
   // Bad to call such, make better
   D3DVertexBufferCache::Current()->Uninitialize();

   //if (_render_target != NULL)
   //{
   //   _render_target->Release();
   //   _render_target = NULL;
   //}
   if (_render_target_data != NULL)
   {
      _render_target_data->Release();
      _render_target_data = NULL;
   }

   // Reset the device
   if (FAILED(hr = _device->Reset(&_d3dpp)))
   {
      ERR("D3DDevice: Reset of the device failed! Error (%X)", (DWORD)hr);
      return hr;
   }

   // Store render target surface desc
   LPDIRECT3DSURFACE9 backbuffer = NULL;
   _device->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &backbuffer);
   if (backbuffer != NULL)
   {
      backbuffer->GetDesc(&_backbuffer_desc);
      backbuffer->Release();
   }

   // Initialize the app's device-dependent objects
   hr = CreateRenderTarget();

   if (FAILED(hr))
   {
      WRN("Restoration of device objects failed");
      // Invalidate objects

      return E_FAIL;
   }

   DBG("Device objects were successfuly restored");
   _textures.Set(NULL);

   //_device_objects_restored = true;
   return S_OK;
}

bool D3DDevice::Begin()
{
   if (FAILED(RestoreDevice()))
      return false;

   //if (_render_to_texture && _render_target != NULL)
   //{
   //   if (FAILED(_device->GetRenderTarget(0, &_original_render_target)))
   //      return false;
   //   if (FAILED(_device->SetRenderTarget(0, _render_target)))
   //      return false;
   //}

   HRESULT hr;
   if (FAILED(hr = _device->BeginScene()))
   {
      WRN("Cannot begin scene: %X", (DWORD)hr);
      return false;
   }

   //static const D3DVIEWPORT9 vp = {0, 0, _width, _height, 0.f, 1.f};
   //_device->SetViewport(&vp);
   //_device->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

   //_device->Clear(0, NULL, D3DCLEAR_TARGET /*| D3DCLEAR_ZBUFFER*/, 0xff8080ff, 1.f, 0);
   return true;
}

bool D3DDevice::End()
{
   _device->EndScene();
   _device->Present(NULL, NULL, NULL, NULL);

   _render_data_updated = false;

   //if (_render_to_texture && _render_target != NULL && _original_render_target != NULL)
   //{
   //   if (FAILED(_device->SetRenderTarget(0, _original_render_target)))
   //      return false;
   //}

   return true;
}

TArray<DWORD> &D3DDevice::GetRenderData()
{
   if (_render_data_updated)
      return _render_data;
   _render_data.Allocate(0);
   if (_render_target_data == NULL)
      return _render_data;

   LPDIRECT3DSURFACE9 surf = NULL;
   HRESULT hr;
   if (FAILED(_device->GetRenderTarget(0, &surf)))
      return _render_data;
   if (FAILED(hr = _device->GetRenderTargetData(surf, _render_target_data)))
   {
      WRN("Failed to get render target data (%X)", (DWORD)hr);
      surf->Release();
      return _render_data;
   }
   D3DLOCKED_RECT lr;
   if (FAILED(_render_target_data->LockRect(&lr, NULL, D3DLOCK_READONLY)))
   {
      surf->Release();
      return _render_data;
   }
   _render_data.Allocate(_width * _height);

   for (int i = 0; i < _height; i++)
   {
      CopyMemory(&_render_data[i * _width], (BYTE *)lr.pBits + i * lr.Pitch,
         _width * sizeof(DWORD));
   }

   _render_target_data->UnlockRect();
   _render_data_updated = true;
   surf->Release();
   return _render_data;
}

HRESULT D3DDevice::SetTexture(DWORD stage, LPDIRECT3DTEXTURE9 tex)
{
   if (stage >= 8)
      return E_FAIL;
   if (_textures.Length() <= (int)stage)
      _textures.Allocate(stage + 1);
   if (_textures[stage] != tex)
   {
      _textures[stage] = tex;
      return _device->SetTexture(stage, tex);
   }
   return S_OK;
}

HRESULT D3DDevice::CreateRenderTarget()
{
   if (_device == NULL)
      return E_FAIL;
   //if (_render_target != NULL &&
   if (_render_target_data != NULL)
      return S_OK;

   //if (FAILED(_device->CreateRenderTarget(_width, _height, _backbuffer_desc.Format,
   //   D3DMULTISAMPLE_NONE, 0, FALSE, &_render_target, NULL)))
   //{
   //   return E_FAIL;
   //}
   if (FAILED(_device->CreateOffscreenPlainSurface(_backbuffer_desc.Width,
      _backbuffer_desc.Height, _backbuffer_desc.Format, D3DPOOL_SYSTEMMEM,
      &_render_target_data, NULL)))
   {
      return E_FAIL;
   }
   return S_OK;
}
