//#define ENABLE_LOG_PRINTF

#include <string.h>

#include <d3dx9.h>

#include "evas_direct3d_object_image.h"
#include "evas_direct3d_image_cache.h"
#include "evas_direct3d_device.h"
#include "evas_direct3d_shader_pack.h"
#include "evas_direct3d_vertex_buffer_cache.h"

TArray<D3DObjectImage *> D3DObjectImage::_cache;
bool D3DObjectImage::_cache_enabled = false;

D3DObjectImage::D3DObjectImage()
{
   _x = _y = _w = _h = 0;
   _sx = _sy = _sw = _sh = 0;
   _u = _v = _du = _dv = 0;
   _image_id = -1;
   _width = _height = 0;
   _source[0] = 0;
   _color = 0xffffffff;
   _cache_i = 0;
   _border = D3DXVECTOR4(0, 0, 0, 0);
   _uvborder = D3DXVECTOR4(0, 0, 0, 0);
   _with_border = false;
   _dirty = false;
   _image_data_updated = false;
}

D3DObjectImage::~D3DObjectImage()
{
   D3DImageCache::Current()->RemoveImageUser(_image_id);
}

void D3DObjectImage::CopyTo(D3DObjectImage *image)
{
   assert(image != NULL);
   image->_u = _u;
   image->_v = _v;
   image->_du = _du;
   image->_dv = _dv;
   image->_image_id = _image_id;
   image->_width = _width;
   image->_height = _height;
   CopyMemory(image->_source, _source, sizeof(_source));
   D3DImageCache::Current()->AddImageUser(image->_image_id);
}

void D3DObjectImage::BeginCache()
{
   _cache.Allocate(0);
   _cache_enabled = true;
}

void D3DObjectImage::EndCache(D3DDevice *d3d)
{
   if (!_cache_enabled || _cache.Length() == 0)
      return;
   D3DShaderPack::Current()->SetVDecl(d3d, D3DShaderPack::VDECL_XYUVC);
   D3DShaderPack::Current()->SetVS(d3d, D3DShaderPack::VS_COPY_UV_COLOR);
   D3DShaderPack::Current()->SetPS(d3d, D3DShaderPack::PS_TEX_COLOR_FILTER);

   static TArray<Vertex> sorted;
   static TArray<GroupDesc> groups;
   sorted.Allocate(0);
   groups.Allocate(0);

   bool found = true;
   while (found)
   {
      found = false;
      int cur_id = -1;
      int num = 0;
      for (int i = 0; i < _cache.Length(); i++)
      {
         // We have processed this
         if (_cache[i]->_image_id < 0)
            continue;
         found = true;
         if (cur_id < 0)
            cur_id = _cache[i]->_image_id;
         if (_cache[i]->_image_id == cur_id)
         {
            if (!_cache[i]->_with_border)
            {
               Vertex *data = _cache[i]->MakeData();
               sorted.Add(data[0]);
               sorted.Add(data[1]);
               sorted.Add(data[2]);
               sorted.Add(data[3]);
               sorted.Add(data[4]);
               sorted.Add(data[5]);
               _cache[i]->_image_id = -_cache[i]->_image_id - 1;
               num++;
            }
            else
            {
               Vertex *data = _cache[i]->MakeDataBorder();
               int last_len = sorted.Length();
               sorted.Allocate(last_len + 6 * 9);
               CopyMemory(&sorted[last_len], data, sizeof(Vertex) * 6 * 9);
               _cache[i]->_image_id = -_cache[i]->_image_id - 1;
               num += 9;
            }
         }
      }
      if (num > 0)
      {
         GroupDesc gd = {num, cur_id};
         groups.Add(gd);
      }
   }

   // Restore ids
   for (int i = 0; i < _cache.Length(); i++)
      _cache[i]->_image_id = -_cache[i]->_image_id - 1;

   D3DVertexBufferCache::CacheEntryInfo ce_info;
   if (!D3DVertexBufferCache::Current()->InitBuffer(d3d, (BYTE *)sorted.Data(),
      sorted.Length() * sizeof(Vertex), ce_info))
   {
      return;
   }
   D3DVertexBufferCache::Current()->SelectBufferToDevice(d3d, ce_info.id, sizeof(Vertex));

   HRESULT hr;
   for (int i = 0, cur = 0; i < groups.Length(); i++)
   {
      if (FAILED(hr = D3DImageCache::Current()->SelectImageToDevice(d3d, groups[i].id)))
      {
         Log("Failed to select texture: %X", (DWORD)hr);
      }
//      d3d->GetDevice()->DrawPrimitiveUP(D3DPT_TRIANGLELIST, groups[i].num * 2,
//         &sorted[cur], sizeof(Vertex));
      d3d->GetDevice()->DrawPrimitive(D3DPT_TRIANGLELIST, cur, groups[i].num * 2);
      cur += groups[i].num * 6;
   }

   DBG("Image cache drawn: %d items, %d groups", _cache.Length(), groups.Length());
   _cache_enabled = false;
}

