/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2015/03/20 - 2015/11/11
/// 
/// @brief  プロセス間メモリ転送クラス（Interactive）
/// 
/// ****************************************************************************

#include "CPL_Core_PipeController.h"


namespace CPL {
namespace Core {


// _____________________________________________________________________________
/// @brief 転送パイプのヘッダ情報を取得。
/// 
/// @param[in] out_pipe_info
///   : データをコピーするためのPipeHeader変数を指定する。
/// 
/// @retval  1 : 成功。
/// @retval  0 : 失敗。
/// @return -1 : 共有メモリが無効である。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int
  PipeController::GetPipeInfo
    ( rout<PipeInfo> out_pipe_info
    ) const
{
  if( m_sp_ctrl_buffer == nullptr )
  {
    CPL_OUTPUT( SwS("Invalid shared buffer.") );
    return -1;
  }

  auto res =
    m_sp_ctrl_buffer->Read
      ( 0
      , &out_pipe_info
      , sizeof(out_pipe_info)
      );

  return (res != 0);
}


// _____________________________________________________________________________
/// @brief 転送パイプタイプを取得。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ePipeType PipeController::GetPipeType() const
{
  return m_pipe_type;
}


// _____________________________________________________________________________
/// @brief 転送パイプモードを取得。
/// 
/// @note モードは相手の要求により変わる可能性があるので、共有メモリを毎回読み込む。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ePipeMode PipeController::GetPipeMode() const
{
  PipeInfo info;
  if( GetPipeInfo( info ) == 1 )
  {
    return info.mode;
  }
  else
  {
    return ePipeMode::None;
  }
}



// _____________________________________________________________________________
/// @brief 自身のステートフラグの取得。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ePipeState
  PipeController::GetPipeState_forMyself() const
{
  return m_pipe_state;
}


// _____________________________________________________________________________
/// @brief 相手側のステートフラグを取得。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ePipeState
  PipeController::GetPipeState_forPartner
    ( pin<PipeInfo> in_ptr_info
    ) const
{
  PipeInfo header;
  PipeInfo const* p_header = in_ptr_info;
  if( in_ptr_info == nullptr )
  {
    if( GetPipeInfo( header ) == 1 )
    {
      p_header = &header;
    }
    else
    {
      return ePipeState::InvalidMemory;
    }
  }

  if( m_pipe_type == ePipeType::Host )
  {
    return p_header->state_guest;
  }
  else
  if( m_pipe_type == ePipeType::Guest )
  {
    return p_header->state_host;
  }
  return ePipeState::InvalidMemory;
}


// _____________________________________________________________________________
/// @brief クローズ
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int PipeController::Close()
{
  CPL_SAFE_PTR(m_sp_ctrl_buffer)->Close();
  CPL_SAFE_PTR(m_sp_pipe_buffer[0])->Close();
  CPL_SAFE_PTR(m_sp_pipe_buffer[1])->Close();

  return 0;
}


// _____________________________________________________________________________
/// @brief ハンドル無効チェック
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool PipeController::IsEmpty() const
{
  bool f0 = m_sp_ctrl_buffer ? m_sp_ctrl_buffer->IsEmpty() : true;
  bool f1 = m_sp_pipe_buffer[0] ? m_sp_pipe_buffer[0]->IsEmpty() : true;
  bool f2 = m_sp_pipe_buffer[1] ? m_sp_pipe_buffer[1]->IsEmpty() : true;

  return f0 && f1 && f2;
}


// _____________________________________________________________________________
/// @brief パイプ情報領域の作成
/// 
/// @retval Error_Invalid_SharedBuffer
///   : 共有メモリオブジェクトが設定されていない。
/// @retval Success_Create_HostMode
///   : ホストモードで作成。\n
///   : 最初に転送用の共有バッファを作成したオブジェクトがホストになる。
/// @retval Success_Create_GuestMode
///   : ゲストモードで作成。\n
///   : 既に同名の共有バッファが存在する場合、その共有バッファが開かれる。\n
///   : この場合、in_size_pipeは無視される。
/// @retval Error_Faild_Create
///   : 共有バッファの作成に失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eResultPipe PipeController::zzf_CreateInfoArea( _in<TimePoint> in_time_point_end )
{
  // 共有バッファオブジェクトのチェック。
  // これは共有バッファそのもののチェックではなく、共有バッファオブジェクトが
  // 設定されているかのチェック。
  CPL_THROWorRETURN_IF
    ( m_sp_ctrl_buffer == nullptr
    , IsThrow_Error()
    , eResultPipe::Error_Invalid_SharedBuffer
    , "Shared buffer is not set.\n"
    )

  eResultPipe res = eResultPipe::Timeout_Myself;

  if( auto locking = LockMutex( in_time_point_end, *m_sp_mutex_ctrl ) )
  {
    // 既に共有メモリが作成されていないか、開いてみる。
    auto res_maked_buffer =
      m_sp_ctrl_buffer->Open
        ( Core::eFlagSharedAccess::FileMap_ReadWrite
        );
    if( res_maked_buffer )
    {
      // 成功したらタイプをゲストタイプにする。
      m_pipe_type = ePipeType::Guest;
      //
      ChangePipeState( ePipeState::Wait, in_time_point_end );
      res = eResultPipe::Success_Create_GuestMode;
    }
    else
    {
      // オープンできなければ作成する。
      bool res_create =
        m_sp_ctrl_buffer->Create
          ( sizeof(PipeInfo)
          , Core::eFlagSharedProtect::Page_ReadWrite
          );
      CPL_THROWorRETURN_IF
        ( !res_create
        , IsThrow_Error()
        , eResultPipe::Error_Faild_Create
        , "Error : Failed to create TransPipe.\n"
        )

      // 作成に成功したオブジェクトはFlagPipeType::Host.
      m_pipe_type = ePipeType::Host;

      // 初期化
      PipeInfo info;
      m_sp_ctrl_buffer->Write( 0, &info, sizeof(info) );
      m_pipe_state = info.state_host;

      res = eResultPipe::Success_Create_HostMode;
    }
  }
  return res;
}


// _____________________________________________________________________________
// 転送元ステート変更
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
U64
  PipeController::zzf_SetStateHost
    ( _in<ePipeState> in_flag_state
    , _in<TimePoint > in_time_point_end
    )
{
  if( auto locking = LockMutex( in_time_point_end, *m_sp_mutex_ctrl ) )
  {
    if( m_pipe_type == ePipeType::Host )
    {
      m_pipe_state = in_flag_state;
    }

    return
      m_sp_ctrl_buffer->Write
        ( offsetof(PipeInfo,state_host)
        , &in_flag_state
        , sizeof(in_flag_state)
        );
  }
  return 0;
}

// _____________________________________________________________________________
// 転送先ステート変更
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
U64
  PipeController::zzf_SetStateGuest
    ( _in<ePipeState> in_flag_state
    , _in<TimePoint > in_time_point_end
    )
{
  if( auto locking = LockMutex( in_time_point_end, *m_sp_mutex_ctrl ) )
  //CPL_EXCTION_( m_mutex_thread, in_time_point_end )
  {
    if( m_pipe_type == ePipeType::Guest )
    {
      m_pipe_state = in_flag_state;
    }

    return
      m_sp_ctrl_buffer->Write
        ( offsetof(PipeInfo,state_guest)
        , &in_flag_state
        , sizeof(in_flag_state)
        );
  }
  return 0;
}


//// _____________________________________________________________________________
///// @brief 転送パイプのモードを設定。
///// 
///// @param[in] in_flag_mode
/////   : 設定するFlagPipeModeの値。
///// @param[in] in_time_point_end
/////   : タイムアウトとするタイムポイント(TimePoint)を指定。\n
/////   : 現時点からオフセットで指定する場合はstd::chrono::durationで指定する(CPL::td_time_msec等でもよし)。\n
/////   : 実質タイムアウトさせない場合、TimePoint::Max()を指定する。
///// 
///// @retval Success_WithoutChange
/////   : 現在のモードと同じ、つまり変更せず処理を完了。
///// @retval Success_ModeChange
/////   : モードの変更に成功。
///// @retval Timeout
/////   : タイムアウトになった。
///// @retval FaildChangeMode
/////   : モードの変更に失敗。
///// 
///// @note
///// モードの書き換えは次の手順を踏む。       \n
///// 1. 双方がWaitになるまで待機。            \n
///// 2. 自身のステートをRequestに変更。       \n
///// 3. 相手のステートがReadyになるまで待機。
/////    このとき相手がWaitでない場合、1.から再トライ\n
///// 4. モード変更。                          \n
///// 5. 自身および相手のステートをWaitに変更。\n
//// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//ResultPipe
//  PipeController::ChangePipeMode
//    ( _in<FlagPipeMode > in_flag_mode
//    , _in<TimePoint    > in_time_point_end
//    )
//{
//  CPL_EXCTION_( in_time_point_end, m_sp_mutex_ctrl )
//  {
//    Core::PipeInfo info_pipe;
//    GetPipeInfo( info_pipe );
//    if( info_pipe.mode == in_flag_mode )
//    {
//      return ResultPipe::Success_WithoutChange;
//    }
//
//    try
//    {
//      while( true )
//      {
//        // wait for "Ready" or "Wait"
//        auto state_partner = GetPipeState_forPartner();
//        if( state_partner == FlagPipeState::Empty ) // モード変更は相手がemptyでもOK.
//        {
//          break;
//        }
//        if( state_partner != FlagPipeState::Ready  &&
//            state_partner != FlagPipeState::Wait )
//        {
//          CPL_SleepNextTimeSlice();
//          continue;
//        }
//      
//        // 自身の状態フラグをRequestに設定。
//        ChangePipeState
//          ( FlagPipeState::Request
//          , in_time_point_end
//          );
//
//        // 相手のリクエストを発行していたらやり直し。
//        state_partner = GetPipeState_forPartner();
//        if( state_partner != FlagPipeState::Ready  &&
//            state_partner == FlagPipeState::Request )
//        {
//          CPL_SleepNextTimeSlice();
//          continue;
//        }
//
//        // 相手の状態フラグがReadyになるまで待機。
//        WaitStateEqual( FlagPipeState::Ready, in_time_point_end );
//        break;
//      }
//
//      // モード変更
//      auto res =
//        m_sp_ctrl_buffer->Write
//          ( offsetof(PipeInfo,mode)
//          , &in_flag_mode
//          , sizeof(in_flag_mode)
//          );
//
//      // 双方の状態フラグをWaitに設定（効率化のためヘッダを直設定）
//      GetPipeInfo( info_pipe );
//      info_pipe.state_host =
//      info_pipe.state_guest = FlagPipeState::Wait;
//      m_sp_ctrl_buffer->Write( 0, &info_pipe, sizeof(info_pipe) );
//
//      if( res != (U64)-1 ) // モード書き換えに成功した場合。
//      {
//        return ResultPipe::Success_ModeChange;
//      }
//
//      throw ResultPipe::Error_Faild_ModeChange;
//    }
//    catch( ResultPipe res )
//    {
//      CPL_THROWorRETURN
//        ( IsThrow_Error()
//        , res
//        , "Error : failed to mode change"
//        )
//    }
//    catch(...)
//    {
//      CPL_THROWorRETURN
//        ( IsThrow_Error()
//        , ResultPipe::Error_Faild_ModeChange
//        , "Error : failed to mode change"
//        )
//    }
//  }
//  else // timeout
//  {
//    return ResultPipe::Timeout_Myself;
//  }
//}


// _____________________________________________________________________________
/// @brief 転送パイプの状態フラグを設定。
/// 
/// @param[in] in_flag_state
///   : 設定するFlagPipeStateの値。
/// @param[in] in_time_point_end
///   : タイムアウトとするタイムポイント(TimePoint)を指定。\n
///   : 現時点からオフセットで指定する場合はstd::chrono::durationで指定する(CPL::td_time_msec等でもよし)。\n
///   : 実質タイムアウトさせない場合、TimePoint::Max()を指定する。
/// 
/// @retval Success_WithoutChange
///   : 現在の状態フラグと同じ。つまり変更せず処理を完了。
/// @retval Success_StateChange
///   : 状態フラグの変更に成功。
/// @retval Error_Faild_StateChange
///   : 状態フラグの変更に失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eResultPipe
  PipeController::ChangePipeState
    ( _in<ePipeState> in_flag_state
    , _in<TimePoint > in_time_point_end
    )
{
  if( m_pipe_state == in_flag_state )
  {
    // 同じ値の設定は意味がないので、変更なし。として抜ける。
    return eResultPipe::Success_WithoutChange;
  }

  // *** ロック処理 ***
  // ここでは共有バッファではなく、ローカルスレッドのロックでいいはず。
  if( auto locking = LockMutex( in_time_point_end, m_mutex_thread ) )
  {
    //CPL_WIN_OUTPUT_EX(
    //  SwS("(%d:%s) : %s\n"),
    //  (int)in_flag_state.state,
    //  ACTION_STATE_DEST_NAME( (int)in_flag_state.state ),
    //  (char_cptr)this->m_shared_memory.GetName() );

    m_pipe_state = in_flag_state;

    switch( m_pipe_type )
    {
    case ePipeType::Host:
      {
        CPL_THROWorRETURN_IF
          ( zzf_SetStateHost( in_flag_state, in_time_point_end ) == 0
          , IsThrow_Error()
          , eResultPipe::Error_Faild_StateChange
          , "Error : Faild to state change.\n"
          )
        else return eResultPipe::Success_StateChange;
      }

    case ePipeType::Guest:
      {
        CPL_THROWorRETURN_IF
          ( zzf_SetStateGuest( in_flag_state, in_time_point_end ) == 0
          , IsThrow_Error()
          , eResultPipe::Error_Faild_StateChange
          , "Error : Faild to state change.\n"
          )
        else return eResultPipe::Success_StateChange;
      }

    default:
      CPL_THROWorRETURN
        ( IsThrow_Error()
        , eResultPipe::Error_Faild_StateChange
        , "Error : Faild to state change.\n"
        )
    }
  }
  return eResultPipe::Timeout_Myself;
}

// _____________________________________________________________________________
/// @brief 転送パイプの相手の状態フラグを設定。
/// 
/// @param[in] in_flag_state
///   : 設定するFlagPipeStateの値。
/// @param[in] in_time_point_end
///   : タイムアウトとするタイムポイント(TimePoint)を指定。\n
///   : 現時点からオフセットで指定する場合はstd::chrono::durationで指定する(CPL::td_time_msec等でもよし)。\n
///   : 実質タイムアウトさせない場合、TimePoint::Max()を指定する。
/// 
/// @retval Success_WithoutChange
///   : 現在の状態フラグと同じ。つまり変更せず処理を完了。
/// @retval Success_StateChange
///   : 状態フラグの変更に成功。
/// @retval Error_Faild_StateChange
///   : 状態フラグの変更に失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eResultPipe
  PipeController::ChangePipeState_forPartner
    ( _in<ePipeState> in_flag_state
    , _in<TimePoint > in_time_point_end
    )
{
  if( m_pipe_state == in_flag_state )
  {
    // 同じ値の設定は意味がないので、変更なし。として抜ける。
    return eResultPipe::Success_WithoutChange;
  }
  
  // *** ロック処理 ***
  // ここでは共有バッファではなく、ローカルスレッドのロックでいいはず。
  if( auto locking = LockMutex( in_time_point_end, m_mutex_thread ) )
  {
    //CPL_WIN_OUTPUT_EX(
    //  SwS("(%d:%s) : %s\n"),
    //  (int)in_flag_state.state,
    //  ACTION_STATE_DEST_NAME( (int)in_flag_state.state ),
    //  (char_cptr)this->m_shared_memory.GetName() );

    m_pipe_state = in_flag_state;

    switch( m_pipe_type )
    {
    case ePipeType::Host:
      {
        CPL_THROWorRETURN_IF
          ( zzf_SetStateGuest( in_flag_state, in_time_point_end ) == 0
          , IsThrow_Error()
          , eResultPipe::Error_Faild_StateChange
          , "Error : Faild to state change.\n"
          )
        else return eResultPipe::Success_StateChange;
      }

    case ePipeType::Guest:
      {
        CPL_THROWorRETURN_IF
          ( zzf_SetStateHost( in_flag_state, in_time_point_end ) == 0
          , IsThrow_Error()
          , eResultPipe::Error_Faild_StateChange
          , "Error : Faild to state change.\n"
          )
        else return eResultPipe::Success_StateChange;
      }

    default:
      CPL_THROWorRETURN
        ( IsThrow_Error()
        , eResultPipe::Error_Faild_StateChange
        , "Error : Faild to state change.\n"
        )
    }
  }
  return eResultPipe::Timeout_Myself;
}


// _____________________________________________________________________________
/// @brief 相手が指定したステート（or タイムアウト or クローズ）になるまで待機。
/// 
/// @param[in] in_state_action
///   : チェックする相手の状態フラグ。
/// @param[in] in_time_point_end
///   : タイムアウトとするタイムポイント(TimePoint)を指定。\n
///   : 現時点からオフセットで指定する場合はstd::chrono::durationで指定する(CPL::td_time_msec等でもよし)。\n
///   : 実質タイムアウトさせない場合、TimePoint::Max()を指定する。
/// @param[in] out_ptr_header
///   : 取得するヘッダーへのポインタ。必要ない場合はNULLを指定。
/// 
/// @return 該当したアクションフラグを返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
eResultPipe
  PipeController::zzf_WaitStateEqual_
    ( _in <ePipeState> in_state_action
    , _in <TimePoint > in_time_point_end
    , pout<PipeInfo  > out_ptr_header
    , _in <bool      > in_flag_equal // in_state_actionと比較方法を指定する。
    ) const
{
  CPL_THROWorRETURN_IF
    ( m_sp_ctrl_buffer == nullptr
    , IsThrow_Error()
    , eResultPipe::Error_Invalid_SharedBuffer
    , "Error : invalid to shared buffer.\n"
    )

  //const size_t header_size = sizeof(PipeInfo);

  while( true )
  {
    // ヘッダ情報読み込み。
    PipeInfo info_pipe;
    GetPipeInfo( info_pipe );
    if( out_ptr_header )
    {
      *out_ptr_header = info_pipe;
    }

    switch( m_pipe_type )
    {
    case ePipeType::Host:
      // 自身がホストの場合、相手となるゲスト側のフラグをチェックする。
      if( (info_pipe.state_guest == in_state_action) == in_flag_equal )
      {
        return eResultPipe::Successful;
      }
      break;

    case ePipeType::Guest:
      // 自身がゲスト側の場合、相手となるホスト側のフラグをチェックする。
      if( (info_pipe.state_host == in_state_action) == in_flag_equal )
      {
        return eResultPipe::Successful;
      }
      break;

    default:
      CPL_THROWorRETURN
        ( IsThrow_Error()
        , eResultPipe::Error_Invalid_Type
        , "Error : invalid type.\n"
        )
    }

    // クローズチェック
    CPL_THROWorRETURN_IF
      ( m_sp_ctrl_buffer->IsEmpty()
      , IsThrow_Error()
      , eResultPipe::Error_Invalid_SharedBuffer
      , "Error : invalid to shared buffer.\n"
      )

    // タイムアウトチェック。
    CPL_THROWorRETURN_IF
      ( IsTimeout( in_time_point_end )
      , IsThrow_Timeout()
      , eResultPipe::Timeout_Myself
      , "Timeout for myself.\n"
      )

    CPL_SleepNextTimeSlice();
  }

  // ここが処理されることは基本的にないはずだが、念のため。
  CPL_THROWorRETURN
    ( IsThrow_Timeout()
    , eResultPipe::Timeout_Myself
    , "Timeout for myself.\n"
    )
}



//// _____________________________________________________________________________
///// @brief 送信サイズ情報の設定。
/////
///// @param[in] in_total_size : 全体の送信サイズ。
///// @param[in] in_data_size  : １回の送信で書き込むサイズ。
/////
///// @retval true  : 設定成功。
///// @retval false : 設定失敗。
//// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//U64
//  PipeController::zzf_SetWriteSize
//    ( _in<U64> in_total_size
//    , _in<U32> in_data_size
//    )
//{
//  auto locking = LockMutex( m_mutex_thread );
//
//  PipeInfo info_pipe;
//  //info_pipe.write_size = in_data_size;
//
//  return
//    m_sp_ctrl_buffer->Write
//      ( offsetof(PipeInfo,write_size)
//      , &info_pipe.write_size
//      , sizeof(info_pipe.write_size) + sizeof(info_pipe.write_size)
//      );
//  return 0;
//}


} // namespace Core
} // namespace CPL
