/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2007/X/X - 2015/11/14
/// 
/// @brief  ソケットクラス 受信スレッド関連。
/// 
/// ****************************************************************************

#include "CPL_WIN_WS_SocketTCP.h"

namespace CPL {
namespace WIN {


// _____________________________________________________________________________
/// @brief コンストラクタ。
/// 
/// @param[in] in_ptr_socket : ソケットクラスへのポインタ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SocketTCP::ThreadRecv::ThreadRecv( SocketTCP* in_ptr_socket )
  : m_ptr_socket( in_ptr_socket )
  , m_complete_seg_num( -1 )
  , m_req_block_size( 0 )
{
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// recv()引数簡略化用
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketTCP::ThreadRecv::zzf_Recv( RecvParam& io_recv_param ) const
{
  int ret =
    recv(
      m_ptr_socket->GetSOCKET(),
      io_recv_param.out_buf,
      static_cast<int>(io_recv_param.in_length),
      0 );

  CPL_BREAK( ret < 0 );
  
#ifdef CPL_DBGSW_WINSOCK_recv
  const int length_max = 128;
  char tmp_recv_str[ length_max * 2 + 1 ] = { 0 };
  int length = Min<int>( ret, length_max );
  Core::strfunc<char>::binary_to_x16(
    tmp_recv_str, sizeof(tmp_recv_str),
    io_recv_param.out_buf, length );

  CPL_Sleep(0); // sendとの表示順の問題（わかりやすいように）でウエイトをいれておく。
  CPL_WIN_OUTPUT_EX_A( "recv( size : %d ) : %s\n", (int)ret, &tmp_recv_str[0] );
#endif

  return ret;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 基本受信処理。
//  引数 :
//    io_recv_param
//  戻り値 :
//    失敗         : SOCKET_ERROR
//    クローズ     : SOCKET_RETURN_CODE_CLOSE
//    タイムアウト : SOCKET_RETURN_CODE_TIMEOUT
//    成功         : 読み込んだサイズを返す。
//    その他       : 0を返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketTCP::ThreadRecv::zzf_RecvEx( RecvParam& io_recv_param ) const
{
  // 受信ループ
  while( true )
  {
    // イベント待機
    EventParam ep;
    ep.in_event_flags = FD_READ | FD_CLOSE;
    int flag = m_ptr_socket->WaitEvents( ep );

    if( flag == SOCKET_ERROR )
    {
      io_recv_param.out_error = ep.out_error;
      return SOCKET_ERROR;
    }
    //CPL_OUTPUT( SwS("[evt:%d]"), ep.out_event_flags );

    // ネットワークイベントチェック
    if( ep.out_event_flags == -1 ) // 強制終了。
    {
      return SOCKET_RETURN_CODE_EXIT;
    }
    else
    if( ep.out_event_flags == 0 ) // タイムアウト。
    {
      return SOCKET_RETURN_CODE_TIMEOUT;
    }
    else
    if( ep.out_event_flags == FD_CLOSE ) // ソケットが閉じられた。
    {
      return SOCKET_RETURN_CODE_CLOSE;
    }
    else
    if( ep.out_event_flags == FD_READ )
    {
      int read_size = 0; // 読み込みサイズカウント用
      RecvParam tmp_RP = io_recv_param; // 受信パラメタコピー（元パラメタ保存のため）
      // 読み込み
      while( true )
      {
        int ret = zzf_Recv( tmp_RP ); // 読み込み
        if( ret > 0 )
        {
          read_size += ret;
          if( read_size >= (int)io_recv_param.in_length )
          {
            return read_size;
          }
          // まだ読み込みきれていない場合
          tmp_RP.out_buf += ret; // アドレスを補正し、再読み込み
          tmp_RP.in_length -= ret; // 最大読み込みサイズも補正しないといけない
          continue;
        }
        else
        if( ret == 0 )
        {
          return SOCKET_RETURN_CODE_CLOSE;
        }
        else // エラーの場合。
        {
          int err = WIN::OutputWSALastErrorMessage();
          if( err == WSAEWOULDBLOCK ) // 一時的に利用できなくなっている。そのため、少し間をおいて再度読み込み。
          {
            CPL_Sleep( td_time_msec(100) );
            CPL_OUTPUT( SwS("recv() - WSAEWOULDBLOCK\n") );
            continue;
          }
          io_recv_param.out_error = err;
          return 0;
        }
      }
    }
    else
    {
      return 0;
    }
  }

}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// リクエスト情報読み取り用関数
//  引数 :
//    out_req_info - 取得するリクエスト情報構造体
//  戻り値 :
//    失敗         : SOCKET_ERROR
//    クローズ     : SOCKET_RETURN_CODE_CLOSE
//    タイムアウト : SOCKET_RETURN_CODE_TIMEOUT
//    成功         : 読み込んだサイズを返す。
//    その他       : 0を返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketTCP::ThreadRecv::zzf_RecvRequestInfo( RequestInfoBuffer& out_req_info ) const
{
  // 復号化パスの取得と復号化判定フラグ設定。
  cref<std::string> password = m_ptr_socket->GetRecvPassword();
  bool flag_password = (!password.empty()  &&  password != "");

  // 基本ヘッダサイズ
  const int basic_info_size = static_cast<int>(sizeof(RequestInfo));
  // 一時バッファ
  char temp_buf[ CPL_WIN_WS_REQUEST_BLOCK_SIZE_MAX ] = { 0 };
  char temp_buf2[ CPL_WIN_WS_REQUEST_BLOCK_SIZE_MAX ] = { 0 }; // デバッグ用
  int ret_size = 0;

  // リクエストヘッダを読み込む。
  // まず、セグメント数の要素まで読み込み、その後セグメント情報を読み込む。
  RecvParam rp( temp_buf, basic_info_size );
  int ret = zzf_RecvEx( rp );
  if( ret <= 0 )
  {
    return ret;
  }
  ret_size = ret;

  //
  memcpy( temp_buf2, temp_buf, sizeof(temp_buf2) );

  // 復号化。
  // ※ 暗号化はヘッダおよび先頭のセグメントサイズまで。
  if( flag_password )
  {
    // 復号化
    Core::Decryption(
      temp_buf,
      basic_info_size,
      &password[0],
      static_cast<int>(password.length()) );
  }

  // 取得したデータをリクエスト型に変換し、現状で参照できる情報を取得する。
  RequestInfo* ptr_req_info = reinterpret_cast<RequestInfo*>(temp_buf);
  U32 seg_num = ptr_req_info->m_segment_num;

  // シグネチャチェック
  if( !ptr_req_info->IsSignature() )
  {
    return SOCKET_ERROR;
  }

  // セグメント数が２以上場合、残りのセグメントサイズを読み込む。
  if( seg_num >= 2 )
  {
    rp.out_buf   = temp_buf + basic_info_size; // 読み込み位置変更。
    rp.in_length = (seg_num-1) * sizeof(SegmentBlock); // 読み込みサイズ変更。
    ret = zzf_RecvEx( rp );
    if( ret <= 0 )
    {
      return ret;
    }
    // 設定
    memcpy( ptr_req_info->m_segment+1, rp.out_buf, ret );

    ret_size += ret;
  }
  
  // 戻り値であるRequestInfoBufferを確保。
  U32 req_info_size = RequestInfo::ComputeSize( seg_num ); // 必要なサイズを取得。
  RequestInfoBuffer req_info( req_info_size );

  // データコピー
  CPL_MEMCPY( req_info.get(), ptr_req_info, req_info_size );

  out_req_info = req_info;

  return ret_size;
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 受信処理（キャッシュへ格納）
// 戻り値：読み込みサイズ（０以下で失敗）
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketTCP::ThreadRecv::zzf_RecvCache() const
{
  DWORD t_base = timeGetTime();
  
  // 複合化パス取得と判定用フラグ設定。
  cref<std::string> password = m_ptr_socket->GetRecvPassword();
  bool flag_password = (!password.empty()  &&  password != "");
 
  // リクエストヘッダ取得
  RequestInfoBuffer req_info( 0 );
  int ret = zzf_RecvRequestInfo( req_info );
  if( ret <= 0 ) // エラーチェック。
  {
    m_req_block_size = 0;
    m_complete_seg_num = -1;

    return ret; // 失敗！
  }

  m_req_block_size = req_info->m_size;
  m_read_size = ret;
  SpByteBuffer sp_buf_active;
  // リクエストサイズをキューに追加
  CPL_EXCTION_( m_mutex_data, m_mutex_request )
  {
    m_recv_request_queue.push_back( req_info );

    sp_buf_active = CPL_MAKE_SHARED(ByteBuffer);
    sp_buf_active->reserve( m_req_block_size ); // 領域確保
    m_recv_buffer_queue.Push( sp_buf_active, false );  // キャッシュ用バッファ追加
  }

  try {
    // ***** セグメントループ ***** //
    // 一時バッファ
    // ※ 読み込みはキャッシュブロック単位で読み込まれる。
    //    キャッシュブロックは
    //    これは高速化ではなく、復号化や圧縮を考慮した処理である。
    u_char  temp_buf_read[ CPL_WIN_WS_SOCKET_TEMP_BUF_SIZE ] = {0}; // 一次読み込みバッファ
    u_char  temp_buf_set[ CPL_WIN_WS_SOCKET_TEMP_BUF_SIZE ] = {0};  // 二次処理バッファ
    // 一時バッファ書き込みアドレス
    //char* temp_buf_write_addr = temp_buf;

    m_complete_seg_num = 0; // 完了済みセグメント数
    int segment_read_count =
      static_cast<int>(req_info->m_segment[ 0 ].m_size);

    // セグメントループ
    while( m_read_size < m_req_block_size )
    {
      //***** データ本体読み込み *****//
      // キャッシュが満杯になるか、リクエストサイズになるまで読み込む。

      // ※ デバッグ用出力文字列設定
      //CPL_SPRINTF( str,
      //  SwS("%s: [PID:%d][TID:%d]: 受信キャッシュに追加: AutoLockRecvCacheAccess"),
      //  GetModuleName(), (int)GetCurrentProcessId(), (int)Core::GetThreadID() );
      //U32 segment_size = req_info->m_segment[ segment_idx ].m_size; // アクティブなセグメントのサイズ
      //U32 segment_read_count = segment_size;

      // フラグメント単位ループ
      do {
        // ヘッダ読み込み。
        Socket_i_::FragmentHeader fmh;
        RecvParam rp( (char_ptr)&fmh, sizeof(fmh) );
        ret = zzf_RecvEx( rp );

        if( ret <= 0  ||  fmh.size == 0 )
        {
#ifdef CPL_DBGSW_WINSOCK_recv
          CPL_WIN_OUTPUT_EX
            ( "%s: recv_cache(id:%d, length:%d, err:%d, time:%d)\n"
            , (ret == SOCKET_RETURN_CODE_CLOSE) ? SwS("error!") : SwS("close!")
            , req_info->m_ID
            , req_info->m_size
            , rp.out_error
            , timeGetTime() - t_base
            );
#endif
          m_req_block_size = 0;
          m_complete_seg_num = -1;
          return ret;
        }

        // データ部読み込み。
        rp.out_buf   = (char_ptr)temp_buf_read;
        rp.in_length = fmh.size;
        ret = zzf_RecvEx( rp );
        // 読み込み失敗 or close判定
        if( ret <= 0 )
        {
#ifdef CPL_DBGSW_WINSOCK_recv
          CPL_WIN_OUTPUT_EX
            ( "%s: recv_cache(id:%d, length:%d, err:%d, time:%d)\n")
            , (ret == SOCKET_RETURN_CODE_CLOSE) ? SwS("error!") : SwS("close!")
            , req_info->m_ID
            , req_info->m_size
            , rp.out_error
            , timeGetTime() - t_base
            );
#endif

          m_req_block_size = 0;
          m_complete_seg_num = -1;

          return ret;
        }

        U32 read_size = ret;
        // 復号化処理。
        if( flag_password )
        {
          // 復号化
          Core::Decryption(
            temp_buf_read,
            read_size,
            &password[0],
            static_cast<int>(password.length()) );
        }

        // 圧縮チェック
        if( fmh.f_compress )
        {
          // 展開
          u_long dest_size = CPL_WIN_WS_SOCKET_TEMP_BUF_SIZE;
          WinSock::zlib_uncompress()(
            temp_buf_set, &dest_size,
            temp_buf_read, read_size );
          read_size = dest_size;
        }
        else
        {
          // 展開の必要がないので、そのままコピー。
          // これは、圧縮・展開において展開先のバッファを用意しているため。
          memcpy( temp_buf_set, temp_buf_read, read_size );
        }

        m_read_size         += read_size;
        segment_read_count  -= static_cast<int>(read_size);

        auto locking = LockMutex( m_mutex_data );
        // キャッシュに追加（リサイズしてメモリコピー）
        U32 now_size = (U32)sp_buf_active->size(); // 現在のサイズを取得（途中で読み出しがあった場合、変化している可能性がある）
        sp_buf_active->resize( now_size + read_size );
        CPL_MEMCPY(
          &(*sp_buf_active)[ now_size ],
          temp_buf_set,
          read_size );

      } while( segment_read_count > 0 );

      // セグメントの読み込み位置調整
      while( segment_read_count <= 0 )
      {
        ++m_complete_seg_num;
        if( m_complete_seg_num >= static_cast<int>(req_info->m_segment_num) )
        {
          break;
        }
        segment_read_count +=
          static_cast<int>(req_info->m_segment[ m_complete_seg_num ].m_size);
      }
    }
  }
  catch( ... )
  {
    // とりあえずいまのところ何もしない。 //

    throw;
  }
  
  //io_recv_param.out_error = 0;
  
#ifdef CPL_DBGSW_WINSOCK_recv  
  CPL_WIN_OUTPUT_EX
    ( "recv_cache(id:%d, length:%d, read:%d, time:%d)\n"
    , req_info->m_ID
    , req_info->m_size
    , m_read_size
    , timeGetTime() - t_base
    );
#endif

  m_req_block_size = 0;

  return m_read_size;
}



// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 受信スレッド。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
size_t SocketTCP::ThreadRecv::operator()()
{ 
#ifdef CPL_DBGSW_WINSOCK_recv
  DbgMsg msg( CPL_FUNCNAME, this->GetThreadName().c_str() );
#endif

  int flag = 0;
  do {
    CPL_SleepNextTimeSlice();

    flag = zzf_RecvCache();

    if( flag == SOCKET_RETURN_CODE_CLOSE  ||  flag == SOCKET_RETURN_CODE_EXIT )
    {
      if( m_ptr_socket  &&  m_ptr_socket->GetSOCKET() != INVALID_SOCKET )
      {
        m_ptr_socket->On_Closed( GlobalFindSocket( m_ptr_socket ) );
      }
    }
  } while( flag > 0 );

  return 0;
}


// _____________________________________________________________________________
/// @brief 既に読み込み完了しているリクエストブロックがあるかどうか取得。
/// 
///  リクエストブロックが読み込み完了している場合は、次のとおり。\n
///  ・リクエストサイズキューの要素が２つ以上ある。\n
///  ・リクエストサイズキューの最初の要素のサイズがリクエストブロックのサイズに達している。
///
/// @retval eFlagComplete::NoneData      : データが無い。
/// @retval eFlagComplete::Receiving     : 受信途中のリクエストブロック有り。
/// @retval eFlagComplete::ReceivedCache : 受信済みのリクエストブロック有り。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SocketTCP::ThreadRecv::eFlagComplete
  SocketTCP::ThreadRecv::IsCompleteRequest() const
{
  U32 buf_num = (U32)m_recv_buffer_queue.GetQueueNum();
  U32 req_num = (U32)m_recv_request_queue.size();

  // データ無し。
  if( buf_num == 0 )
  {
    return eFlagComplete::NoneData;
  }
  
  // リクエスト情報キューの要素が２つ以上ある場合、先頭リクエストは読み込み済みとなる。
  else
  if( req_num > 1 )
  {
    return eFlagComplete::ReceivedCache;
  }

  // この判定の時点では、データ受信中（もしくは受信完了）であることがわかっている。
  // 読み込んだデータがあり、かつ、読み込みサイズがリクエストブロックのサイズに達している場合（つまり、１ブロック読み込み済み）
  else
  if( m_read_size > 0 && m_read_size == m_req_block_size )
  {
    return eFlagComplete::ReceivedCache;
  }
  // リクエストブロック自体がない。
  else
  if( m_req_block_size == 0 )
  {
    return eFlagComplete::ReceivedCache;
  }
  else
  {
    return eFlagComplete::Receiving;
  }
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// 既に読み込み完了しているセグメント数（処理中のリクエスト）を取得。
// 受信中でなければ-1を返す。
// 受信中ならば０以上を返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketTCP::ThreadRecv::GetCompleteSegmentCacheNum() const
{
  return m_complete_seg_num;

  //U32 seg_buf_num = (U32)m_recv_buffer_queue.size();
  //U32 req_num     = (U32)m_recv_request_queue.size();

  //if( seg_buf_num == 0 )
  //{
  //  return -1;
  //}

  ////
  //RequestInfoBuffer req_info = m_recv_request_queue.front();
  //U32 seg_num = req_info->m_segment_num;
  //U32 offs_cnt = 0;
  //U32 seg_offs_end = req_info.GetBuffer()->size();

  ////
  //CPL_LOOP2( seg_num, idx )
  //{
  //  seg_offs_end += req_info->m_segment[ idx ].m_size;

  //  if( m_read_size < seg_offs_end )
  //  {
  //    return (int)idx;
  //  }
  //  ef( m_read_size == seg_offs_end )
  //  {
  //    return (int)(idx + 1);
  //  }
  //}
  //
  //return seg_num; // 仕様的にここが処理されることはないと思うが…
}


} // namespace WIN
} // namespace CPL