void D3DObjectImage::Draw(D3DDevice *d3d)
{
   _dirty = false;

   DBG("Image draw: (%.3f, %.3f, %.3f, %.3f)", _x, _y, _w, _h);

   if (_cache_enabled)
   {
      _cache.Add(this);
      _cache_i = _cache.Length() - 1;
      return;
   }

   D3DShaderPack::Current()->SetVDecl(d3d, D3DShaderPack::VDECL_XYUVC);
   D3DShaderPack::Current()->SetVS(d3d, D3DShaderPack::VS_COPY_UV_COLOR);
   D3DShaderPack::Current()->SetPS(d3d, D3DShaderPack::PS_TEX_COLOR_FILTER);
   D3DImageCache::Current()->SelectImageToDevice(d3d, _image_id);

   if (!_with_border)
      d3d->GetDevice()->DrawPrimitiveUP(D3DPT_TRIANGLELIST, 2, MakeData(), sizeof(Vertex));
   else
      d3d->GetDevice()->DrawPrimitiveUP(D3DPT_TRIANGLELIST, 18, MakeDataBorder(), sizeof(Vertex));
}

void D3DObjectImage::Init(FLOAT u, FLOAT v, FLOAT du, FLOAT dv,
   int image_id, int width, int height, const char *source)
{
   _u = u;
   _v = v;
   _du = du;
   _dv = dv;
   _image_id = image_id;
   _width = width;
   _height = height;
#ifdef __MINGW32__
   strncpy(_source, source, sizeof(_source) - 1);
#else
   strncpy_s(_source, sizeof(_source), source, sizeof(_source) - 1);
#endif // ! __MINGW32__
}

void D3DObjectImage::Setup(FLOAT x, FLOAT y, FLOAT w, FLOAT h,
   int sx, int sy, int sw, int sh)
{
   if (!_dirty)
   {
      _x = 1.f;
      _y = -1.f;
      _w = _h = 0.f;
      _sx = _sy = 1.f;
      _sw = _sh = 0.f;
   }

   if (!_with_border)
   {
      _x = x;
      _y = y;
      _w = w;
      _h = h;
      _sx = FLOAT(sx) / FLOAT(_width);
      _sy = FLOAT(sy) / FLOAT(_height);
      _sw = FLOAT(sw) / FLOAT(_width);
      _sh = FLOAT(sh) / FLOAT(_height);
   }
   else
   {
      _x = min(_x, x);
      _y = max(_y, y);
      _w += w / 3;
      _h += h / 3;
      _sx = min(_sx, FLOAT(sx) / FLOAT(_width));
      _sy = min(_sy, FLOAT(sy) / FLOAT(_height));
      _sw += FLOAT(sw) / (3.f * FLOAT(_width));
      _sh += FLOAT(sh) / (3.f * FLOAT(_height));
   }
   _dirty = true;

}

void D3DObjectImage::SetupColorFilter(DWORD color)
{
   //_color = ((a & 0xff) << 24) | ((r & 0xff) << 16) | ((g & 0xff) << 8) | (b & 0xff);
   _color = color;
}

