/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2009/X/X - 2015/11/11
/// 
/// @brief  共有メモリ関連。
/// 
/// ****************************************************************************



#include "CPL_WIN_ProcessSharedBuffer.h"


namespace CPL {
namespace WIN {

  
// _____________________________________________________________________________
/// @brief 自動マッピング用クラス
///
// *****************************************************************************
class ProcessSharedBuffer::AutoMapping
{
  U8* m_addr_mapping_base;
  U8* m_addr_mapping;

public:
  static cref<SYSTEM_INFO> GetSysInfo()
  {
    static SYSTEM_INFO si;
    CPL_BLOCK_ONCE
    {
      GetSystemInfo( &si );
    }
    return si;
  }

  // _____________________________________________________________________________
  /// @brief コンストラクタ。
  /// 
  /// @param[in] in_shared_memory
  ///   : マッピングする共有メモリクラスを指定。
  /// @param[in] in_desired_access
  ///   : アクセスタイプ。                                         \n
  ///   : ファイルへマップされているページの保護タイプを指定する。 \n
  ///   : - FILE_MAP_WRITE	                                       \n
  ///   : - FILE_MAP_READ                                          \n
  ///   : - FILE_MAP_ALL_ACCESS                                    \n
  ///   : - FILE_MAP_COPY                                          \n
  ///   : ※ 詳細はMapViewOfFile参照。
  /// @param[in] in_file_offset
  ///   : マッピングを開始するファイルのオフセットを指定する。
  /// @param[in] in_bytes_to_map
  ///   : マップ対象のファイルのバイト数を指定する。                        \n
  ///   : 0 を指定すると、ファイルマッピングオブジェクト全体がマップされる。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  AutoMapping
  ( _in<ProcessSharedBuffer>
               in_shared_memory
  , _in<DWORD> in_desired_access = FILE_MAP_ALL_ACCESS
  , _in<U64  > in_file_offset    = 0
  , _in<U64  > in_bytes_to_map   = 0
  )
  {
    if( in_shared_memory.IsEmpty() )
    {
      CPL_THROW("ProcessSharedBuffer is closed.");
    }

    const DWORD  granularity      = GetSysInfo().dwAllocationGranularity; // 粒度
    const DWORD  file_offset_high = (DWORD)(in_file_offset >> 32);
    const DWORD  file_offset_low  = AlignLow( (DWORD)(in_file_offset & 0xffffffff), granularity );
    const U64 bytes_to_map     =
      (in_bytes_to_map == 0  ||  granularity >= in_shared_memory.GetSize()) ?
       0 : AlignHigh<U64>( in_bytes_to_map, granularity );

    m_addr_mapping_base =
      (U8*)::MapViewOfFile
        ( in_shared_memory.m_handle_shared_memory
        , in_desired_access
        , file_offset_high
        , file_offset_low
        , static_cast<size_t>(bytes_to_map)
        );
    m_addr_mapping = m_addr_mapping_base + in_file_offset;

    if( !m_addr_mapping_base )
    {
      WIN::OutputLastErrorMessage();
    }
  }

  // _____________________________________________________________________________
  /// @brief デストラクタ。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  ~AutoMapping()
  {
    // マッピングを解除
    ::UnmapViewOfFile( m_addr_mapping_base );
  }


