#pragma once
/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2015/03/19 - 2015/11/11
/// 
/// @brief  共有メモリ基本クラス
/// 
/// ****************************************************************************


#include "CPL_Core_PipeBase.h"


namespace CPL {
namespace Core {


// _____________________________________________________________________________
/// @brief 共有メモリインターフェイス
///
// *****************************************************************************
class PipeController
  : virtual public PipeBase
{
protected:
  using td_SpSharedBuffer = sdptr<SharedBufferBase>;
  using td_SpMutex = sdptr<Core::MutexBase>;
  using td_MutexMap = std::map<td_tstring, td_SpMutex>;

  CPL_SINGLETON( td_MutexMap, sm_mutex_pool );
  
  //// 一時的にスコープ内で例外設定を変えるためのオブジェクト
  //struct TempOptionThrow
  //{
  //  friend class PipeController;
  //  PipeController* m_ptr_param;
  //  bool m_tmp_throw_error;
  //  bool m_tmp_throw_timeout;
  //  TempOptionThrow( PipeController* ppb, bool in_tmp_throw_error, bool in_tmp_throw_timeout )
  //    : m_ptr_param( ppb )
  //    , m_tmp_throw_error  ( ppb->m_flag_throw_error )
  //    , m_tmp_throw_timeout( ppb->m_flag_throw_timeout )
  //  {
  //    ppb->m_flag_throw_error   = in_tmp_throw_error;
  //    ppb->m_flag_throw_timeout = in_tmp_throw_timeout;
  //  }
  //  ~TempOptionThrow()
  //  {
  //    m_ptr_param->m_flag_throw_error   = m_tmp_throw_error;
  //    m_ptr_param->m_flag_throw_timeout = m_tmp_throw_timeout;
  //  }
  //};

  PipeController()
    : PipeBase( SwS("") )
  {}
private:
public:
  // _____________________________________________________________________________
  /// @brief バッファ構築用コンストラクタ
  /// 
  /// @param[in] in_name_pipe
  ///   : パイプ名を指定します。
  /// @param[in] in_size_pipe
  ///   : １ラインのバッファサイズを指定します。\n
  ///   : ただし直接この値が使われるわけではなく、補正された値が使われます。\n
  ///   : なお、１つの転送パイプは２つのライン（ダブルバッファ）と管理領域の３つのバッファを持ちます。
  /// @param[in] in_sp_mutex
  ///   : 共有バッファ用のミューテックスを指定します。\n
  ///   : 次のようにnullキャストで型Tを指定し、自動で生成することが可能です。\n
  ///   : - sdptr<ThreadMutex<std::timed_mutx>>(nullptr)\n
  ///   : - sdptr<ProcessMutex>(nullptr)\n
  ///   : ただし、ここで指定できるのはMutexBaseクラスを継承したクラスのみです。
  /// @param[in] type_buffer_
  ///   : 共有バッファ型を指定するための変数。\n
  ///   : (T*)nullptrのように指定します（TにCPL::WIN::ProcessSharedBuffer等を指定する）
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  template<class T_Mutex, class T_SharedBuffer>
  PipeController
  ( _in<td_tstring> in_name_pipe
  , _in<U64       > in_size_pipe
  , sdptr<T_Mutex > in_sp_mutex
  , T_SharedBuffer* type_buffer_
  ) : PipeBase( in_name_pipe ) // これは継承先からは呼ばれない。
  {
    Create<T_Mutex, T_SharedBuffer>
      ( in_name_pipe
      , in_size_pipe
      , in_sp_mutex
      , type_buffer_
      );
  }

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  int GetPipeInfo( rout<PipeInfo> out_pipe_info ) const;

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ePipeType GetPipeType() const;

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ePipeMode GetPipeMode() const;

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ePipeState GetPipeState_forMyself() const;

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ePipeState
    GetPipeState_forPartner( pin<PipeInfo> in_ptr_info = nullptr ) const;

  // _____________________________________________________________________________
  /// @brief 作成（or オープン）
  /// 
  /// @tparam T_Mutex        : ミューテックスオブジェクトの型を指定します。
  /// @tparam T_SharedBuffer : 作成する共有バッファオブジェクトの型を指定します。
  /// 
  /// @param[in] in_name_pipe
  ///   : パイプ名を指定します。
  /// @param[in] in_size_pipe
  ///   : パイプとして使用する共有メモリのサイズを指定。\n
  ///   : 既に作成されていた場合、何も処理しないので、この値は無視される。
  /// @param[in] in_sp_mutex
  ///   : ミューテックスオブジェクトを指すスマートポインタを指定します。\n
  ///   : nullptrを指定すると自動生成されたミューテックスが割り当てられます
  /// @param[in] type_buffer_
  ///   : シェーダバッファの型識別用の変数です。
  /// 
  /// @retval Error_Invalid_SharedBuffer
  ///   : 共有メモリオブジェクトが設定されていない。
  /// @retval Success_Create_HostMode
  ///   : ホストモードで作成。\n
  ///   : 最初に転送用の共有バッファを作成したオブジェクトがホストになる。
  /// @retval Success_Create_GuestMode
  ///   : ゲストモードで作成。\n
  ///   : 既に同名の共有バッファが存在する場合、その共有バッファが開かれる。\n
  ///   : この場合、in_size_pipeは無視される。
  /// @retval Error_Faild_Create
  ///   : 共有バッファの作成に失敗。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  template<class T_Mutex, class T_SharedBuffer>
  eResultPipe
    Create
      ( _in<td_tstring> in_name_pipe
      , _in<U64       > in_size_pipe
      , sdptr<T_Mutex > in_sp_mutex
      , T_SharedBuffer* type_buffer_
      )
  {
    auto& mutex_pool = sm_mutex_pool();

    m_name_pipe = in_name_pipe;
    try {
      m_sp_ctrl_buffer =
        CPL_MAKE_SHARED( T_SharedBuffer, m_name_pipe ); // ここではバッファは作らない（サイズを指定しない）こと。
      auto name_mutex = in_name_pipe + SwS("_mutex");

      if( in_sp_mutex == nullptr ) // ミューテックスオブジェクトを指定していない場合。
      {
        auto hit = mutex_pool.find( name_mutex ); // 名前で検索。
        if( hit != mutex_pool.end() )
        {
          m_sp_mutex_ctrl = hit->second;
        }
        else // 該当する名前のミューテックスがない場合は、新規に作成する。
        {
          mutex_pool[ name_mutex ] =
          m_sp_mutex_ctrl = CPL_MAKE_SHARED( T_Mutex, name_mutex );
        }
      }
      else // ミューテックスオブジェクトが指定されている場合。
      {
        // まずプールからキーではなくデータで検索し、
        // 同一ミューテックスであるにも関わらず名前が違う場合は削除する。
        for( auto it = mutex_pool.begin(); it != mutex_pool.end(); ++it )
        {
          if( it->second == in_sp_mutex )
          {
            if( it->first != name_mutex )
            {
              mutex_pool.erase( it );
            }
            break;
          }
        }
        // 新しく設定
        mutex_pool[ name_mutex ] = m_sp_mutex_ctrl = in_sp_mutex;
      }

      auto res = zzf_CreateInfoArea( TimePointOffset(td_time_sec(1) ) );

      m_sp_pipe_buffer[0] =
        CPL_MAKE_SHARED
          ( PipeBuffer
          , CPL_MAKE_SHARED( T_SharedBuffer, m_name_pipe + SwS("0"), in_size_pipe )
          , CPL_MAKE_SHARED( typename T_SharedBuffer::TypeFlag, true, m_name_pipe + SwS("_flag_write0") )
          , CPL_MAKE_SHARED( typename T_SharedBuffer::TypeFlag, false, m_name_pipe + SwS("_flag_read0") )
          );
      m_sp_pipe_buffer[1] =
        CPL_MAKE_SHARED
          ( PipeBuffer
          , CPL_MAKE_SHARED( T_SharedBuffer, m_name_pipe + SwS("1"), in_size_pipe )
          , CPL_MAKE_SHARED( typename T_SharedBuffer::TypeFlag, true, m_name_pipe + SwS("_flag_write1") )
          , CPL_MAKE_SHARED( typename T_SharedBuffer::TypeFlag, false, m_name_pipe + SwS("_flag_read1") )
          );

      return res;
    }
    catch(...)
    {
      //m_sp_ctrl_buffer    = nullptr;
      m_sp_pipe_buffer[0] = nullptr;
      m_sp_pipe_buffer[1] = nullptr;
      throw;
    }
  }

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  int Close();

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  bool IsEmpty() const;

  //// _____________________________________________________________________________
  //// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  //ResultPipe
  //  ChangePipeMode
  //    ( _in<FlagPipeMode> in_flag_mode
  //    , _in<TimePoint   > in_time_point_end
  //    );

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  eResultPipe
    ChangePipeState
      ( _in<ePipeState> in_flag_state
      , _in<TimePoint > in_time_point_end
      );

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  eResultPipe
    ChangePipeState_forPartner
      ( _in<ePipeState> in_flag_state
      , _in<TimePoint > in_time_point_end
      );

  // _____________________________________________________________________________
  /// @brief 相手が指定したステートになるまで待機。
  /// 
  /// タイムアウトまたはクローズされた場合も離脱する。
  /// 
  /// @copydetails zzf_WaitStateEqual_
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  eResultPipe
    WaitStateEqual
      ( _in <ePipeState> in_state_action
      , _in <TimePoint > in_time_point_end
      , pout<PipeInfo  > out_ptr_header = nullptr
      ) const
  {
    return zzf_WaitStateEqual_
      ( in_state_action
      , in_time_point_end
      , out_ptr_header
      , true
      );
  }

  // _____________________________________________________________________________
  /// @brief 相手が指定したステート以外になるまで待機。
  /// 
  /// タイムアウトまたはクローズされて場合も離脱する。
  /// 
  /// @copydetails zzf_WaitStateEqual_
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  eResultPipe
    WaitStateNotEqual
      ( _in <ePipeState> in_state_action
      , _in <TimePoint > in_time_point_end
      , pout<PipeInfo  > out_ptr_header = nullptr
      ) const
  {
    return zzf_WaitStateEqual_
      ( in_state_action
      , in_time_point_end
      , out_ptr_header
      , false
      );
  }

  // _____________________________________________________________________________
  /// @brief 例外設定。
  /// 
  /// @param[in] in_flag_throw_error
  ///   : true  - エラーは例外を投げる。\n
  ///   : false - エラーは戻り値として返す。
  /// @param[in] in_flag_throw_timeout
  ///   : true  - タイムアウトは例外を投げる。\n
  ///   : false - タイムアウトは戻り値として返す。
  /// 
  /// @return なし。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  void
    SetThrowOption
      ( _in<bool> in_flag_throw_error
      , _in<bool> in_flag_throw_timeout
      )
  {
    m_flag_throw_error   = in_flag_throw_error;
    m_flag_throw_timeout = in_flag_throw_timeout;
  }

  // _____________________________________________________________________________
  /// @brief エラー例外を投げるかどうかの設定値を取得。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  bool IsThrow_Error() const noexcept
  {
    return m_flag_throw_error;
  }

  // _____________________________________________________________________________
  /// @brief タイムアウト例外を投げるかどうかの設定値を取得。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  bool IsThrow_Timeout() const noexcept
  {
    return m_flag_throw_timeout;
  }

  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  auto& GetThreadMutex() const
  {
    return m_mutex_thread;
  }

protected:
  // _____________________________________________________________________________
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  eResultPipe zzf_CreateInfoArea( _in<TimePoint> in_time_point );

  //void zzf_SetupIntaractiveType()
  //{
  //  ChangePipeMode
  //    ( FlagPipeMode::Interactive
  //    , TimePoint::Max()
  //    );
  //}

  sdptr<PipeBuffer> GetActivePipe() const
  {
    return m_sp_pipe_buffer[ m_idx_buffer ];
  }
  U64 zzf_SetStateHost
    ( _in<ePipeState> in_state
    , _in<TimePoint > in_time_point_end
    );
  U64 zzf_SetStateGuest
    ( _in<ePipeState> in_state
    , _in<TimePoint > in_time_point_end
    );
  //U64 zzf_SetWriteSize
  //  ( _in<U64> in_total_size
  //  , _in<U32> in_data_size
  //  );
  eResultPipe
    zzf_WaitStateEqual_
      ( _in <ePipeState> in_state_action
      , _in <TimePoint > in_time_point_end
      , pout<PipeInfo  > out_ptr_header
      , _in <bool      > in_flag_equal
      ) const;

  td_MutexThread          m_mutex_thread;  // ローカルスレッド用
  sdptr<MutexBase>        m_sp_mutex_ctrl; // 共有バッファ用。thread/process/networkで変わる。
  sdptr<SharedBufferBase> m_sp_ctrl_buffer;
  sdptr<PipeBuffer>       m_sp_pipe_buffer[2];
  PipeCacheManager        m_cache_mng;
  ePipeType               m_pipe_type  = ePipeType::Invalid; ///< 初期はinvalid。Create/Openで自動で設定する。
  ePipeState              m_pipe_state = ePipeState::Empty;  ///< 効率化のため、メンバにキャッシュ。
  int                     m_idx_buffer = 0; ///< 共有ダブルバッファのアクティブ側を指すインデックス。
  bool                    m_flag_throw_error   = true; ///< エラーの場合スローするかどうか。default = true.
  bool                    m_flag_throw_timeout = true; ///< タイムアウトの場合スローするかどうか。default = true.
};


} // namespace Core
} // namespace CPL