D3DObjectImage::Vertex *D3DObjectImage::MakeData()
{
   //FLOAT z = (FLOAT(_cache_i) + 0.5f) / _cache.Length();
   Vertex data[6] = {
      {_x, _y,             _u + _sx * _du, _v + _sy * _dv,                 _color},
      {_x + _w, _y,        _u + (_sx + _sw) * _du, _v + _sy * _dv,         _color},
      {_x, _y + _h,        _u + _sx * _du, _v + (_sy + _sh) * _dv,         _color},
      {_x, _y + _h,        _u + _sx * _du, _v + (_sy + _sh) * _dv,         _color},
      {_x + _w, _y,        _u + (_sx + _sw) * _du, _v + _sy * _dv,         _color},
      {_x + _w, _y + _h,   _u + (_sx + _sw) * _du, _v + (_sy + _sh) * _dv, _color}};
   CopyMemory(_data, data, sizeof(data));
   return _data;
}

D3DObjectImage::Vertex *D3DObjectImage::MakeDataBorder()
{
   //FLOAT z = (FLOAT(_cache_i) + 0.5f) / _cache.Length();
   if (_border.x + _border.z > _w)
      _border.x = _border.z = _w / 2;
   if (_border.y + _border.w < _h)
      _border.y = _border.w = _h / 2;

   FLOAT ul, ut, ur, ub;
   ul = _uvborder.x * _du;
   ut = _uvborder.y * _dv;
   ur = _uvborder.z * _du;
   ub = _uvborder.w * _dv;
   FLOAT bl, bt, br, bb;
   bl = _border.x;
   bt = _border.y;
   br = _border.z;
   bb = _border.w;

   const FLOAT half_x = 0.5f * _du / FLOAT(_width);
   const FLOAT half_y = 0.5f * _dv / FLOAT(_height);

   // Diagonal knots
   Vertex data[4] = {
      {_x, _y,                         _u + _sx * _du + half_x,     _v + _sy * _dv + half_y,         _color},
      {_x + bl, _y + bt,               _u + ul + _sx * _du,         _v + ut + _sy * _dv,             _color},
      {_x + _w - br, _y + _h - bb,     _u - ur + (_sx + _sw) * _du, _v - ub + (_sy + _sh) * _dv,     _color},
      {_x + _w, _y + _h,               _u + (_sx + _sw) * _du - half_x, _v + (_sy + _sh) * _dv - half_y, _color}};

   static const int yshift[6] = {0, 0, 1, 1, 0, 1};
   static const int xshift[6] = {0, 1, 0, 0, 1, 1};

   int vi = 0;
   for (int i = 0; i < 3; i++)
   {
      for (int j = 0; j < 3; j++)
      {
         for (int v = 0; v < 6; v++)
         {
            _data[vi].x = data[xshift[v] + j].x;
            _data[vi].y = data[yshift[v] + i].y;
            _data[vi].u = data[xshift[v] + j].u;
            _data[vi].v = data[yshift[v] + i].v;
            _data[vi].col = data[0].col;
            vi++;
         }
      }
   }

   return _data;
}

void D3DObjectImage::SetupBorder(const D3DXVECTOR4 &world_border, const D3DXVECTOR4 &pix_border)
{
   _border = world_border;
   _uvborder = pix_border;
   _with_border = (_border.x > 0.0001f || _border.y > 0.0001f ||
      _border.z > 0.0001f || _border.w > 0.0001f);
}

bool D3DObjectImage::UpdateImageData(DWORD *image_data)
{
   D3DImageCache::CacheEntryInfo info = {_image_id, _width, _height, _u, _v, _du, _dv};
   _image_data_updated = false;
   return D3DImageCache::Current()->UpdateImageData(info, image_data);
}

DWORD *D3DObjectImage::GetImageData()
{
   if (_image_data_updated)
      return _image_data.Data();
   _image_data_updated = true;
   D3DImageCache::CacheEntryInfo info = {_image_id, _width, _height, _u, _v, _du, _dv};
   D3DImageCache::Current()->GetImageData(info, _image_data);
   return _image_data.Data();
}
