/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2002?/X/X - 2014/01/20
/// 
/// @brief  メモリプール関連。
/// 
/// ****************************************************************************

#include "../CPL_Core.h"

#ifdef CPL_WINDOWS

#ifndef NOMINMAX
#define NOMINMAX // min/max定義すんな！
#endif

#include <winsock2.h> // windows.hより先に読み込んでおかないと、マズイ場合がある
//#include <windows.h>
#endif

namespace CPL {
namespace Core {
namespace Memory {


// _____________________________________________________________________________
/// @brief コンストラクタ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MemoryPool::MemoryPool( size_t in_size, int in_num )
{
  if( in_num <= 0 )
  {
    in_num = MEMPOOl_DEf_MAx_NUm;
  }

  in_size += (sizeof(PoolInfo) - sizeof(U8[sizeof(size_t)])) * in_num;
  in_size = (in_size + 0x7) & ~0x7; // 8byteで合わせてみる

  m_ptr_alloc_top = ::malloc( in_size );
  m_ptr_alloc_end = (void*)(((U8*)m_ptr_alloc_top) + in_size);

//  ::memset( m_ptr_alloc_top, 0, in_size );

  // 初期状態の情報を設定
  Refresh();
}


// _____________________________________________________________________________
/// @brief デストラクタ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MemoryPool::~MemoryPool()
{
  ::free( m_ptr_alloc_top );
}



// _____________________________________________________________________________
/// @brief フリーエリアから取得可能なテーブルを検索　※ キャッシュアドレス指定可版。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
MemoryPool::type_btree_poolinfo_itr
  MemoryPool::zzf_FindFreeArea
  ( size_t in_size,
    type_btree_poolinfo_itr in_itr_cache_begin,
    type_btree_poolinfo_itr in_itr_cache_end )
{
  // 先頭検査
  auto itr_BT_poolinfo = in_itr_cache_begin;
  
  while( itr_BT_poolinfo != in_itr_cache_end )
  {
    auto ptr_poolinfo = (*itr_BT_poolinfo).get();
    if( in_size < ptr_poolinfo->GetSize() )
    {
      return itr_BT_poolinfo;
    }
    ++itr_BT_poolinfo;
  }

  itr_BT_poolinfo = m_free_tree.GetMin();
  while( itr_BT_poolinfo != in_itr_cache_begin )
  {
    auto ptr_poolinfo = (*itr_BT_poolinfo).get();
    if( in_size < ptr_poolinfo->GetSize() )
    {
      return itr_BT_poolinfo;
    }
    ++itr_BT_poolinfo;
  }

  return type_btree_poolinfo_itr(nullptr);
}



// _____________________________________________________________________________
/// @brief バッファ取得。
///
/// @param[in] in_alloc_size
///   : 確保するバッファのサイズ。
///
/// @return 確保されたバッファのポインタ。失敗でnullを返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void* MemoryPool::New( size_t in_alloc_size )
{
  auto itr_new_info =
    zzf_FindFreeArea
      ( in_alloc_size,
        m_free_tree.begin(),
        m_free_tree.end() );

  if( itr_new_info )
  {
    auto ptr_new_poolinfo = (*itr_new_info).get();
    size_t new_size = zzf_GetReviseSize( in_alloc_size, ptr_new_poolinfo ); // 補正サイズを取得
    // 領域の分割判定
    if( new_size < ptr_new_poolinfo->GetSize() ) // 予約サイズが、検索されたフリー領域を分割できる場合
    {
      // 新たなフリー情報を作成して追加
      auto ptr_new_free_info =
        reinterpret_cast<PoolInfo*>(ptr_new_poolinfo->m_ptr_top + new_size);
      ptr_new_free_info->m_ptr_end = ptr_new_poolinfo->m_ptr_end; 
      m_free_tree.Add( ptr_new_free_info );
      // 全リストを保持するテーブルへ追加
      m_all_tree.Add( ptr_new_free_info );
    }
    // 新規予約情報の補正
    ptr_new_poolinfo->m_ptr_end = ptr_new_poolinfo->m_ptr_top + new_size;
    void* ptr_ret_addr = ptr_new_poolinfo->m_ptr_top;
    // 情報テーブルの修正
    m_reserve_tree.Add( ptr_new_poolinfo ); // 予約情報テーブルへ追加し、
    m_free_tree.Del( itr_new_info );        // フリー情報テーブルから削除する

    return ptr_ret_addr;
  }
  return nullptr;
}



// _____________________________________________________________________________
/// @brief 取得済みバッファを返却。
///
/// @param[in] in_ptr_del
///   : 取得済みバッファのポインタ。
///
/// @return 返却されたバッファサイズ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
size_t MemoryPool::Delete( void* in_ptr_del )
{
  auto ptr_del_info =
    reinterpret_cast<PoolInfo*>((U8*)in_ptr_del - (sizeof(PoolInfo) - sizeof(long)));
  size_t
    del_size = ptr_del_info->GetSize();

  // 予約情報テーブルから検索。
  type_btree_poolinfo_itr
    itr_del_info_reserve = m_reserve_tree.Find( ptr_del_info );

  if( itr_del_info_reserve ) // 削除要素が該当した場合。
  {
    // 全ての情報テーブルから検索。
    auto itr_info_all_del = m_all_tree.Find( ptr_del_info );

    if( itr_info_all_del ) // これは必ず真となるはず。
    {
      // 前後の予約エリアを取得。
      auto itr_info_reserve_prev( -itr_del_info_reserve );
      PoolInfo* ptr_info_reserve_prev =
        itr_info_reserve_prev ? itr_info_reserve_prev->get() : nullptr;
      auto itr_info_reserve_next( +itr_del_info_reserve );
      PoolInfo* ptr_info_reserve_next =
        itr_info_reserve_next ? itr_info_reserve_next->get() : nullptr;
      // 前後のエリアを取得。
      auto itr_info_all_prev(-itr_info_all_del);
      PoolInfo* ptr_info_all_prev =
        itr_info_all_prev ? itr_info_all_prev->get() : nullptr;
      auto itr_info_all_next(+itr_info_all_del);
      PoolInfo* ptr_info_all_next =
        itr_info_all_next ? itr_info_all_next->get() : nullptr;
      // 前後のエリアの種類フラグを設定。
      // 0:エリアなし 1:フリーエリア 2:予約エリア
      int f_prev_area =
        ptr_info_all_prev
          ? ((ptr_info_all_prev == ptr_info_reserve_prev) ? 2 : 1)
          : 0;
      int f_next_area =
        ptr_info_all_next
          ? ((ptr_info_all_next == ptr_info_reserve_next) ? 2 : 1)
          : 0;
      int f_all_area  = ((f_prev_area & 0xf) << 4) | (f_next_area & 0xf);

      /* 処理パターンとして以下の場合がある
      ・削除指定エリア（予約エリア）をフリーエリアに変更
      ・削除指定エリアを前後のフリーエリアに統合
      ・削除指定エリアを前のフリーエリアに統合
      ・削除指定エリアを次のフリーエリアに統合
      */

      /**** 前後のエリアの種類によって処理を分ける ***/
      switch( f_all_area )
      {
      case 0x00: // 前後ともエリアなし
      case 0x02: // 前がエリアなしで後ろが予約エリア
      case 0x20: // 前が予約エリアで後ろにはエリアなし
      case 0x22: // 前後とも予約エリア
        /* 削除指定エリアをフリーエリアに変更 */
        m_free_tree.Add( itr_del_info_reserve->get() );
        m_reserve_tree.Del( itr_del_info_reserve );
        break;

      case 0x11: // 前後ともフリーエリア
        /* 削除指定エリアを前後のフリーエリアに統合 */
        // 前のエリアの情報を書き換え、削除指定エリアと次のエリアを削除する。
        ptr_info_all_prev->m_ptr_end = ptr_info_all_next->m_ptr_end; // 終端ポインタを変更
        m_reserve_tree.Del( itr_del_info_reserve ); // 予約エリアテーブルから削除指定エリアの登録削除
        m_free_tree.Del( ptr_info_all_next );  // フリーエリアテーブルから後ろのエリアの登録削除
        m_all_tree.Del( itr_info_all_del );
        m_all_tree.Del( itr_info_all_next );
        break;

      case 0x10: // 前がフリーエリアで後ろがエリアなし
      case 0x12: // 前がフリーエリアで後ろが予約エリア
        /* 削除指定エリアを前のフリーエリアに統合 */
        // 前のエリア情報を書き換えて現在の削除エリアを削除する。
        ptr_info_all_prev->m_ptr_end = itr_del_info_reserve->get()->m_ptr_end;
        m_reserve_tree.Del( itr_del_info_reserve );
        m_all_tree.Del( itr_info_all_del );
        break;

      case 0x01: // 前がエリアなしで後ろがフリーエリア
      case 0x21: // 前が予約エリアで後ろがフリーエリア
        /* 削除指定エリアを後ろのフリーエリアに統合 */
        // 現在の削除エリアの情報を書き換えて次のエリアを削除する。
        itr_del_info_reserve->get()->m_ptr_end = ptr_info_all_next->m_ptr_end;
        m_free_tree.Add( itr_del_info_reserve->get() );
        m_reserve_tree.Del( itr_del_info_reserve );
        m_free_tree.Del( ptr_info_all_next ); // フリー情報テーブルから削除。
        m_all_tree.Del( itr_info_all_next );   // 全エリアテーブルから削除。
        break;
      }
    }
    else
    {
      // ここはありえないのでブレークかけてみる
      CPL_BREAK(true);
    }
  }

  return del_size;
}


// _____________________________________________________________________________
/// @brief 最初に取得したエリアを削除。
///
/// @return 取得済みエリアがあれば、そのサイズ。無ければ０を返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
size_t MemoryPool::DeleteFirst()
{
  if( m_reserve_tree.GetNum() > 0 )
  {
    return Delete( m_reserve_tree.GetMin()->get()->m_ptr_top );
  }
  return 0;
}


// _____________________________________________________________________________
/// @brief 最後に取得したエリアを削除。
///
/// @return 取得済みエリアがあれば、そのサイズ。無ければ０を返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
size_t MemoryPool::DeleteLast()
{
  if( m_reserve_tree.GetNum() > 0 )
  {
    type_btree_poolinfo_itr itr_info_del( m_reserve_tree.GetMax() );
    return Delete( (*itr_info_del).get()->m_ptr_top );
  }
  return 0;
}


// _____________________________________________________________________________
/// @brief 全ての予約エリアを開放。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void MemoryPool::Refresh()
{
  m_free_tree.Clear();
  m_reserve_tree.Clear();
  m_all_tree.Clear();

  // 初期状態の情報を設定
  PoolInfo* ptr_top_info = static_cast<PoolInfo*>(m_ptr_alloc_top);
  ptr_top_info->m_ptr_end = static_cast<U8*>(m_ptr_alloc_end);

  //
  m_free_tree.Add( ptr_top_info );
  m_all_tree.Add( ptr_top_info );

  //
  memset( ptr_top_info->m_ptr_top, 0, ptr_top_info->GetSize() );
}



} // namespace Memory
} // namespace Core
} // namespace CPL

