/*  RetroArch - A frontend for libretro.
 *  Copyright (C) 2011-2018 - Daniel De Matteis
 *  Copyright (C) 2014-2018 - Ali Bouhlel
 *
 *  RetroArch is free software: you can redistribute it and/or modify it under the terms
 *  of the GNU General Public License as published by the Free Software Found-
 *  ation, either version 3 of the License, or (at your option) any later version.
 *
 *  RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 *  without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 *  PURPOSE.  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with RetroArch.
 *  If not, see <http://www.gnu.org/licenses/>.
 */

#define CINTERFACE

#include <retro_miscellaneous.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "../menu_driver.h"

#include "../../retroarch.h"
#include "../../gfx/font_driver.h"
#include "../../gfx/video_driver.h"
#include "../../gfx/common/d3d10_common.h"

static const float* menu_display_d3d10_get_default_vertices(void)
{
   return NULL;
}

static const float* menu_display_d3d10_get_default_tex_coords(void)
{
   return NULL;
}

static void* menu_display_d3d10_get_default_mvp(video_frame_info_t *video_info)
{
   return NULL;
}

static void menu_display_d3d10_blend_begin(video_frame_info_t *video_info)
{
   d3d10_video_t* d3d10 = video_info ? (d3d10_video_t*)video_info->userdata : NULL;
   D3D10SetBlendState(d3d10->device,
         d3d10->blend_enable, NULL, D3D10_DEFAULT_SAMPLE_MASK);
}

static void menu_display_d3d10_blend_end(video_frame_info_t *video_info)
{
   d3d10_video_t* d3d10 = video_info ? (d3d10_video_t*)video_info->userdata : NULL;
   D3D10SetBlendState(d3d10->device,
         d3d10->blend_disable, NULL, D3D10_DEFAULT_SAMPLE_MASK);
}

static void menu_display_d3d10_viewport(void* data, video_frame_info_t *video_info)
{
}

static void menu_display_d3d10_draw(void* data, video_frame_info_t *video_info)
{
   int                      vertex_count;
   d3d10_video_t*           d3d10 = video_info ? (d3d10_video_t*)video_info->userdata : NULL;
   menu_display_ctx_draw_t* draw  = (menu_display_ctx_draw_t*)data;

   if (!d3d10 || !draw || !draw->texture)
      return;

   switch (draw->pipeline.id)
   {
      case VIDEO_SHADER_MENU:
      case VIDEO_SHADER_MENU_2:
      case VIDEO_SHADER_MENU_3:
      case VIDEO_SHADER_MENU_4:
      case VIDEO_SHADER_MENU_5:
      case VIDEO_SHADER_MENU_6:
         d3d10_set_shader(d3d10->device, &d3d10->shaders[draw->pipeline.id]);
         D3D10Draw(d3d10->device, draw->coords->vertices, 0);

         D3D10SetBlendState(d3d10->device, d3d10->blend_enable, NULL, D3D10_DEFAULT_SAMPLE_MASK);
         d3d10_set_shader(d3d10->device, &d3d10->sprites.shader);
         D3D10SetVertexBuffer(d3d10->device, 0, d3d10->sprites.vbo, sizeof(d3d10_sprite_t), 0);
         D3D10SetPrimitiveTopology(d3d10->device, D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);
         return;
   }

   if (draw->coords->vertex && draw->coords->tex_coord && draw->coords->color)
      vertex_count = draw->coords->vertices;
   else
      vertex_count = 1;

   if (!d3d10->sprites.enabled || vertex_count > d3d10->sprites.capacity)
      return;

   if (d3d10->sprites.offset + vertex_count > d3d10->sprites.capacity)
      d3d10->sprites.offset = 0;

   {
      void*           mapped_vbo;
      d3d10_sprite_t* sprite = NULL;

      D3D10MapBuffer(d3d10->sprites.vbo, D3D10_MAP_WRITE_NO_OVERWRITE, 0, &mapped_vbo);

      sprite = (d3d10_sprite_t*)mapped_vbo + d3d10->sprites.offset;

      if (vertex_count == 1)
      {
         sprite->pos.x = draw->x / (float)d3d10->viewport.Width;
         sprite->pos.y =
               (d3d10->viewport.Height - draw->y - draw->height) / (float)d3d10->viewport.Height;
         sprite->pos.w = draw->width / (float)d3d10->viewport.Width;
         sprite->pos.h = draw->height / (float)d3d10->viewport.Height;

         sprite->coords.u = 0.0f;
         sprite->coords.v = 0.0f;
         sprite->coords.w = 1.0f;
         sprite->coords.h = 1.0f;

         if (draw->scale_factor)
            sprite->params.scaling = draw->scale_factor;
         else
            sprite->params.scaling = 1.0f;

         sprite->params.rotation = draw->rotation;

         sprite->colors[3] = DXGI_COLOR_RGBA(
               0xFF * draw->coords->color[0], 0xFF * draw->coords->color[1],
               0xFF * draw->coords->color[2], 0xFF * draw->coords->color[3]);
         sprite->colors[2] = DXGI_COLOR_RGBA(
               0xFF * draw->coords->color[4], 0xFF * draw->coords->color[5],
               0xFF * draw->coords->color[6], 0xFF * draw->coords->color[7]);
         sprite->colors[1] = DXGI_COLOR_RGBA(
               0xFF * draw->coords->color[8], 0xFF * draw->coords->color[9],
               0xFF * draw->coords->color[10], 0xFF * draw->coords->color[11]);
         sprite->colors[0] = DXGI_COLOR_RGBA(
               0xFF * draw->coords->color[12], 0xFF * draw->coords->color[13],
               0xFF * draw->coords->color[14], 0xFF * draw->coords->color[15]);
      }
      else
      {
         int          i;
         const float* vertex    = draw->coords->vertex;
         const float* tex_coord = draw->coords->tex_coord;
         const float* color     = draw->coords->color;

         for (i = 0; i < vertex_count; i++)
         {
            d3d10_vertex_t* v = (d3d10_vertex_t*)sprite;
            v->position[0]    = *vertex++;
            v->position[1]    = *vertex++;
            v->texcoord[0]    = *tex_coord++;
            v->texcoord[1]    = *tex_coord++;
            v->color[0]       = *color++;
            v->color[1]       = *color++;
            v->color[2]       = *color++;
            v->color[3]       = *color++;

            sprite++;
         }

         d3d10_set_shader(d3d10->device, &d3d10->shaders[VIDEO_SHADER_STOCK_BLEND]);
         D3D10SetPrimitiveTopology(d3d10->device, D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);
      }

      D3D10UnmapBuffer(d3d10->sprites.vbo);
   }

   d3d10_set_texture_and_sampler(d3d10->device, 0, (d3d10_texture_t*)draw->texture);
   D3D10Draw(d3d10->device, vertex_count, d3d10->sprites.offset);
   d3d10->sprites.offset += vertex_count;

   if (vertex_count > 1)
   {
      d3d10_set_shader(d3d10->device, &d3d10->sprites.shader);
      D3D10SetPrimitiveTopology(d3d10->device, D3D10_PRIMITIVE_TOPOLOGY_POINTLIST);
   }

   return;
}