  // _____________________________________________________________________________
  /// @brief マッピングアドレスを取得。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  U8*       GetMappingAddress()       { return m_addr_mapping; }
  // _____________________________________________________________________________
  /// @brief マッピングアドレスを取得。
  // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  U8 const* GetMappingAddress() const { return m_addr_mapping; }
};



// _____________________________________________________________________________
/// @brief バッファ作成付きコンストラクタ
/// 
/// デフォルトのミューテックスを使用する共有バッファ。
/// 
/// @exception 共有バッファ作成失敗でstd::bad_allocを投げる。
/// 
/// @param[in] in_name_buffer
///   : 共有メモリ名を指定する。
/// @param[in] in_size_buffer
///   : 作成するバッファのサイズを指定する。\n
///   : 0を指定するとコンストラクタでは生成しない。
/// @param[in] in_flag_protect
///   : 保護属性フラグを指定する。                           \n
///   : 次の組み合わせが指定可能。                           \n
///   : [Page_ReadOnly / Page_ReadWrite / Page_WriteCopy] |  \n
///   : [Sec_Commit	/ Sec_Image / Sec_NoCache / Sec_Reserve ]\n
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ProcessSharedBuffer::ProcessSharedBuffer
( _in<td_tstring              > in_name_buffer
, _in<U64                     > in_size_buffer
, _in<Core::eFlagSharedProtect> in_flag_protect
) : Core::SharedBufferBase
    ( in_name_buffer
    , in_size_buffer
    //, CPL_MAKE_SHARED
    //    ( Core::ProcessMutex
    //    , in_name_buffer + SwS("_") +
    //      Core::ChangeCodeStrings(td_tstring(),typeid(ProcessSharedBuffer).name())
    //    )
    )
{
  if( in_size_buffer > 0 )
  {
    if( !Create( in_size_buffer, in_flag_protect ) )
    {
      throw std::bad_alloc();
    }
  }
}

// _____________________________________________________________________________
/// @brief コンストラクタ（バッファ生成なし。バッファ名のみ指定する）
/// 
/// デフォルトのミューテックスを使用する共有バッファ。
/// 
/// @exception 共有バッファ作成失敗でstd::bad_allocを投げる。
/// 
/// @param[in] in_name_buffer
///   : 共有メモリ名を指定する。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ProcessSharedBuffer::ProcessSharedBuffer( _in<td_tstring> in_name_buffer )
  : Core::SharedBufferBase
    ( in_name_buffer
    //, CPL_MAKE_SHARED
    //    ( Core::ProcessMutex
    //    , in_name_buffer + SwS("_") +
    //      Core::ChangeCodeStrings(td_tstring(),typeid(ProcessSharedBuffer).name())
    //    )
    )
{
}

// _____________________________________________________________________________
/// @brief デストラクタ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ProcessSharedBuffer::~ProcessSharedBuffer()
{
  // 
  Close();
}


// _____________________________________________________________________________
/// @brief 共有メモリの作成。
/// 
/// @param[in] in_max_size
///   : ファイルマッピングオブジェクトの最大サイズを指定する。
/// @param[in] in_flag_protect
///   : 保護属性フラグを指定する。                           \n
///   : 次の組み合わせが指定可能。                           \n
///   : [Page_ReadOnly / Page_ReadWrite / Page_WriteCopy] |  \n
///   : [Sec_Commit	/ Sec_Image / Sec_NoCache / Sec_Reserve ]\n
/// 
/// @retval true  : 成功。
/// @retval false : 失敗。
/// 
/// @note
/// この関数では排他制御は行いません。\n
/// 必要に応じて上位のモジュールで排他制御を行ってください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool
  ProcessSharedBuffer::Create
    ( _in<U64                     > in_max_size
    , _in<Core::eFlagSharedProtect> in_flag_protect
    )
{
  auto max_size =
    AlignHigh<U64>( in_max_size, AutoMapping::GetSysInfo().dwAllocationGranularity );
  m_size_max = max_size;
  const DWORD max_size_high = (DWORD)(max_size >> 32);
  const DWORD max_size_low  = (DWORD)(max_size & 0xffffffff);

  m_handle_shared_memory =
    CreateFileMapping
      ( INVALID_HANDLE_VALUE // ページングファイル上に作成。
      , nullptr
      , static_cast<U32>(in_flag_protect) // PAGE_READWRITE
      , max_size_high
      , max_size_low
      , m_shared_buffer_name.c_str()
      );

  if( !m_handle_shared_memory )
  {
    WIN::OutputLastErrorMessage();
    return false;
  }

  return true;
}


// _____________________________________________________________________________
/// @brief 共有メモリのオープン。
/// 
/// @param[in] in_desired_access
///   : アクセス属性フラグを指定。                                            \n
///   : 次の指定が可能。                                                      \n
///   : [FILE_MAP_COPY / FILE_MAP_WRITE / FILE_MAP_READ / FILE_MAP_ALL_ACCESS]\n
/// 
/// @retval true  : 成功。
/// @retval false : 失敗。
/// 
/// @note
/// この関数では排他制御は行いません。\n
/// 必要に応じて上位のモジュールで排他制御を行ってください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool ProcessSharedBuffer::Open( _in<Core::eFlagSharedAccess> in_desired_access )
{
  //
  m_handle_shared_memory =
    ::OpenFileMapping
      ( static_cast<U32>(in_desired_access)
      , FALSE
      , m_shared_buffer_name.c_str()
      );
  //
  if( m_handle_shared_memory == nullptr ) // 共有メモリなし
  {
    return false;
  }
  // サイズを求める。
  AutoMapping map( *this, FILE_MAP_WRITE, 0, 0 );
  MEMORY_BASIC_INFORMATION mbi;
  memset( &mbi, 0, sizeof(mbi) );
  VirtualQuery( map.GetMappingAddress(), &mbi, sizeof(mbi) );
  m_size_max = mbi.RegionSize;

  return true;
}


// _____________________________________________________________________________
/// @brief プロセス間共有メモリへ書き込み。
/// 
/// @param[in] in_pos     : 書き込む位置をバイト単位で指定します。
/// @param[in] in_ptr_buf : 書き込まれるデータを指すアドレスを指定します。
/// @param[in] in_size    : 書き込むサイズをバイト単位で指定します。
/// @param[in] in_type_access
///   : アクセスタイプを指定します。\n
///   : MapViewOfFile関数のdwDesiredAccessに渡されるので、詳しくはそちらを検索してください。
/// 
/// @note
/// この関数では排他制御は行いません。\n
/// 必要に応じて上位のモジュールで排他制御を行ってください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
U64
  ProcessSharedBuffer::Write
    ( _in<U64 > in_pos
    , pin<void> in_ptr_buf
    , _in<U64 > in_size
    , _in<U32 > in_type_access
    )
{
  try {
    AutoMapping map( *this, in_type_access, in_pos, in_size );
    U8* map_addr = map.GetMappingAddress();
    if( map_addr )
    {
      ::memcpy( map_addr, in_ptr_buf, static_cast<size_t>(in_size) );

      return in_size;
    }

    return 0;
  }
  catch(...)
  {
    return 0;
  }
}


// _____________________________________________________________________________
/// @brief プロセス間共有メモリへバッファリストの書き込み。
/// 
  /// @param[in]  in_ptr_param_write
  ///   : 書き込みパラメータ配列の先頭ポインタを指定します。\n
  ///   : パラメータはstd::tuple<U64,void_cptr,U64>の構成とし、それぞれpos,address,sizeを表します。\n
  /// @param[in]  in_num_param
  ///   : 書き込みパラメータ配列の要素数を指定します。
  /// @param[out] out_ptr_result
  ///   : 各要素の実際に書き込まれたサイズを格納する配列へのポインタを指定します。\n
  ///   : 必要ない場合、nullptr指定できます。
/// @param[in] in_type_access
///   : アクセスタイプを指定します。\n
///   : MapViewOfFile関数のdwDesiredAccessに渡されるので、詳しくはそちらを検索してください。
/// 
/// @note
/// この関数では排他制御は行いません。\n
/// 必要に応じて上位のモジュールで排他制御を行ってください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
std::pair<U64,U64>
  ProcessSharedBuffer::Write
    ( pin <std::tuple<U64,void_cptr,U64>> in_ptr_param_write
    , _in <size_t                       > in_num_param
    , pout<std::vector<U64>             > out_ptr_result
    , _in <U32                          > in_type_access
    )
{
  try {
    AutoMapping map( *this, in_type_access, 0, 0 ); // 全体をマップ（必要な分だけロックしても速度が変わらなかった）
    U8* map_addr = map.GetMappingAddress();
    if( map_addr )
    {
      if( out_ptr_result )
      {
        out_ptr_result->resize( in_num_param );
      }

      auto size_buf_max = GetSize();
      U64 count_req  = 0; // リクエストされたサイズ
      U64 count_real = 0; // 
      for( size_t i = 0; i < in_num_param; ++i )
      {
        auto pos       = std::get<0>(in_ptr_param_write[i]);
        auto ptr_src   = std::get<1>(in_ptr_param_write[i]);
        auto size_req  = std::get<2>(in_ptr_param_write[i]);
        auto size_real = Min(pos + size_req, size_buf_max) - pos;

        if( pos < size_buf_max )
        {
          memcpy
            ( map_addr + static_cast<size_t>(pos)
            , ptr_src
            , static_cast<size_t>(size_real)
            );
        }

        //
        count_req  += size_req;
        count_real += size_real;
        if( out_ptr_result )
        {
          (*out_ptr_result)[i] = size_real;
        }
      }
      return std::pair<U64,U64>( count_req, count_real );
    }

    return std::pair<U64,U64>(0,0);
  }
  catch(...)
  {
    return std::pair<U64,U64>(0,0);
  }
}

// _____________________________________________________________________________
/// @brief プロセス間共有メモリから読み込み。
/// 
/// @param[in]  in_pos      : 読み込み開始位置をバイト単位で指定します。
/// @param[out] out_ptr_buf : 読み出し先のアドレスを指定します。
/// @param[in]  in_size     : 読み込むサイズをバイト単位で指定します。
/// @param[in]  in_type_access
///   : アクセスタイプを指定します。\n
///   : MapViewOfFile関数のdwDesiredAccessに渡されるので、詳しくはそちらを検索してください。
///
/// @note
/// この関数では排他制御は行いません。\n
/// 必要に応じて上位のモジュールで排他制御を行ってください。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
U64
  ProcessSharedBuffer::Read
    ( _in<U64  > in_pos
    , pout<void> out_ptr_buf
    , _in<U64  > in_size
    , _in<U32  > in_type_access
    ) const
{
  if( in_size <= 0 )
  {
    CPL_OUTPUT( SwS("CorePLi Error: ProcessSharedBuffer::Read() : in_size == 0\n") );
    return false;
  }

  try {
    AutoMapping map( *this, in_type_access, in_pos, in_size );
    U8 const* map_addr = map.GetMappingAddress();

    if( map_addr )
    {
      ::memcpy( out_ptr_buf, map_addr, static_cast<size_t>(in_size) );

      return in_size;
    }

    return 0;
  }
  catch(...)
  {
    return 0;
  }
}


// _____________________________________________________________________________
/// @brief 共有メモリを閉じる。
/// 
/// @retval 0 : 正常終了。
/// @retval -1 : 何らかのエラーが発生。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int ProcessSharedBuffer::Close()
{
  try
  {
    if( m_handle_shared_memory )
    {
      ::CloseHandle( m_handle_shared_memory );

      m_handle_shared_memory = nullptr;
    }
    return 0;
  }
  catch(...)
  {
    m_handle_shared_memory = nullptr;
    return -1;
  }
}



} // namespace WIN
} // namespace CPL

