/// ****************************************************************************
/// @file   
/// @author Dr.Neeton
/// @date   2007? - 2014/01/24
/// 
/// @brief  基底ソケットクラス
///
/// ネットワークの基本仕様\n
/// ・一度に転送指示するデータは「ブロック」として扱う。\n
/// ・転送ブロックは１つ以上のバッファで構成される。\n
/// ・ただし、受信ブロックは必ず１つのバッファとして扱われる。\n
/// 
/// ****************************************************************************


#include "CPL_WIN_WS_SocketBase.h"

namespace CPL {
namespace WIN {


// _____________________________________________________________________________
/// @brief コンストラクタ
///
/// @param[in] in_sock
///   : 既に作成済みのソケットハンドル。
/// @param[in] in_sp_socket_parent
///   : 親となるソケットクラスのスマートポインタ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SocketBase::SocketBase( SOCKET in_sock, cref<SpSocket> in_sp_socket_parent )
  : m_sp_sock_parent( in_sp_socket_parent )
#ifdef CPL_DEBUG
  , dbgmsg_( CPL_CLASSNAME )
#endif
{
  m_socket = in_sock;
}

// _____________________________________________________________________________
/// @brief ソケット作成付コンストラクタ。
///
/// @param[in] in_AF
///   : アドレスファミリ。基本的にAF_INET(IPv4)かAF_INET6(IPv6)。
///   : socket()の第一引数に渡すので、詳細は検索するべし。
/// @param[in] in_type
///   : ソケットタイプ。
///   : socket()の第二引数に渡すので、詳細は検索するべし。
/// @param[in] in_protocol
///   : プロトコル。基本0でＯＫ？。
///   : socket()の第三引数に渡すので、詳細は検索するべし。
/// @param[in] in_sp_socket_parent
///   : 属する(親)ソケットのスマートポインタを渡す。
///   : 属さない場合はnullptrを指定する。基本nullptrでＯＫ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SocketBase::SocketBase
( int            in_AF,
  int            in_type,
  int            in_protocol,
  cref<SpSocket> in_sp_socket_parent )
  : m_sp_sock_parent( in_sp_socket_parent )
#ifdef CPL_DEBUG
  , dbgmsg_( CPL_CLASSNAME )
#endif
{
  //m_socket = WinSock::SocketBase( in_AF, in_type, in_protocol );
  m_socket = ::socket( in_AF, in_type, in_protocol );
  if( m_socket == INVALID_SOCKET )
  {
    int err = WIN::OutputWSALastErrorMessage();
    CPL_THROW_CODE( err, "WinSock error\n" );
  }
}


// _____________________________________________________________________________
/// @brief ソケット作成付コンストラクタ。
///
/// @param[in] in_hostname
///   : ホスト名。アドレスかホスト名を文字列で指定する。
///   : getaddrinfo()の第一引数に渡すので、詳細は検索するべし。
/// @param[in] in_service
///   : サービス名かポート番号を文字列で指定。
///   : getaddrinfo()の第二引数に渡すので、詳細は検索するべし。
/// @param[in] in_sp_socket_parent
///   : 属する(親)ソケットのスマートポインタを渡す。
///   : 属さない場合はnullptrを指定する。基本nullptrでＯＫ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SocketBase::SocketBase
( char_cptr      in_hostname,
  char_cptr      in_service,
  cref<SpSocket> in_sp_socket_parent )
  : m_sp_sock_parent( in_sp_socket_parent )
#ifdef CPL_DEBUG
  , dbgmsg_( CPL_CLASSNAME )
#endif
{
  addrinfo hints, *res_first;
  memset( &hints, 0, sizeof(hints) );

  hints.ai_family   = PF_UNSPEC; // IPv4/IPv6(不特定)
  hints.ai_socktype = SOCK_STREAM;
	//hints.ai_flags    = AI_PASSIVE;


  int err = getaddrinfo( in_hostname, in_service, &hints, &res_first );
  if( err != 0 )
  {
    throw err;
  }
  
  for( addrinfo* res = res_first; res; res = res->ai_next )
  {
    // ソケット作成。
    m_socket = socket( res->ai_family, res->ai_socktype, res->ai_protocol );
    if( m_socket == INVALID_SOCKET )
    {
      continue;
    }
    // ソケットアドレスデータのコピー。
    m_buf_sockaddr.resize( res->ai_addrlen );
    memcpy( GetSockAddr(), res->ai_addr, res->ai_addrlen );

    break;
  }

  freeaddrinfo( res_first );
}


// _____________________________________________________________________________
/// @brief デストラクタ
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SocketBase::~SocketBase()
{
  // 今のところ特に何もしない。
  // Closeは外部、もしくは派生先で行うこと。
}



// _____________________________________________________________________________
/// @brief sockaddr_in構造体の設定
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SocketBase::SetSockAddr( sockaddr const* in_sockaddr, int in_size )
{
  m_buf_sockaddr.resize( in_size );
  CPL_MEMCPY( &m_buf_sockaddr[0], in_sockaddr, in_size );
}


// _____________________________________________________________________________
/// @brief イベント待機（拡張イベント付）
///
/// @param[in,out] io_event_param
///   : イベント待機用パラメータ。EventParam参照。
///
/// @retval 0            : 成功。
/// @retval SOCKET_ERROR : 失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketBase::WaitEvents( rio<EventParam> io_event_param ) const
{
  // ネットワークイベントへの関連付け。
  int result;
  WSEventSelect es( m_socket, io_event_param.in_event_flags, result );
  //int flag =
  //  ::WSAEventSelect(
  //      this->m_socket,
  //      evt_sock.GetHandle(),
  //      io_event_param.in_event_flags );
  if( result == SOCKET_ERROR )
  {
    io_event_param.out_error = WIN::OutputWSALastErrorMessage();
    return SOCKET_ERROR;
  }
  
  // イベントテーブル構築
  WSAEVENT evt_table[2+16] = {
    es.GetEvent(),
    m_evt_exit.GetHandle()
  };
  int event_num = 2;
  if( io_event_param.in_events )
  {
    int num = io_event_param.in_event_num;
    Clamp<int>( num, 0, CPL_ARRAY_NUM(evt_table)-2 );
    CPL_LOOP2( num, idx )
    {
      evt_table[ idx+2 ] = io_event_param.in_events[ idx ];
    }
    event_num += num;
  }

  DWORD flag_result =
    WSAWaitForMultipleEvents
      ( event_num
      , evt_table
      , FALSE
      , WSA_INFINITE
      , FALSE
      );

  //{
  //  DbgMsg msg( SwS("イベント発生！") );
  //}

  if( flag_result == WSA_WAIT_FAILED ) // エラー
  {
    io_event_param.out_error = WIN::OutputWSALastErrorMessage();
    return SOCKET_ERROR;
  }
  else
  if( flag_result == WSA_WAIT_TIMEOUT )
  {
    io_event_param.out_event_flags = 0;
    return 0;
  }
  else
  if( flag_result == WSA_WAIT_EVENT_0 + 1 ) // exit
  {
    io_event_param.out_event_flags = -1;
    io_event_param.out_event_index = 1;
    return 0;
  }
  //ef( Limit<int>( flag_result-WSA_WAIT_EVENT_0, 2, 2+event_num ) == 0 )
  else
  {
    io_event_param.out_event_index = flag_result - WSA_WAIT_EVENT_0;
  }
  // ネットワークイベントを取得。
  WSANETWORKEVENTS events;
  result =
    WSAEnumNetworkEvents
      ( m_socket
      , es.GetEvent()
      , &events
      );
  if( result == SOCKET_ERROR )
  {
    io_event_param.out_error = WIN::OutputWSALastErrorMessage();
    return result;
  }

  io_event_param.out_event_flags = events.lNetworkEvents;

#ifdef CPL_DBGSW_WINSOCK_event
  Core::String module_name, str_evt;
  GetModuleName( module_name );
  if( io_event_param.out_event_flags & FD_ACCEPT )
  {
    str_evt << SwS("FD_ACCEPT");
  }
  if( io_event_param.out_event_flags & FD_CONNECT )
  {
    str_evt << SwS(" FD_CONNECT");
  }
  if( io_event_param.out_event_flags & FD_READ )
  {
    str_evt << SwS("FD_READ");
  }
  if( io_event_param.out_event_flags & FD_WRITE )
  {
    str_evt << SwS("FD_WRITE");
  }
  if( io_event_param.out_event_flags & FD_CLOSE )
  {
    str_evt << SwS("FD_CLOSE");
  }

  CPL_WIN_OUTPUT_EX
    ( "%s: (Event:%d)\n"
    , (str_cptr)str_evt
    , (int)io_event_param.out_event_flags
    );
#endif

  CPL_BREAK(
    io_event_param.out_event_flags == 0  &&
    io_event_param.out_event_index == 0 );

  return 0;
}



// _____________________________________________________________________________
/// @brief 
/// ソケットを閉じる。
/// 戻り値はclosesocket()をググってちょ。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
int SocketBase::Close()
{
  if( m_socket == INVALID_SOCKET )
  {
    return 0;
  }

  AutoThreadLock_GrobalSockList locking;

  Core::String module_name;
  GetModuleName( module_name );
  str_type str[ 1024 ];
  CPL_SPRINTF( str,
    SwS("%s: [P:%d][T:%d]: SocketBase::Close()"),
    (str_cptr)module_name,
    (int)GetCurrentProcessId(),
    (int)Core::GetThreadID() );

  //WSACloseEvent( m_evt_exit );

  //this->ExitOn(); // 離脱イベントON
  // スレッド終了
  //WIN::Thread::Close();
  //WIN::Thread::WaitFinish();

  ::shutdown( m_socket, SD_BOTH );
  int ret = ::closesocket( m_socket );

  On_Closed( GlobalFindSocket( this ) );
  
  m_socket = INVALID_SOCKET;

  return ret;
}


// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// acceptイベント
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SocketBase::On_Accepted( cref<SpSocket> in_sp_socket )
{
  CPL_SAFE_PTR( m_sp_sock_parent )->On_Accepted( in_sp_socket );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// クローズイベント
// 引数は自身のポインタを示す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
void SocketBase::On_Closed( cref<SpSocket> in_sp_socket )
{
  CPL_SAFE_PTR( m_sp_sock_parent )->On_Closed( in_sp_socket );

  if( in_sp_socket.get() == this )
  {
    m_sp_sock_parent = nullptr;
    // ソケットのスマートポインタ登録を解除する。
    GlobalCancelSocket( in_sp_socket );
  }
}



// _____________________________________________________________________________
/// @brief グローバルリストに登録。
///
/// @param[in] in_sp_socket
///   : 登録対象のソケット歩インタ。
///
/// @retval true  : 登録成功。
/// @retval false : 登録失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool SocketBase::GlobalEntrySocket( cref<SpSocket> in_sp_socket )
{
  if( in_sp_socket == nullptr  ||
      in_sp_socket->GetSOCKET() == INVALID_SOCKET )
  {
    return false;
  }

  AutoThreadLock_GrobalSockList locking;

  auto& socket_list = sm_socket_list();
  auto itr_sp_socket =
    std::find( socket_list.begin(), socket_list.end(), in_sp_socket );
  if( itr_sp_socket == socket_list.end() ) // 見つからない（つまり、まだ未登録）
  {
    sm_socket_list().push_back( in_sp_socket );
    return true;
  }
  return false;
}


// _____________________________________________________________________________
/// @brief グローバルリストから解除。
///
/// @param[in] in_sp_socket
///   : 登録解除対象のソケットポインタ。
///
/// @retval true  : 解除成功。
/// @retval false : 解除失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool SocketBase::GlobalCancelSocket( cref<SpSocket> in_sp_socket )
{
  AutoThreadLock_GrobalSockList locking;

  auto& socket_list = sm_socket_list();
  auto itr_sp_socket =
    std::find( socket_list.begin(), socket_list.end(), in_sp_socket );
  if( itr_sp_socket != socket_list.end() ) // 見つかった場合
  {
    sm_socket_list().erase( itr_sp_socket );
    return true;
  }
  return false;
}


// _____________________________________________________________________________
/// @brief グローバルリストから解除（ID指定版）
///
/// @param[in] in_socket_ID
///   : 登録解除対象のソケットID.
///
/// @retval true  : 解除成功。
/// @retval false : 解除失敗。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
bool SocketBase::GlobalCancelSocket( size_t in_socket_ID )
{
  AutoThreadLock_GrobalSockList locking;
  
  auto& socket_list = sm_socket_list();
  //CPL_LOOP_ITR( socket_list, itr_sp_socket )
  //for( SocketList::iterator itr_sp_socket : socket_list )
  if( !socket_list.empty() )
  {
    for( auto itr_sp_socket = socket_list.begin();
         itr_sp_socket != socket_list.end();
         ++itr_sp_socket )
    {
      if( *itr_sp_socket )
      {
        if( **itr_sp_socket == (SOCKET)in_socket_ID )
        {
          sm_socket_list().erase( itr_sp_socket );
          return true;
        }
      }
    }
  }

  return false;
}



// _____________________________________________________________________________
/// @brief グローバルリストからソケットの検索。
///
/// @param[in] in_ptr_socket
///   : 比較するソケットクラスのポインタ。
///
/// @return 一致したスマートポインタ。該当なしでnullptrを返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SpSocket SocketBase::GlobalFindSocket( Socket_i_ const* in_ptr_socket )
{
  AutoThreadLock_GrobalSockList locking;

  auto& socket_list = sm_socket_list();
  //CPL_LOOP_ITR( socket_list, itr_sp_socket )
  //for( auto itr_sp_socket = socket_list.begin();
  //     itr_sp_socket != socket_list.end();
  //     ++itr_sp_socket )
  for( auto sp_socket : socket_list )
  {
    if( sp_socket.get() == in_ptr_socket )
    {
      return sp_socket;
    }
  }

  return nullptr;
}


// _____________________________________________________________________________
/// @brief グローバルリストからソケットの検索。
///
/// @param[in] in_socket_ID
///   : 比較するソケットID。
///
/// @return 一致したスマートポインタ。該当なしでnullptrを返す。
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
SpSocket SocketBase::GlobalFindSocket( size_t in_socket_ID )
{
  AutoThreadLock_GrobalSockList locking;
  
  for( auto sp_socket : sm_socket_list() )
  {
    if( sp_socket )
    {
      if( *sp_socket == (SOCKET)in_socket_ID )
      {
        return sp_socket;
      }
    }
  }

  return nullptr;
}




} // namespace WIN
} // namespace CPL