static void menu_display_d3d10_draw_pipeline(void* data,
      video_frame_info_t *video_info)
{
   menu_display_ctx_draw_t* draw  = (menu_display_ctx_draw_t*)data;
   d3d10_video_t*           d3d10 = video_info ? (d3d10_video_t*)video_info->userdata : NULL;

   if (!d3d10 || !draw)
      return;

   switch (draw->pipeline.id)
   {
      case VIDEO_SHADER_MENU:
      case VIDEO_SHADER_MENU_2:
      {
         video_coord_array_t* ca = menu_display_get_coords_array();

         if (!d3d10->menu_pipeline_vbo)
         {
            D3D10_BUFFER_DESC desc = { 0 };
            desc.Usage             = D3D10_USAGE_IMMUTABLE;
            desc.ByteWidth         = ca->coords.vertices * 2 * sizeof(float);
            desc.BindFlags         = D3D10_BIND_VERTEX_BUFFER;

            D3D10_SUBRESOURCE_DATA vertexData = { ca->coords.vertex };
            D3D10CreateBuffer(d3d10->device, &desc, &vertexData, &d3d10->menu_pipeline_vbo);
         }
         D3D10SetVertexBuffer(d3d10->device, 0, d3d10->menu_pipeline_vbo, 2 * sizeof(float), 0);
         draw->coords->vertices = ca->coords.vertices;
         D3D10SetBlendState(d3d10->device, d3d10->blend_pipeline, NULL, D3D10_DEFAULT_SAMPLE_MASK);
         break;
      }

      case VIDEO_SHADER_MENU_3:
      case VIDEO_SHADER_MENU_4:
      case VIDEO_SHADER_MENU_5:
      case VIDEO_SHADER_MENU_6:
         D3D10SetVertexBuffer(d3d10->device, 0, d3d10->frame.vbo, sizeof(d3d10_vertex_t), 0);
         draw->coords->vertices = 4;
         break;
      default:
         return;
   }

   D3D10SetPrimitiveTopology(d3d10->device, D3D10_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP);

   d3d10->ubo_values.time += 0.01f;

   {
      void* mapped_ubo;
      D3D10MapBuffer(d3d10->ubo, D3D10_MAP_WRITE_DISCARD, 0, (void**)&mapped_ubo);
      *(d3d10_uniform_t*)mapped_ubo = d3d10->ubo_values;
      D3D10UnmapBuffer(d3d10->ubo);
   }
}

static void menu_display_d3d10_restore_clear_color(void) {}

static void menu_display_d3d10_clear_color(
      menu_display_ctx_clearcolor_t* clearcolor,
      video_frame_info_t *video_info)
{
   d3d10_video_t *d3d10 = video_info ? 
      (d3d10_video_t*)video_info->userdata : NULL;

   if (!d3d10 || !clearcolor)
      return;

   D3D10ClearRenderTargetView(d3d10->device,
         d3d10->renderTargetView, (float*)clearcolor);
}

static bool menu_display_d3d10_font_init_first(
      void**      font_handle,
      void*       video_data,
      const char* font_path,
      float       font_size,
      bool        is_threaded)
{
   font_data_t** handle     = (font_data_t**)font_handle;
   font_data_t*  new_handle = font_driver_init_first(
         video_data, font_path, font_size, true,
         is_threaded, FONT_DRIVER_RENDER_D3D10_API);
   if (!new_handle)
      return false;
   *handle = new_handle;
   return true;
}

menu_display_ctx_driver_t menu_display_ctx_d3d10 = {
   menu_display_d3d10_draw,
   menu_display_d3d10_draw_pipeline,
   menu_display_d3d10_viewport,
   menu_display_d3d10_blend_begin,
   menu_display_d3d10_blend_end,
   menu_display_d3d10_restore_clear_color,
   menu_display_d3d10_clear_color,
   menu_display_d3d10_get_default_mvp,
   menu_display_d3d10_get_default_vertices,
   menu_display_d3d10_get_default_tex_coords,
   menu_display_d3d10_font_init_first,
   MENU_VIDEO_DRIVER_DIRECT3D10,
   "menu_display_d3d10",
   true
};
