/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   200X/X/X - 2016/11/27
/// 
/// @brief  Win32 テキストボックスコンポーネント
/// 
/// ****************************************************************************



#include "CPL_WIN_NumBox.h"

#include <float.h>

namespace CPL {
namespace WIN {


// 静的メンバ
//WNDPROC NumBox::_NUMBOX_PROC;

// _____________________________________________________________________________
/// @brief NumBox コンストラクタ
/// 
/// @param[in] in_hdl_win_parent : 親ウィンドウのハンドルを指定します。
/// @param[in] in_pos  : 左上の座標を指定します。
/// @param[in] in_size : ウィンドウのサイズを指定します。
/// @param[in] in_win_style
///   : ウィンドウスタイルを指定します。\n
///   : 詳しくはCreateWindowで検索、dwStyleを参照してください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
NumBox::NumBox
( _in<HWND     > in_hdl_win_parent
, _in<td_Coord2> in_pos
, _in<td_Coord2> in_size
, _in<LONG_PTR > in_win_style
) : TextBox( in_hdl_win_parent, in_pos, in_size, in_win_style )
  , m_flag_integer( eNumMode::Float )
  , m_limit_d( -(double)Ei, (double)Ei )
  , m_limit_i( -Ei, Ei )
  , m_ptr_reference_d( nullptr )
  , m_ptr_reference_i( nullptr )
{
//  static int f_init = _StaticInitialize();
//  // サブクラス化
//  zzf_SetProc( _StaticProcNumBox );

  SetValue( (double)0 );
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 拡張プロシージャ
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SDEF NumBox::DefaultProc_( _in<UINT> in_message, _in<WPARAM> in_wparam, _in<LPARAM> in_lparam )
{
  if( in_message == WM_CHAR )
  {
    int code = (int)in_wparam;
    double value;
    if( code == '.' )
    {
      if( m_flag_integer != eNumMode::Float )
      {
        return true;
      }
      value = GetValue();
      if( value == 0 )
      {
        TCHAR str[64];
        ::GetWindowText( m_hdl_window, str, sizeof(str) );
//        if( ::_tcscmp( str, SwS("0") ) )
        if( Core::String::str_cmp_( str, SwS("0") ) )
        {
          ::SetWindowText( m_hdl_window, SwS("0.") );
          return true;
        }
      }
    }
    else
    if( code == '+' )
    {
      value = GetValue();
      if( value < 0 )
      {
        SetValue( -value );
      }
      return true;
    }
    else
    if( code == '-' )
    {
      value = GetValue();
      if( value > 0 )
      {
        SetValue( -value );
      }
      return true;
    }
    else
    if( !('0' <= code  &&  code <= '9')  &&  code != VK_BACK )
    {
      if( !(m_flag_integer == eNumMode::IntX
       &&  (('a' <= code  &&  code <= 'f')  ||  ('A' <= code  &&  code <= 'F'))) )
      {
        return true;
      }
    }
  }
  else
  if( in_message == WM_IME_NOTIFY )
  {
    if( in_wparam == IMN_SETCOMPOSITIONWINDOW ) // 内容が変更された
    {
//      SetValue( GetValue() ); // 制限値補正のためSetValue()呼び出し
      TCHAR str[128];
      ::GetWindowText( m_hdl_window, str, CPL_ARRAY_NUM(str) );
      if( m_flag_integer == 0 ) // 実数モード
      {
        double num = (double)SwS_toF( str );
        if( num < m_limit_d.x  ||  num > m_limit_d.y )
        {
          ::SetTimer( m_hdl_window, 1, 1000, nullptr );
        }

        // データの自動変更
        if( m_ptr_reference_d )
        {
          *m_ptr_reference_d = Max( Min( m_limit_d.y, num ), m_limit_d.x );
        }
      }
      else // 整数モード
      {
        S64 num = SwS_toI( str );
        if( num < m_limit_i.x  ||  num > m_limit_i.y )
        {
          ::SetTimer( m_hdl_window, 1, 1000, nullptr );
        }
        // データの自動変更
        if( m_ptr_reference_i )
        {
          *m_ptr_reference_i = Max( Min( m_limit_i.y, num ), m_limit_i.x );
        }
      }
    }
  }
  else
  if( in_message == WM_TIMER )
  {
    SetValue( GetValue() ); // 制限値補正のためSetValue()呼び出し
    ::KillTimer( m_hdl_window, 1 );
  }
  return TextBox::DefaultProc_( in_message, in_wparam, in_lparam );
}


// _____________________________________________________________________________
/// @brief 数値の取得
/// 
/// @return 現在の値を返します。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
double NumBox::GetValue() const
{
  double val;
  GetValue( val );
  return val;
}

// _____________________________________________________________________________
/// @brief 数値の取得
/// 
/// @param[in] out_value : 取得用のスカラー変数を指定します。
/// 
/// @return なし。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NumBox::GetValue( rout<S32> out_value ) const
{
  TCHAR str[128];
  ::GetWindowText( m_hdl_window, str, CPL_ARRAY_NUM(str) );
  out_value =
    Clamp<S32>( (S32)SwS_toI( str ), (S32)m_limit_i.x, (S32)m_limit_i.y );
}
/// @copydoc CPL::WIN::NumBox::GetValue(rout<S32>)const
void NumBox::GetValue( rout<S64> out_value ) const
{
  TCHAR str[128];
  ::GetWindowText( m_hdl_window, str, CPL_ARRAY_NUM(str) );
  out_value =
    Clamp<S64>
      ( SwS_toI64( str )
      , m_limit_i.x
      , m_limit_i.y
      );
}
/// @copydoc CPL::WIN::NumBox::GetValue(rout<S32>)const
void NumBox::GetValue( rout<double> out_value ) const
{
  TCHAR str[128];
  ::GetWindowText( m_hdl_window, str, CPL_ARRAY_NUM(str) );
  out_value =
    Clamp<double>
      ( SwS_toF( str )
      , m_limit_d.x
      , m_limit_d.y
      );
}
/// @copydoc CPL::WIN::NumBox::GetValue(rout<S32>)const
void NumBox::GetValue( rout<float> out_value ) const
{
  TCHAR str[128];
  ::GetWindowText( m_hdl_window, str, CPL_ARRAY_NUM(str) );
  out_value =
    (float)Clamp<double>
      ( SwS_toF( str )
      , m_limit_d.x
      , m_limit_d.y
      );
}


// _____________________________________________________________________________
/// @brief 値の設定
/// 
/// @param[in] in_num : 設定する値を指定します。
/// 
/// @return なし。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NumBox::SetValue( _in<double> in_num )
{
  auto num = in_num;
  TCHAR str[64];
  if( num < m_limit_d.x )
  {
    num = m_limit_d.x;
  }
  else
  if( num > m_limit_d.y )
  {
    num = m_limit_d.y;
  }

  switch( m_flag_integer )
  {
  case eNumMode::Float: CPL_SPRINTF( str, SwS("%f"), num );    break;
  case eNumMode::Int:   CPL_SPRINTF( str, SwS("%d"), (int)num ); break;
  case eNumMode::IntX:  CPL_SPRINTF( str, SwS("%x"), (int)num ); break;
  default:
    str[0] = 0;
  }
  str[ CPL_ARRAY_NUM(str)-1 ] = 0;
  ::SetWindowText( m_hdl_window, str );
}
/// @copydoc CPL::WIN::NumBox::SetValue(_in<double>)
void NumBox::SetValue( _in<S64> in_num )
{
  auto num = in_num;
  TCHAR str[64];
  if( num < m_limit_i.x )
  {
    num = m_limit_i.x;
  }
  else
  if( num > m_limit_i.y )
  {
    num = m_limit_i.y;
  }

  switch( m_flag_integer )
  {
  case eNumMode::Float: CPL_SPRINTF( str, SwS("%f"), (float)num );    break;
  case eNumMode::Int:   CPL_SPRINTF( str, SwS("%I64d"), num ); break;
  case eNumMode::IntX:  CPL_SPRINTF( str, SwS("%I64x"), num ); break;
  default:
    str[0] = 0;
  }
  str[ CPL_ARRAY_NUM(str)-1 ] = 0;
  ::SetWindowText( m_hdl_window, str );
}


// _____________________________________________________________________________
/// @brief 整数モード切替
/// 
/// @param[in] in_mode
///   : 数値モードを指定します。詳しくはeNumModeを参照してください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NumBox::SetIntegerMode( eNumMode in_mode )
{
  m_flag_integer = in_mode;
  SetValue( GetValue() );
}


// _____________________________________________________________________________
/// @brief 最小値設定
/// 
/// @param[in] in_min : 最小値を指定します。
/// 
/// @return なし。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NumBox::SetMin( _in<double> in_min )
{
  double value = GetValue();
  m_limit_d.x = in_min;
  if( value < in_min )
  {
    SetValue( in_min );
  }
}
/// @copydoc CPL::WIN::NumBox::SetMin(_in<double>)
void NumBox::SetMin( _in<S64> in_min )
{
  double value = GetValue();
  m_limit_i.x = in_min;
  if( value < in_min )
  {
    SetValue( in_min );
  }
}


// _____________________________________________________________________________
/// @brief 最大値設定
/// 
/// @param[in] in_max : 最大値を指定します。
/// 
/// @return なし。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void NumBox::SetMax( _in<double> in_max )
{
  double value = GetValue();
  m_limit_d.y = in_max;
  if( value > in_max )
  {
    SetValue( in_max );
  }
}
/// @copydoc CPL::WIN::NumBox::SetMax(_in<double>)
void NumBox::SetMax( _in<S64> in_max )
{
  double value = GetValue();
  m_limit_i.y = in_max;
  if( value > in_max )
  {
    SetValue( in_max );
  }
}


// _____________________________________________________________________________
/// @copydoc CPL::WIN::Component::CreateClone
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Component*
  NumBox::CreateClone
    ( _in<HWND> in_hdl_win_parent
    , _in<int > in_offs_x
    , _in<int > in_offs_y
    ) const
{
  try{
    auto hdl_win_parent = in_hdl_win_parent;
    Rect rt;
    LONG_PTR style = this->GetStyle();
    this->GetWindowRect( rt );
    if( hdl_win_parent == nullptr )
    {
      hdl_win_parent = this->GetParentWindowHandle();
    }

    NumBox* ptr_num_box =
      CPL_NEW(NumBox)
        ( hdl_win_parent
        , td_Coord2( int(rt.left + in_offs_x), int(rt.top + in_offs_y) )
        , td_Coord2( int(rt.GetWidth()), int(rt.GetHeight()) )
        , style
        );
    if( ptr_num_box )
    {
      ptr_num_box->SetFont( this->GetFont() );
      ptr_num_box->SetLimit( this->GetMin(), this->GetMax() );
      ptr_num_box->SetValue( this->GetValue() );
      return ptr_num_box;
    }
    else
    {
      return nullptr;
    }
  }
  catch(...)
  {
    return nullptr;
  }
}


} // namespace WIN
} // namespace CPL