/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2002/X/X - 2015/01/09
/// 
/// @brief  アロケータ周り
/// 
/// ****************************************************************************


#include <stdio.h>

#include "../CPL_Core.h"


//#ifdef _WINDOWS
#ifdef CPL_WINDOWS

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

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



//#ifdef CPL_ALLOCATOR


static size_t dump_tid = -1;

// クリティカルセクションオブジェクト
//static CPL::Core::CriticalSection CPL_ALLOC_CS;

namespace CPL {
namespace Core {
namespace Memory {


// _____________________________________________________________________________
/// @brief アロケータの基本情報
///
// *****************************************************************************
class AllocInfoData
{
public:
  void* m_addr;
  SDEF  m_call_no;
  SDEF  m_size;
  char* m_filename;
  SDEF  m_fileline;
  SDEF* m_ptr_foot; // バッファオーバーラン検査用エリアへのポインタ および バッファアンダーラン検査値
  int   debug_info_idx; // デバッグ用の保存データ参照先インデックス（deleteチェック用）
  int   flag_deleted;   // trueならばdelete済み。（）
};


// _____________________________________________________________________________
/// @brief アロケータ情報
///
// *****************************************************************************
class AllocInfo
  : public List::DoublyElement<AllocInfo,Core::BaseObject>
  , public AllocInfoData
{
public:
//  AllocInfo* m_pLink;  // バックアップテーブルの要素のポインタ

  AllocInfo()
  {
    m_addr         = nullptr;
    m_call_no      = -1;
    m_size         = 0;
    m_filename     = nullptr;
    m_fileline     = -1;
    m_ptr_foot     = nullptr;
    debug_info_idx = 0;
    flag_deleted   = false;
  }

  AllocInfo( AllocInfo const& ai )
  {
    m_addr         = ai.m_addr;
    m_call_no      = ai.m_call_no;
    m_size         = ai.m_size;
    m_filename     = ai.m_filename;
    m_fileline     = ai.m_fileline;
    m_ptr_foot     = ai.m_ptr_foot;
    debug_info_idx = ai.debug_info_idx;
    flag_deleted   = ai.flag_deleted;
  }
};


// _____________________________________________________________________________
/// @brief アロケータ管理クラス
///
// *****************************************************************************
class AllocCtl
{
private:
  SDEF m_count_new;   // new/new[] 総コール数
  SDEF m_count_del;   // delete/delete[] 総コール数
  SDEF m_count_now;   // 現在の確保数
  SDEF m_size_now;    // 現在の総確保サイズ（アロケート情報を含む）
  SDEF m_size_max;    // 総確保サイズの最高値
  SDEF m_size_total;  // 今まで確保した総確保サイズ

  AllocInfo* m_ptr_top;
  AllocInfo* m_ptr_end;


public:
  SDEF  GetCountNew() const { return m_count_new; }
  SDEF  GetCountDel() const { return m_count_del; }
  SDEF  GetCountNow() const { return m_count_now; }
  SDEF  GetSizeNow() const { return m_count_now; }
  SDEF  GetSizeMax() const { return m_size_max; }
  SDEF  GetSizeTotal() const { return m_size_total; }

  AllocInfo*        GetTop()       { return m_ptr_top; }
  AllocInfo const*  GetTop() const { return m_ptr_top; }
  AllocInfo*        GetEnd()       { return m_ptr_end; }
  AllocInfo const*  GetEnd() const { return m_ptr_end; }

  void Add(
        AllocInfo* io_ptr_alloc_info,
        size_t     in_size,
        char_cptr  in_file_name,
        int        in_line );
  void Delete( AllocInfo* pAi );
  
  static size_t GetAlignSize()
  {
    return Max<size_t>( sizeof(void*), 8 );
  }
  static size_t GetHeaderSize()
  {
    return CPL::AlignHigh( sizeof(AllocInfo), GetAlignSize() );
  }
  static size_t GetAllSize( size_t in_size )
  {
    return GetHeaderSize() + AlignHigh( in_size, GetAlignSize() ) + sizeof(SDEF);
  }
  static void* GetDataAddr( void* in_org_ptr )
  {
    return reinterpret_cast<char*>(in_org_ptr) + GetHeaderSize();
  }
  static void* GetFooterAddr( void* in_org_ptr, size_t in_size )
  {
    return
      reinterpret_cast<char*>(GetDataAddr( in_org_ptr ))
        + AlignHigh( in_size, GetAlignSize() );
  }

