/*
 *  Copyright (c) 2013 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */
#include "webrtc/test/win/d3d_renderer.h"

#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"

namespace webrtc {
namespace test {

#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ|D3DFVF_TEX1)

struct D3dCustomVertex {
  float x, y, z;
  float u, v;
};

const char kD3DClassName[] = "d3d_renderer";

VideoRenderer* VideoRenderer::CreatePlatformRenderer(const char* window_title,
                                                     size_t width,
                                                     size_t height) {
  return D3dRenderer::Create(window_title, width, height);
}

D3dRenderer::D3dRenderer(size_t width, size_t height)
    : width_(width),
      height_(height),
      hwnd_(NULL),
      d3d_(NULL),
      d3d_device_(NULL),
      texture_(NULL),
      vertex_buffer_(NULL) {
  assert(width > 0);
  assert(height > 0);
}

D3dRenderer::~D3dRenderer() { Destroy(); }

LRESULT WINAPI D3dRenderer::WindowProc(HWND hwnd, UINT msg, WPARAM wparam,
                                       LPARAM lparam) {
  if (msg == WM_DESTROY || (msg == WM_CHAR && wparam == VK_RETURN)) {
    PostQuitMessage(0);
    return 0;
  }

  return DefWindowProcA(hwnd, msg, wparam, lparam);
}

void D3dRenderer::Destroy() {
  texture_ = NULL;
  vertex_buffer_ = NULL;
  d3d_device_ = NULL;
  d3d_ = NULL;

  if (hwnd_ != NULL) {
    DestroyWindow(hwnd_);
    assert(!IsWindow(hwnd_));
    hwnd_ = NULL;
  }
}

bool D3dRenderer::Init(const char* window_title) {
  hwnd_ = CreateWindowA(kD3DClassName,
                        window_title,
                        WS_OVERLAPPEDWINDOW,
                        0,
                        0,
                        static_cast<int>(width_),
                        static_cast<int>(height_),
                        NULL,
                        NULL,
                        NULL,
                        NULL);

  if (hwnd_ == NULL) {
    Destroy();
    return false;
  }

  d3d_ = Direct3DCreate9(D3D_SDK_VERSION);
  if (d3d_ == NULL) {
    Destroy();
    return false;
  }

  D3DPRESENT_PARAMETERS d3d_params = {};

  d3d_params.Windowed = TRUE;
  d3d_params.SwapEffect = D3DSWAPEFFECT_COPY;

  IDirect3DDevice9* d3d_device;
  if (d3d_->CreateDevice(D3DADAPTER_DEFAULT,
                         D3DDEVTYPE_HAL,
                         hwnd_,
                         D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                         &d3d_params,
                         &d3d_device) != D3D_OK) {
    Destroy();
    return false;
  }
  d3d_device_ = d3d_device;
  d3d_device->Release();

  IDirect3DVertexBuffer9* vertex_buffer;
  const int kRectVertices = 4;
  if (d3d_device_->CreateVertexBuffer(kRectVertices * sizeof(D3dCustomVertex),
                                      0,
                                      D3DFVF_CUSTOMVERTEX,
                                      D3DPOOL_MANAGED,
                                      &vertex_buffer,
                                      NULL) != D3D_OK) {
    Destroy();
    return false;
  }
  vertex_buffer_ = vertex_buffer;
  vertex_buffer->Release();

  d3d_device_->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
  d3d_device_->SetRenderState(D3DRS_LIGHTING, FALSE);
  Resize(width_, height_);

  ShowWindow(hwnd_, SW_SHOWNOACTIVATE);
  d3d_device_->Present(NULL, NULL, NULL, NULL);

  return true;
}

D3dRenderer* D3dRenderer::Create(const char* window_title,
                                 size_t width,
                                 size_t height) {
  static ATOM wc_atom = 0;
  if (wc_atom == 0) {
    WNDCLASSA wc = {};

    wc.style = CS_HREDRAW | CS_VREDRAW;
    wc.lpfnWndProc = WindowProc;
    wc.hCursor = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = reinterpret_cast<HBRUSH>(COLOR_WINDOW);
    wc.lpszClassName = kD3DClassName;

    wc_atom = RegisterClassA(&wc);
    if (wc_atom == 0)
      return false;
  }

  D3dRenderer* d3d_renderer = new D3dRenderer(width, height);
  if (!d3d_renderer->Init(window_title)) {
    delete d3d_renderer;
    return NULL;
  }

  return d3d_renderer;
}

void D3dRenderer::Resize(size_t width, size_t height) {
  width_ = width;
  height_ = height;
  IDirect3DTexture9* texture;

  d3d_device_->CreateTexture(static_cast<UINT>(width_),
                             static_cast<UINT>(height_),
                             1,
                             0,
                             D3DFMT_A8R8G8B8,
                             D3DPOOL_MANAGED,
                             &texture,
                             NULL);
  texture_ = texture;
  texture->Release();

  // Vertices for the video frame to be rendered to.
  static const D3dCustomVertex rect[] = {
    {-1.0f, -1.0f, 0.0f, 0.0f, 1.0f},
    {-1.0f, 1.0f, 0.0f, 0.0f, 0.0f},
    {1.0f, -1.0f, 0.0f, 1.0f, 1.0f},
    {1.0f, 1.0f, 0.0f, 1.0f, 0.0f},
  };

  void* buf_data;
  if (vertex_buffer_->Lock(0, 0, &buf_data, 0) != D3D_OK)
    return;

  memcpy(buf_data, &rect, sizeof(rect));
  vertex_buffer_->Unlock();
}

void D3dRenderer::RenderFrame(const webrtc::I420VideoFrame& frame,
                              int /*render_delay_ms*/) {
  if (static_cast<size_t>(frame.width()) != width_ ||
      static_cast<size_t>(frame.height()) != height_) {
    Resize(static_cast<size_t>(frame.width()),
           static_cast<size_t>(frame.height()));
  }

  D3DLOCKED_RECT lock_rect;
  if (texture_->LockRect(0, &lock_rect, NULL, 0) != D3D_OK)
    return;

  ConvertFromI420(frame, kARGB, 0, static_cast<uint8_t*>(lock_rect.pBits));
  texture_->UnlockRect(0);

  d3d_device_->BeginScene();
  d3d_device_->SetFVF(D3DFVF_CUSTOMVERTEX);
  d3d_device_->SetStreamSource(0, vertex_buffer_, 0, sizeof(D3dCustomVertex));
  d3d_device_->SetTexture(0, texture_);
  d3d_device_->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);
  d3d_device_->EndScene();

  d3d_device_->Present(NULL, NULL, NULL, NULL);
}
}  // namespace test
}  // namespace webrtc
