/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2008?/X/X - 2014/03/10
/// 
/// @brief  DirectX用ウィンドウ管理
/// 
/// ****************************************************************************

#include "CPL_DX_Window.h"
#include "CPL_DX_Graphics.h"
#include "CPL_DX_Factory.h"
#include "CPL_DX_Exception.h"


namespace CPL {
namespace DX {


// _____________________________________________________________________________
/// @brief コンストラクタ
/// 
/// @param[in] in_sp_graphics
///   : グラフィックスオブジェクトを指すスマートポインタを指定します。
/// @param[in] in_sp_input
///   : DirectInput用のInputオブジェクトを指すスマートポインタを指定します。\n
///   : 使用しない場合、nullptrを指定可能です。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Window::Window
( _in<sdptr<Graphics_i_>> in_sp_graphics
, _in<sdptr<Input      >> in_sp_input
)
{
  m_sp_graphics = in_sp_graphics;
  m_sp_input    = in_sp_input;

  //m_swap_effect    = D3DSWAPEFFECT_DISCARD;
  m_flag_depth_clear   = 0;
  m_flag_stencil_clear = 0;

  in_sp_graphics->AddWindow( this ); // in_ptr_graphics==nullptr then throw
}


// _____________________________________________________________________________
/// @brief デストラクタ
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Window::~Window()
{
  // 破棄されたことを通知する
//  GetGraphics()->SendDestroyWindow( this );
}



// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// DirectX用プロシージャ
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SDEF Window::DefaultProc_( _in<UINT> in_message, _in<WPARAM> in_wparam, _in<LPARAM> in_lparam )
{
  switch( in_message )
  {
  case WM_SIZE:
    if( in_wparam != SIZE_MINIMIZED )
    {
      WIN::Rect rt;
      this->GetClientRect( rt );

      float width = static_cast<float>( rt.GetWidth() );
      float height = static_cast<float>( rt.GetHeight() );
      m_sp_viewport->SetWH( width, height );
      m_sp_projection->SetAspect( width, height );
      m_sp_projection->UpdateMatrixLH();

      if( Graphics::GetDevice()  &&  m_cp_swap_chain )
      {
        // バックバッファの作り直し
        RemakeBackBuffer( int2( (int)width, (int)height ) );
      }
      return WIN::Window::DefaultProc_( in_message, in_wparam, in_lparam );
    }

  case WM_PAINT:
    return DxDraw();

  case WM_MOUSEMOVE:
  case WM_LBUTTONDOWN:
  case WM_RBUTTONDOWN:
  case WM_LBUTTONUP:
  case WM_RBUTTONUP:
  case WM_KEYDOWN:
  case WM_KEYUP:
    if( m_sp_input )
    {
      return false;
    }

  default:
    return WIN::Window::DefaultProc_( in_message, in_wparam, in_lparam );
  }
}


//// _____________________________________________________________________________
//// プレゼントパラメータ取得
//// _____________________________________________________________________________
//HRESULT Window::GetDxPresentParameters( D3DPRESENT_PARAMETERS* in_ptr_d3d_pp ) const {
//  if( m_cp_swap_chain )
//  {
//    return m_cp_swap_chain->GetPresentParameters( in_ptr_d3d_pp );
//  }
//  else
//  {
//    return D3DERR_INVALIDCALL;
//  }
//}


// _____________________________________________________________________________
/// @brief 描画用のコールバック関数
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SDEF Window::DxDraw()
{
  if( !m_cp_swap_chain ){ return false; }

  CPL_DBG_OUTPUT_RETURN_VALUE_IF
    ( !GetGraphics()->GetDevice()
    , 0
    , SwS(CPL_DX_MSG_NOT_DEVICE)
    );

  auto& cp_d3d_IC = GetGraphics()->GetImmediateContext();
  CPL_DBG_OUTPUT_RETURN_VALUE_IF
    ( !cp_d3d_IC
    , 0
    , SwS("Failed to Immediate context.\n")
    );

  // レンダリングターゲットの設定
  cp_d3d_IC->OMSetRenderTargets
    ( 1
    , &this->m_cp_render_target_view
    , this->m_cp_depth_stencil_view
    );

  // 情報取得
//  ptr_surface->GetDesc( &desc );
//  D3DCOLOR color = pView ? pView->GetBackColor() : D3DCOLOR_XRGB(0,0,0);
  SDEF ret = 0;

  // フレーム毎に呼び出すコールバック関数。
  On_FrameMove();

  // 描画設定コール
  m_render_collection.RenderSetup( cp_d3d_IC );

  // ターゲットビュークリア
  COLORREF bg_color = GetBgColor();
  float const r = GetR( bg_color ) / 255.0f;
  float const g = GetG( bg_color ) / 255.0f;
  float const b = GetB( bg_color ) / 255.0f;
  float4 clear_color( r,g,b,1 ); // クリアカラー
  cp_d3d_IC->ClearRenderTargetView
    ( this->m_cp_render_target_view
    , &clear_color.x
    );
  // 深度バッファクリア
  cp_d3d_IC->ClearDepthStencilView
    ( this->m_cp_depth_stencil_view
    , D3D11_CLEAR_DEPTH
    , 1.0f
    , 0
    );

  // 描画コールバック関数。
  ret = On_Paint();

  // 
  this->m_cp_swap_chain->Present( 0, 0 );

  ValidateRect( m_hdl_window, nullptr );

  return ret;
}


// _____________________________________________________________________________
/// @brief バックバッファサイズを取得
/// 
/// @return バックバッファのサイズを返します。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
u_short2 Window::GetBackBufferSize() const
{
  if( m_cp_swap_chain )
  {
    // バックバッファ取得
    DXGI_SWAP_CHAIN_DESC sc_desc;
    HRESULT hr =
      const_cast<Window*>(this)->m_cp_swap_chain->GetDesc( &sc_desc );
    if( FAILED(hr) )
    {
      return u_short2( 0, 0 );
    }
    return
      u_short2
        ( static_cast<u_short>(sc_desc.BufferDesc.Width)
        , static_cast<u_short>(sc_desc.BufferDesc.Height)
        );
  }
  else
  {
    return u_short2( 0, 0 );
  }
}


// _____________________________________________________________________________
/// @brief 指定サイズが現在のバックバッファサイズ内に収まるかチェックする
/// 
/// @param[in] in_size
///   : バックバッファのサイズを指定します。
/// 
/// @retval true  : 現在のバッファサイズ以下
/// @retval false : バッファサイズ変更の必要あり
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool Window::CheckBackBufferSize( _in<u_int2> in_size ) const
{
  // バックバッファサイズを取得
  u_short2 back_buf_size = GetBackBufferSize();

  if( in_size.x <= back_buf_size.x  &&  in_size.y <= back_buf_size.y )
  {
    return true;
  }
  return false;
}


// _____________________________________________________________________________
/// @brief バッファサイズ変更
/// 
/// 新しいスワップチェインを作成し、古いものと入れ替えます。
/// 
/// @param[in] in_size : 
/// 
/// @retval S_OK : サイズ変更成功。
/// @retval E_FAIL : サイズ変更失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
HRESULT Window::RemakeBackBuffer( _in<u_int2> in_size )
{
  try {
    // スワップチェインポインタチェック
    CPL_THROW_HRESULT_IF
      ( !m_cp_swap_chain
      , E_POINTER
      , "The SwapChain is invalid.\n"
      );
    // 引数チェック
    CPL_THROW_HRESULT_IF
      ( in_size.x <= 0  ||  in_size.y <= 0
      , E_INVALIDARG
      , "Invalid argument.\n"
      );

    // 現在のスワップチェーンの情報を取得
    DXGI_SWAP_CHAIN_DESC desc_SC;
    HRESULT hr = m_cp_swap_chain->GetDesc( &desc_SC );
    CPL_THROW_FAILED
      ( hr, "Failed to get the SwapChain description.\n" );
    if( in_size.x == desc_SC.BufferDesc.Width  &&
        in_size.y == desc_SC.BufferDesc.Height )
    {
      return S_OK; // サイズ変更なし。
    }
    UINT width  = in_size.x;
    UINT height = in_size.y;
    auto& cp_d3d_dev = Graphics_i_::GetDevice();

    //if( in_size.x > desc_SC.BufferDesc.Width  ||
    //    in_size.y > desc_SC.BufferDesc.Height )
    //{
    //  // スワップチェイン再作成
    //  m_cp_swap_chain =
    //    CreateDefaultSwapChain( cp_d3d_dev, width, height );

    //  // レンダリングターゲットビューの再作成
    //  m_cp_render_target_view =
    //    CreateDefaultRenderTargetView( cp_d3d_dev, m_cp_swap_chain );

    //  // 深度ステンシルビューの再作成
    //  m_cp_depth_stencil_view =
    //    CreateDefaultDepthStencilView( cp_d3d_dev, width, height );
    //}
    //else
    //{
    m_cp_render_target_view = nullptr;
    // スワップチェインのリサイズ。
    //auto res =
      m_cp_swap_chain->ResizeBuffers
        ( desc_SC.BufferCount
        , width
        , height
        , desc_SC.BufferDesc.Format
        , desc_SC.Flags
        );

      //desc_SC.BufferDesc.Width  = width;
      //desc_SC.BufferDesc.Height = height;
      //m_cp_swap_chain->ResizeTarget( desc_SC.BufferDesc );

      // レンダリングターゲットビューの再作成
      m_cp_render_target_view =
        CreateDefaultRenderTargetView( cp_d3d_dev, m_cp_swap_chain );

      // 深度ステンシルビューの再作成
      m_cp_depth_stencil_view =
        CreateDefaultDepthStencilView( cp_d3d_dev, width, height );
    //}

    return S_OK;
  }
  catch( CPL::DX::ExceptionHR const& e )
  {
    CPL_OUTPUT( SwS(" - CPL::DX::Window::RemakeBackBuffer()\n") );
    return e.GetHR();
  }
  catch( std::exception )
  {
    CPL_OUTPUT( SwS(" - CPL::DX::Window::RemakeBackBuffer()\n") );
  }
  catch( ... )
  {
    CPL_OUTPUT( SwS("Some errors occurred : - CPL::DX::Window::RemakeBackBuffer()\n") );
  }

  // 失敗
  return E_FAIL;
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// フレーム更新関数
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void Window::On_FrameMove()
{
  // ビュートランスフォーム設定
  //this->SetTransformView();
}


// _____________________________________________________________________________
/// @brief Graphicsクラスの作成
/// 
/// @return Graphicsオブジェクトを指すスマートポインタが返ります。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
sdptr<DX::Graphics_i_> Window::CreateGraphics() const
{
  return CPL_MAKE_SHARED(DX::Graphics);
}


// _____________________________________________________________________________
/// @brief ウィンドウ作成
/// 
/// @copydetails CPL::WIN::Window::Create
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool
  Window::Create
    ( _in<HWND      > in_hdl_win_parent
    , _in<td_Coord2 > in_pos
    , _in<td_Coord2 > in_size
    , _in<bool      > in_flag_client_size
    , _in<WIN::STYLE> in_win_style
    )
{
  try {
    auto& cp_d3d_dev = Graphics_i_::GetDevice();
    CPL_DBG_OUTPUT_RETURN_VALUE_IF
      ( !cp_d3d_dev
      , false
      , SwS("DX device pointer is invalid.\n\t - CPL::DX::Window::Create()\n")
      );

    bool flag =
      WIN::Window::Create
        ( in_hdl_win_parent
        , in_pos
        , in_size
        , in_flag_client_size
        , in_win_style
        );
    CPL_DBG_OUTPUT_RETURN_VALUE_IF
      ( !flag
      , false
      , SwS("Failed to create Window.\n\t - CPL::DX::Window::Create()\n")
      );

    WIN::Rect rt;
    this->GetClientRect( rt );
    const int width  = rt.GetWidth();
    const int height = rt.GetHeight();

    // 描画コレクション登録
    m_render_collection
      << m_sp_viewport
      << m_sp_projection;
    
    // スワップチェイン作成
    m_cp_swap_chain =
      CreateDefaultSwapChain( cp_d3d_dev, width, height );

    // レンダリングターゲットビューの作成
    m_cp_render_target_view =
      CreateDefaultRenderTargetView( cp_d3d_dev, m_cp_swap_chain );

    // 深度ステンシルビューの作成
    m_cp_depth_stencil_view =
      CreateDefaultDepthStencilView( cp_d3d_dev, width, height );

    return true;
  }
  catch( CPL::DX::ExceptionHR )
  {
    CPL_OUTPUT( SwS(" - CPL::DX::Window::Create()\n") );
  }
  catch( ... )
  {
    CPL_OUTPUT( SwS(" - CPL::DX::Window::Create()\n") );
  }

  return false;
}


// _____________________________________________________________________________
/// @brief DirectX用にMDI子ウィンドウの作成
/// 
/// @copydetails CPL::WIN::Window::CreateMdiChild
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool
  Window::CreateMdiChild
    ( _in<HWND      > in_hdl_win_parent
    , _in<td_Coord2 > in_pos
    , _in<td_Coord2 > in_size
    , _in<bool      > in_flag_client_size
    , _in<WIN::STYLE> in_win_style
    )
{
  bool flag =
    WIN::Window::CreateMdiChild
      ( in_hdl_win_parent
      , in_pos
      , in_size
      , in_flag_client_size
      , in_win_style
      );

  return flag;
}


// _____________________________________________________________________________
/// @brief ウィンドウ作成イベント処理。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SDEF Window::On_Create()
{
  WIN::Rect rt;
  this->GetClientRect( rt );
  const int width  = rt.GetWidth();
  const int height = rt.GetHeight();

  // ビューポート作成
  m_sp_viewport =
    CPL_MAKE_SHARED
      ( Viewport, 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f );

  // プロジェクションデータ作成
  const float aspect = (float)width / (float)height;
  m_sp_projection =
    CPL_MAKE_SHARED( Projection, toRAD(60.0f), aspect, 1.0f, 1000.0f );
  m_sp_projection->UpdateMatrixLH();

  return 0;
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// デフォルト設定のスワップチェインを作成（失敗した場合は例外を投げること。）
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// _____________________________________________________________________________
/// @brief デフォルトのスワップチェインの作成
/// 
/// デフォルトの設定でスワップチェインを作成します。
/// 
/// @param[in] in_ptr_d3d_dev : DirectXデバイスのポインタを指定します。
/// @param[in] in_width  : バックバッファの横幅を指定します。
/// @param[in] in_height : バックバッファの縦幅を指定します。
/// 
/// @return 生成されたスワップチェインを指すスマートポインタを返します。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CpGISwapChain
  Window::CreateDefaultSwapChain
    ( pio<DXDevice> in_ptr_d3d_dev
    , _in<UINT    > in_width
    , _in<UINT    > in_height
    )
{
  // ファクトリ作成（失敗で例外を投げる）
  DX::Factory factory( in_ptr_d3d_dev );

  // スワップチェイン設定
  DXGI_SWAP_CHAIN_DESC desc_SC;
  ZeroMemory( &desc_SC, sizeof( desc_SC ) );
  desc_SC.BufferCount       = 1;
  desc_SC.BufferDesc.Width  = in_width;
  desc_SC.BufferDesc.Height = in_height;
  desc_SC.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
  desc_SC.BufferDesc.RefreshRate.Numerator   = 60;
  desc_SC.BufferDesc.RefreshRate.Denominator = 1;
  desc_SC.BufferUsage        = DXGI_USAGE_RENDER_TARGET_OUTPUT;
  desc_SC.OutputWindow       = GetWindowHandle();
  desc_SC.SampleDesc.Count   = 1;
  desc_SC.SampleDesc.Quality = 0;
  desc_SC.Windowed           = TRUE;

  // スワップチェイン作成
  CpGISwapChain cp_swap_chain;
  HRESULT hr =
    factory->CreateSwapChain
      ( in_ptr_d3d_dev
      , &desc_SC
      , &cp_swap_chain
      );
  if( FAILED(hr) )
  {
    CPL_OUTPUT_A("Failed to create a SwapChain.\n");
    char_cptr msg = GetErrorMessage( hr );
    if( msg )
    {
      CPL_OUTPUT_A( "  - %s\n", msg );
    }
    CPL_THROW_HRESULT( hr, "  - CPL::DX::Window::CreateDefaultSwapChain()\n" );
  }

  return cp_swap_chain;
}


// _____________________________________________________________________________
/// @brief デフォルトの描画ターゲットビューの作成
/// 
/// @param[in,out] in_ptr_d3d_dev : DirectXデバイスのポインタを指定します。
/// @param[in,out] in_ptr_swap_chain : スワップチェインのポインタを指定します。
/// 
/// @return 生成された描画ターゲットビューを指すスマートポインタを返します。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CpDXRenderTargetView
  Window::CreateDefaultRenderTargetView
    ( pio<DXDevice   > in_ptr_d3d_dev
    , pio<GISwapChain> in_ptr_swap_chain
    )
{
  // バッファ取得
  CpDXTexture2D cp_back_buffer;
  HRESULT hr =
    in_ptr_swap_chain->GetBuffer
      ( 0
      , __uuidof(ID3D11Texture2D)
      , reinterpret_cast<void**>(&cp_back_buffer)
      );
  CPL_THROW_FAILED
    ( hr
    , "Failed to get the SwapChain buffer.\n"
      "CorePLi :  - CPL::DX::Window::CreateDefaultRenderTargetView()\n"
    );

  // 取得したバッファから描画ターゲットビューを作成
  CpDXRenderTargetView sp_render_target_view;
  hr =
    in_ptr_d3d_dev->CreateRenderTargetView
      ( cp_back_buffer
      , nullptr
      , &sp_render_target_view
      );
  CPL_THROW_FAILED
    ( hr
    , "Failed to create a RenderTargetView.\n"
      "CorePLi :  - CPL::DX::Window::CreateDefaultRenderTargetView()\n"
    );

  return sp_render_target_view;
}


// _____________________________________________________________________________
/// @brief 深度ステンシルビューの作成
/// 
/// @param[in] in_ptr_d3d_dev : DirectXデバイスのポインタを指定します。
/// @param[in] in_width  : 深度ステンシルビューの横幅を指定します。
/// @param[in] in_height : 深度ステンシルビューの縦幅を指定します。
/// 
/// @return 生成された深度ステンシルビューを指すスマートポインタを返します。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
CpDXDepthStencilView
  Window::CreateDefaultDepthStencilView
    ( pio<DXDevice> in_ptr_d3d_dev
    , _in<UINT    > in_width
    , _in<UINT    > in_height
    )
{
  // 深度ステンシルバッファ（テクスチャ）を作成
  D3D11_TEXTURE2D_DESC desc_DSB;
  ZeroMemory( &desc_DSB, sizeof(desc_DSB) );
  desc_DSB.Width              = in_width;
  desc_DSB.Height             = in_height;
  desc_DSB.MipLevels          = 1;
  desc_DSB.ArraySize          = 1;
  desc_DSB.Format             = DXGI_FORMAT_D24_UNORM_S8_UINT;
  desc_DSB.SampleDesc.Count   = 1;
  desc_DSB.SampleDesc.Quality = 0;
  desc_DSB.Usage              = D3D11_USAGE_DEFAULT;
  desc_DSB.BindFlags          = D3D11_BIND_DEPTH_STENCIL;
  desc_DSB.CPUAccessFlags     = 0;
  desc_DSB.MiscFlags          = 0;
  CpDXTexture2D sp_DSB;
  HRESULT hr =
    in_ptr_d3d_dev->CreateTexture2D
      ( &desc_DSB
      , nullptr
      , &sp_DSB
      );
  CPL_THROW_FAILED
    ( hr
    , "Failed to create a DepthStencilBuffer.\n"
      "CorePLi :  - CPL::DX::Window::CreateDefaultDepthStencilView()\n"
    );

  // 深度ステンシルビューの作成
  CpDXDepthStencilView cp_DSV;
  D3D11_DEPTH_STENCIL_VIEW_DESC desc_DSV;

  ZeroMemory( &desc_DSV, sizeof(desc_DSV) );
  desc_DSV.Format             = desc_DSB.Format;
  desc_DSV.ViewDimension      = D3D11_DSV_DIMENSION_TEXTURE2D;
  desc_DSV.Texture2D.MipSlice = 0;
  hr =
    in_ptr_d3d_dev->CreateDepthStencilView
      ( sp_DSB
      , &desc_DSV
      , &cp_DSV
      );
  CPL_THROW_FAILED
    ( hr
    , "Failed to create a DepthStencilView.\n"
      "CorePLi :  - CPL::DX::Window::CreateDefaultDepthStencilView()\n"
    );

  return cp_DSV;
}



} // namespace DX
} // namespace CPL