  AllocCtl();
  ~AllocCtl(){}
};

#ifdef CPL_DEBUG
static AllocInfoData backup_info[256] = {};
static int backup_info_idx = 0;
#endif




// _____________________________________________________________________________
/// @brief 管理構造体を取得。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AllocCtl* GetAllocCtl()
{
  static AllocCtl alloc_ctl;
  return &alloc_ctl;
}


// _____________________________________________________________________________
/// @brief コンストラクタ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
AllocCtl::AllocCtl()
{
  m_ptr_top = nullptr;
  m_ptr_end = nullptr;
  atexit( allocate_dump );
}


// _____________________________________________________________________________
/// @brief 追加。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void
  AllocCtl::Add
    ( AllocInfo* io_ptr_alloc_info
    , size_t     in_size
    , char_cptr  in_file_name
    , int        in_line
    )
{
  new( io_ptr_alloc_info ) AllocInfo; // コンストラクタ呼び出し
  
  io_ptr_alloc_info->m_addr      = io_ptr_alloc_info + 1;
  io_ptr_alloc_info->m_call_no   = m_count_new;
  io_ptr_alloc_info->m_size      = (SDEF)in_size;
  io_ptr_alloc_info->m_filename  = const_cast<char*>(in_file_name);
  io_ptr_alloc_info->m_fileline  = in_line;
  io_ptr_alloc_info->m_ptr_foot  = (SDEF*)GetFooterAddr( (void*)io_ptr_alloc_info, in_size );
  *io_ptr_alloc_info->m_ptr_foot = -1; // オーバーラン検査用


#ifdef CPL_DEBUG
  io_ptr_alloc_info->debug_info_idx = backup_info_idx;
  backup_info[ backup_info_idx ] = *static_cast<AllocInfoData*>(io_ptr_alloc_info);
  backup_info_idx = (backup_info_idx + 1) & 0xff;

  static auto* old_foot = io_ptr_alloc_info->m_ptr_foot;
  static U64 foot_check_cnt = 0;
  if( old_foot == io_ptr_alloc_info->m_ptr_foot )
  {
    foot_check_cnt++;
  }
  else
  {
    //CPL_BREAK( foot_check_cnt > 1000 );
    foot_check_cnt = 0;
    old_foot = io_ptr_alloc_info->m_ptr_foot;
  }
#endif

  m_count_new++;
  m_count_now++;
  m_size_now   += (SDEF)in_size;
  m_size_total += (SDEF)in_size;

  if( m_size_now > m_size_max )
  {
    m_size_max = m_size_now;
  }

  // リスト連結
  if( !m_ptr_top )
  {
    m_ptr_top = m_ptr_end = io_ptr_alloc_info;
  }
  else
  {
    m_ptr_end->AddNodeNext( io_ptr_alloc_info );
    m_ptr_end = io_ptr_alloc_info;
  }

#ifdef CPL_DBGSW_DUMP_NEW
  if( dump_tid == -1   ||
      dump_tid == Core::GetThreadID() )
  {
#ifdef CPL_PLATFORM_64
    CPL_DBG_OUTPUT_A
      ( "[T:%8d]:new(%I64d,0x%016I64x,%8I64d(%8I64d)) line(%d) %s\n"
      , (int)GetThreadID()
      , io_ptr_alloc_info->m_call_no
      , (U64)(io_ptr_alloc_info + 1)
      , in_size
      , in_size + sizeof(CPL::Core::Memory::AllocInfo) + sizeof(*io_ptr_alloc_info->m_ptr_foot)
      , (int)io_ptr_alloc_info->m_fileline
      , io_ptr_alloc_info->m_filename
      );
#else
    CPL_DBG_OUTPUT_A
      ( "[T:%8d]:new(%d,0x%08x,%8d(%8d)) line(%d) %s\n"
      , (int)GetThreadID()
      , io_ptr_alloc_info->m_call_no
      , (U32)(io_ptr_alloc_info + 1)
      , in_size
      , in_size + sizeof(CPL::Core::Memory::AllocInfo) + sizeof(*io_ptr_alloc_info->m_ptr_foot)
      , io_ptr_alloc_info->m_fileline
      , io_ptr_alloc_info->m_filename
      );
#endif
  }
#endif
}


// _____________________________________________________________________________
/// @brief 削除。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void AllocCtl::Delete( AllocInfo* io_ptr_alloc_info )
{
  // エラー検査
  // バッファアンダーラン（フッタアドレス）検査
  SDEF* foot_ptr = (SDEF*)GetFooterAddr( io_ptr_alloc_info, io_ptr_alloc_info->m_size );
  if( io_ptr_alloc_info->m_ptr_foot != foot_ptr )
  {
    OutputDebugString( SwS("CorePLi: Error: memory under write!\n") );
    CPL_BREAK(1);
//  CPL_THROW(DEC_UNDER_RUN);
  }
  // バッファオーバーラン検査
  if( *(io_ptr_alloc_info->m_ptr_foot) != -1 )
  {
    OutputDebugString( SwS("CorePLi: Error: memory over write!\n") );
    CPL_BREAK(1);
//  CPL_THROW(DEC_OVER_RUN);
  }

#ifdef CPL_DEBUG
  auto& bi = backup_info[ io_ptr_alloc_info->debug_info_idx ];
  if( bi.m_call_no == io_ptr_alloc_info->m_call_no )
  {
    bi.flag_deleted = true;
  }
#endif

  m_count_del++;
  m_count_now--;
  m_size_now -= io_ptr_alloc_info->m_size;

  if( io_ptr_alloc_info == m_ptr_end )
  {
    m_ptr_end = io_ptr_alloc_info->GetNodePrev();
  }

  if( io_ptr_alloc_info == m_ptr_top )
  {
    m_ptr_top = m_ptr_top->GetNodeNext();
  }
  io_ptr_alloc_info->Detach();

#ifdef CPL_DBGSW_DUMP_DEL
  if( dump_tid == -1   ||
      dump_tid == Core::GetThreadID() )
  {
#if defined CPL_PLATFORM_64
    CPL_DBG_OUTPUT_A
      ( "[T:%8d]:del(%I64d,0x%016I64x,%8I64d(%8I64d)) line(%d) %s\n"
      , (int)GetThreadID()
      , io_ptr_alloc_info->m_call_no
      , (U64)(io_ptr_alloc_info + 1)
      , io_ptr_alloc_info->m_size
      , io_ptr_alloc_info->m_size+sizeof(CPL::Core::Memory::AllocInfo) + sizeof(*io_ptr_alloc_info->m_ptr_foot)
      , (int)io_ptr_alloc_info->m_fileline
      , io_ptr_alloc_info->m_filename
      );
#else
    CPL_DBG_OUTPUT_A
      ( "[T:%8d]:del(%d,0x%08x,%8d(%8d)) line(%d) %s\n"
      , (int)GetThreadID()
      , io_ptr_alloc_info->m_call_no
      , (U32)(io_ptr_alloc_info + 1)
      , io_ptr_alloc_info->m_size
      , io_ptr_alloc_info->m_size+sizeof(CPL::Core::Memory::AllocInfo) + sizeof(*io_ptr_alloc_info->m_ptr_foot)
      , io_ptr_alloc_info->m_fileline
      , io_ptr_alloc_info->m_filename
      );
#endif
  }
#endif
}






// _____________________________________________________________________________
/// @brief メモリ割り当て状況のダンプ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void allocate_dump()
{
  AllocCtl* ptr_alloc_ctrl = GetAllocCtl();
  
#if defined CPL_PLATFORM_64
  CPL_DBG_OUTPUT(
    SwS("\nCPL output message --------------------------------\n")
    SwS("    new count num = %I64d\n")
    SwS("    delete count num = %I64d\n")
    SwS("    memory leak list( num == %I64d )\n"),
    ptr_alloc_ctrl->GetCountNew(),
    ptr_alloc_ctrl->GetCountDel(),
    ptr_alloc_ctrl->GetCountNow() );
#else
  CPL_DBG_OUTPUT(
    SwS("\nCPL output message --------------------------------\n")
    SwS("    new count num = %d\n")
    SwS("    delete count num = %d\n")
    SwS("    memory leak list( num == %d )\n"),
    ptr_alloc_ctrl->GetCountNew(),
    ptr_alloc_ctrl->GetCountDel(),
    ptr_alloc_ctrl->GetCountNow() );
#endif

  AllocInfo* ptr_alloc_info = ptr_alloc_ctrl->GetTop();
  while( ptr_alloc_info )
  {
#if defined CPL_PLATFORM_64
    CPL_DBG_OUTPUT_A(
      " adr(0x%016I64x) call_no(%016I64d) size(%016I64d) line(%d) filename(%s)\n",
      (U64)ptr_alloc_info,
      ptr_alloc_info->m_call_no,
      ptr_alloc_info->m_size,
      (int)ptr_alloc_info->m_fileline,
      ptr_alloc_info->m_filename );
#else
    CPL_DBG_OUTPUT_A(
      " adr(0x%08x) call_no(%08d) size(%08d) line(%d) filename(%s)\n",
      (U32)ptr_alloc_info,
      ptr_alloc_info->m_call_no,
      ptr_alloc_info->m_size,
      ptr_alloc_info->m_fileline,
      ptr_alloc_info->m_filename );
#endif

    ptr_alloc_info = ptr_alloc_info->GetNodeNext();
  }
  
  OutputDebugString( SwS("/CPL ----------------------------------------------\n\n") );
}



// _____________________________________________________________________________
/// @brief 基本アロケーター。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void*
  Allocator
    ( size_t         in_size
    , ThreadMutex<>* in_ptr_mutex
    , const char*    in_file_name
    , int            in_line
    )
{
#ifdef CPL_DEBUG

  if( in_size == 0 )
  {
    in_size = 1;
  }

  // 予約サイズ補正
  size_t header_size = CPL::Core::Memory::AllocCtl::GetHeaderSize();
  size_t alloc_size  = CPL::Core::Memory::AllocCtl::GetAllSize( in_size );

  auto locking = LockMutex( in_ptr_mutex );

  while( true )
  {
    CPL::Core::Memory::AllocInfo*
      p = (CPL::Core::Memory::AllocInfo*)malloc( alloc_size );  
    if( !p )
    {
      new_handler GlobalHandler = set_new_handler(0);
      set_new_handler( GlobalHandler );
      if( GlobalHandler )
      {
        (*GlobalHandler)();
      }
      else
      {
        throw std::bad_alloc();
      }
    }
    else
    {
      CPL::Core::Memory::GetAllocCtl()->Add( p, in_size, in_file_name, in_line );
      return (void*)((char*)p + header_size);
    }
  }

  return 0; // ここが処理されることはない。

#else

  return ::operator new( in_size );

#endif
}


// _____________________________________________________________________________
/// @brief 基本デリーター。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void Deleter( void* p, ThreadMutex<>* in_ptr_mutex )
{
#ifdef CPL_DEBUG

  if( !p ) return;

  size_t align_size  = Max<size_t>( sizeof(void*), 8 );
  size_t header_size = CPL::AlignHigh( sizeof(CPL::Core::Memory::AllocInfo), align_size );

  auto locking = LockMutex( in_ptr_mutex );

  CPL::Core::Memory::AllocInfo*
    pAi = (CPL::Core::Memory::AllocInfo*)((char*)p - header_size);
  CPL::Core::Memory::AllocInfo save_AI = *pAi; // デバッグ用に保存。
  CPL::Core::Memory::GetAllocCtl()->Delete( pAi );

  free( (void*)pAi );

#else

  ::operator delete( p );

#endif
}


// _____________________________________________________________________________
/// @brief 範囲チェック付メモリコピー。
/// 
/// ※ new で取得したバッファ限定。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void* MemCopy( void* out_buf0, const void* in_buf1, size_t in_copy_size )
{
  auto ptr_alloc_info = GetAllocCtl()->GetEnd();
  void* dest0 = out_buf0;
  void* dest1 = (char*)dest0 + in_copy_size - 1;
  bool flag_break = true;

  while( ptr_alloc_info )
  {
    void* p0 = ptr_alloc_info + 1;
    void* p1 = ptr_alloc_info->m_ptr_foot;

    if( p0 <= dest0  &&  dest0 < p1  &&
        p0 <= dest1  &&  dest1 < p1 )
    {
      flag_break = false;
      break;
    }

    ptr_alloc_info = ptr_alloc_info->GetNodePrev();
  }

  CPL_BREAK( flag_break );

  return memcpy( out_buf0, in_buf1, in_copy_size );
}



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




//#endif // CPL_ALLOCATOR

