//---------------------------------------------------------------------------

#include <vcl.h>
#include <winsock2.h>
#include "VMSCommLibF.h"
#pragma hdrstop

#include "FrmNetworkF.h"
#include "PacketHandllingF.h"
#include "ClientSessionF.h"
#include "ClientSessionManagerF.h"

//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TFrmNetwork *FrmNetwork;
//---------------------------------------------------------------------------

__fastcall TFrmNetwork::TFrmNetwork(TComponent* Owner)
    : TForm(Owner)
{
    FInitialize = false;
    FStart      = false;
    FLastErr    = 0;
    FListenSock = INVALID_SOCKET;

    reMsg->Lines->Clear();
}
//---------------------------------------------------------------------------
void __fastcall TFrmNetwork::FormClose(TObject *Sender, TCloseAction &Action)
{
    Hide();
}
//---------------------------------------------------------------------------
void __fastcall TFrmNetwork::FormDestroy(TObject *Sender)
{
    Stop();
}
//---------------------------------------------------------------------------
bool __fastcall TFrmNetwork::InitSock()
{
    /// 윈속 초기화
    WSADATA wsa ;
    if (WSAStartup(MAKEWORD(2,2), &wsa) != 0)
        reMsg->Lines->Add("Network library initialize failed");

    FInitialize = true;
    return true;
}
//---------------------------------------------------------------------------
void __fastcall TFrmNetwork::TermSock()
{
    // 윈속 종료
    if (FInitialize)
    {
        WSACleanup();
    }
    FInitialize = false;
}
//---------------------------------------------------------------------------
int TFrmNetwork::LogWrite(int ALogKind, char *AFmt, ...)
{
    va_list ap;
    int     cnt = 0;
    char    szFmtData[MAX_LOG_BUFFER];

    bool bLog = false;
    switch(ALogKind)
    {
    case eLOG_INFO   : bLog = g_LogCfg.Info;    break;
    case eLOG_DATA   : bLog = g_LogCfg.Data;    break;
    case eLOG_ERROR  : bLog = g_LogCfg.Error;   break;
    case eLOG_WARNING: bLog = g_LogCfg.Warning; break;
    case eLOG_DEBUG  : bLog = g_LogCfg.Debug;   break;
    case eLOG_DETAIL : bLog = g_LogCfg.Detail;  break;
    }
    if (!bLog) return -1;

    try
    {
        va_start(ap, AFmt);
        cnt = vsprintf(szFmtData, AFmt, ap);
        va_end(ap);

        IPC_LOG_MESSAGE *pLog = g_logBuff.GetBuff();
        if (pLog)
        {
            memset(pLog->Msg,  0x00, sizeof(pLog->Msg));

            pLog->From   = from_srvTcp;
            pLog->Target = log_sys;
            pLog->Kind   = ALogKind;
            pLog->Flag   = 0;
            pLog->Write  = true;
            pLog->Tm     = Now();
            pLog->Type   = 'S';
            pLog->Obj    = ITSLog;
            pLog->Ctlr   = NULL;
            sprintf(pLog->Msg, "+SVR %s", szFmtData);
            pLog->Len    = strlen(pLog->Msg);

            LogWrite((void*)pLog);

            g_logQ.PushTimeout((DWORD)pLog, 2000);
        }
    }
    catch(Exception &e)
    {
    }
    return cnt;
}
//---------------------------------------------------------------------------
#if 1
void TFrmNetwork::LogWrite(void *pLog)
{
    IPC_LOG_MESSAGE *ALog = (IPC_LOG_MESSAGE*)pLog;
    if (!ALog) return;

    try
    {
        AnsiString  sLogKind = " [XXX] ";
#if 0
        bool bLog = false;
        switch(ALog->Kind)
        {
        case eLOG_INFO   : bLog = g_LogCfg.Info;    sLogKind = " [INF] "; break;
        case eLOG_DATA   : bLog = g_LogCfg.Data;    sLogKind = " [DAT] "; break;
        case eLOG_ERROR  : bLog = g_LogCfg.Error;   sLogKind = " [ERR] "; break;
        case eLOG_WARNING: bLog = g_LogCfg.Warning; sLogKind = " [WAN] "; break;
        case eLOG_DEBUG  : bLog = g_LogCfg.Debug;   sLogKind = " [DBG] "; break;
        case eLOG_DETAIL : bLog = g_LogCfg.Detail;  sLogKind = " [DET] "; break;
        }
        if (!bLog) return -1;
#else
        switch(ALog->Kind)
        {
        case eLOG_INFO   : sLogKind = " [INF] "; break;
        case eLOG_DATA   : sLogKind = " [DAT] "; break;
        case eLOG_ERROR  : sLogKind = " [ERR] "; break;
        case eLOG_WARNING: sLogKind = " [WAN] "; break;
        case eLOG_DEBUG  : sLogKind = " [DBG] "; break;
        case eLOG_DETAIL : sLogKind = " [DET] "; break;
        }
#endif
        try
        {
            if (reMsg->Lines->Count >= g_AppCfg.nMaxLogLines)
                reMsg->Lines->Clear();

            reMsg->Lines->Add(ALog->Tm.FormatString("hh:nn:ss") + "." + Now().FormatString("ss") + sLogKind + String(ALog->Msg));
        }
        catch(Exception &e)
        {
        }
    }
    __finally
    {
    }
}
#endif
//---------------------------------------------------------------------------
bool __fastcall TFrmNetwork::Start()
{
    SOCKET       listenSock = INVALID_SOCKET;
    SOCKADDR_IN  sockAddr;

    FLastErr = 0;

    if (!InitSock())
    {
        SocketError();
        LERROR("Socket library loading fail: %d.%s", FLastErr, FErrorString.c_str());
        return false;
    }

    listenSock = socket(AF_INET, SOCK_STREAM, 0);
    if (listenSock == INVALID_SOCKET)
    {
        SocketError();
        LERROR("Socket socket error: %d.%s", FLastErr, FErrorString.c_str());
        return false;
    }

    int opt = 1;
    setsockopt(listenSock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(int));

    ZeroMemory(&sockAddr, sizeof(sockAddr));
    sockAddr.sin_family      = AF_INET;
    sockAddr.sin_addr.s_addr = INADDR_ANY;
    sockAddr.sin_port        = htons(g_AppCfg.comm.nListenPort);

    FLastErr = bind(listenSock, (SOCKADDR FAR *)&sockAddr, sizeof(sockAddr));
    if (FLastErr != 0)
    {
        SocketError();
        LERROR("Socket bind error: %d.%s", FLastErr, FErrorString.c_str());
        CloseSocket(listenSock);
        return false;
    }

    FLastErr = listen(listenSock, 50);
    if (FLastErr < 0)
    {
        SocketError();
        LERROR("Socket listen error: %d.%s", FLastErr, FErrorString.c_str());
        CloseSocket(listenSock);
        return false;
    }

    FLastErr = WSAAsyncSelect(listenSock, this->Handle, WM_SOCKET_ACCEPT, FD_ACCEPT);
    if (FLastErr != 0)
    {
        SocketError();
        LERROR("Socket WSAAsyncSelect error: %d.%s", FLastErr, FErrorString.c_str());
        CloseSocket(listenSock);
        return false;
    }

    FStart = true;
    FListenSock = listenSock;

    LINFO("TCP Server Start ok: %d", listenSock);

    return true;
}
//---------------------------------------------------------------------------
void __fastcall TFrmNetwork::Stop()
{
    if (FListenSock != INVALID_SOCKET)
    {
        CloseSocket(FListenSock);
        FStart      = false;
        FLastErr    = 0;
        FListenSock = INVALID_SOCKET;
    }
    TermSock();
}
//---------------------------------------------------------------------------
void __fastcall TFrmNetwork::OnAcceptClient(TMessage &Msg)
{
    SOCKET          sLinstenSock = (SOCKET)Msg.WParam;
    WORD            nErr         = WSAGETSELECTERROR(Msg.LParam);
    WORD            nEvent       = WSAGETSELECTEVENT(Msg.LParam);
    TClientSession *pSession     = NULL;

    if (nErr != NO_ERROR)
    {
        SocketError(nErr);
        LERROR("Accept Event error: %d.%s", FLastErr, FErrorString.c_str());
        return;
    }

    LINFO("TCP Client Connect Request");
    if (nEvent != FD_ACCEPT)
    {
        LERROR("OnAcceptClient Unknown Event: %d", nEvent);
        return ;
    }

    SOCKADDR_IN addrClient;
    int         nAddrClient;
    SOCKET      sClientSock = INVALID_SOCKET;

    nAddrClient = sizeof(addrClient);
    sClientSock = accept(sLinstenSock, (struct sockaddr *)&addrClient, (int FAR *)&nAddrClient);
	if (sClientSock == INVALID_SOCKET)
    {
        SocketError();
        LERROR("Accept accept error: %d.%s", FLastErr, FErrorString.c_str());
        return ;
	}

    pSession = ClientSessionManager->CreateClientSession(sClientSock, &addrClient);
    if (!pSession)
    {
        SocketError();
        LERROR("Accept session allocation failed: %s, %d.%s", pSession->IpAddress.c_str(), FLastErr, FErrorString.c_str());
        CloseSocket(sClientSock);
        return ;
    }

    IPC_JOB_MESSAGE *pMsg = g_jobBuff.GetBuff();
    pMsg->ObjPtr = (DWORD)pSession;

    //if (WSAAsyncSelect(pSession->Socket, this->Handle, WM_SOCKET_SELECT, FD_READ | FD_WRITE | FD_CLOSE) > 0)
    if (WSAAsyncSelect(pSession->Socket, this->Handle, WM_SOCKET_SELECT, FD_READ | FD_CLOSE) > 0)
    {
        SocketError();
        CloseSocket(sClientSock);
        LERROR("OnAcceptClient WSAAsyncSelect error: %d.%s", FLastErr, FErrorString.c_str());
        pSession->State = eSS_OnDisconnect;
        pMsg->Type = eTcpClose;
        g_jobQ.PushBlocking((DWORD)pMsg);
        return ;
    }

    LINFO("TCP Client Connect Accept: %s [%d]", pSession->IpAddress.c_str(), sClientSock);//pSession->Port);

    pMsg->Type   = eTcpConnect;
    g_jobQ.PushBlocking((DWORD)pMsg);
}
//---------------------------------------------------------------------------
void __fastcall TFrmNetwork::OnSelectClient(TMessage &Msg)
{
    SOCKET          sClientSock = (SOCKET)Msg.WParam;
    WORD            nErr        = WSAGETSELECTERROR(Msg.LParam);
    WORD            nEvent      = WSAGETSELECTEVENT(Msg.LParam);
    TClientSession *pSession    = NULL;
    u_long          nInBytes;
    int             nFreeSize;
    int             nRecvLen;

    AnsiString  sIpAddress;
    SOCKADDR_IN addrClient;
    int addrlen = sizeof(addrClient);
    getpeername(sClientSock, (SOCKADDR*)&addrClient, &addrlen);
    sIpAddress.printf("%s[%d]", inet_ntoa(addrClient.sin_addr), sClientSock);

    pSession = ClientSessionManager->FindClientSession(sClientSock);
    if (!pSession)
    {
        LERROR("Select client not found from session manager: %s", sIpAddress.c_str());
        CloseSocket(sClientSock);
        return ;
    }

    IPC_JOB_MESSAGE *pMsg = g_jobBuff.GetBuff();

    if (nErr != NO_ERROR)
    {
        SocketError(nErr);
        LERROR("Socket Select Client Event Error: %d.%s", FLastErr, FErrorString.c_str());
        pMsg->Type   = eTcpErrorEvent;
        pMsg->ObjPtr = (DWORD)pSession;
        pSession->State = eSS_OnDisconnect;
        g_jobQ.PushBlocking((DWORD)pMsg);
        //pSession->Disconnect();
        return;
    }

    switch(nEvent)
    {
    case FD_READ:
        {
            pMsg->Type   = eTcpRecv;
            pMsg->ObjPtr = (DWORD)pSession;
            nFreeSize    = pSession->GetFreeSpaceSize();

            nFreeSize = (nFreeSize < MAX_TCP_BUFF) ? nFreeSize : MAX_TCP_BUFF;
            nRecvLen  = recv(pSession->Socket, pMsg->Buff, nFreeSize, 0);
            if (nRecvLen == SOCKET_ERROR)
            {
                if (WSAGetLastError() != WSAEWOULDBLOCK)
                {
                    SocketError();
                    LERROR("Select client recv error: %s, %d.%s", sIpAddress.c_str(), FLastErr, FErrorString.c_str());
                    pSession->State = eSS_OnDisconnect;
                    pMsg->Type = eTcpErrorRecv;
                }
            }
            if (nRecvLen == 0)
            {
                PostMessage(this->Handle, WM_SOCKET_SELECT, sClientSock, FD_READ);
                LERROR("Select client recv length zero: %s, %d.%s", sIpAddress.c_str(), FLastErr, FErrorString.c_str());
                return;
            }
            else
            {
                pMsg->Len = nRecvLen;
                LDEBUG("Select client recv: %s, %4d bytes recv", sIpAddress.c_str(), pMsg->Len);
            }

#if 0
            int   nRcvSize = pSession->GetFreeBuffSize();
            char *pRcvBuff = pSession->GetBuff();
            nErr = ioctlsocket(pSession->Socket, FIONREAD, &nInBytes);
            if (nErr == SOCKET_ERROR)
            {
                SocketError();
                LERROR("Select client ioctlsocket error: %s, %d.%s", sIpAddress.c_str(), FLastErr, FErrorString.c_str());
                pSession->State = eSS_OnDisconnect;
                pMsg->Type = eTcpErrorRecv;
            }
            else
            {
                nRcvSize = nRcvSize > (int)nInBytes ? nInBytes : nRcvSize;
                int nRecvLen = recv(pSession->Socket, pRcvBuff, nRcvSize, 0);
                if (nRecvLen != nRcvSize)
                {
                    SocketError();
                    LERROR("Select client recv error: %s, %d.%s", sIpAddress.c_str(), FLastErr, FErrorString.c_str());
                    pSession->State = eSS_OnDisconnect;
                }
                else
                {
                    LDEBUG("Select client recv: %s, %4d bytes recv, %d InBytes", sIpAddress.c_str(), nRecvLen, (int)nInBytes);
                }
            }
#endif
        }
        break;
    case FD_WRITE:
        break;
    case FD_CLOSE:
        pMsg->Type   = eTcpClose;
        pMsg->ObjPtr = (DWORD)pSession;
        pSession->State = eSS_OnDisconnect;
        LERROR("Socket Select client OnClose: %s", sIpAddress.c_str());
        break;
    }

    g_jobQ.PushBlocking((DWORD)pMsg);
}
//---------------------------------------------------------------------------
int __fastcall TFrmNetwork::CloseSocket(SOCKET& ASock)
{
    LINGER linger;
    int    res = 0;

    if (ASock == INVALID_SOCKET)
    {
        return 0;
    }

    linger.l_onoff  = 1;
    linger.l_linger = 0;

    setsockopt(ASock,
               SOL_SOCKET,
               SO_LINGER,
               (CHAR FAR *)&linger,
               sizeof(linger));

    res = closesocket(ASock);
    ASock = INVALID_SOCKET;
    return res;
}
//---------------------------------------------------------------------------
int __fastcall TFrmNetwork::SocketError(int AError/*=-1*/)
{
    if (AError == -1) FLastErr = WSAGetLastError();
    else FLastErr = AError;

    LPVOID lpMsgBuf = NULL;
    try
    {
        try
        {
            FormatMessage(
                FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
                NULL,
                FLastErr,
                //MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
                MAKELANGID(LANG_ENGLISH, SUBLANG_DEFAULT),
                (LPTSTR) &lpMsgBuf,
                0,
                NULL
            );

            char *pData = (char*)lpMsgBuf;
            if (strlen(pData) > 2)
            {
                pData[strlen(pData)-2] = 0x00;
                pData[strlen(pData)-1] = 0x00;
            }
            FErrorString.printf("%s", pData);
        }
        catch(Exception &e)
        {
            FErrorString.printf("%s, %s", AnsiString(e.ClassName()).c_str(), AnsiString(e.Message).c_str());
        }
    }
    __finally
    {
        if (lpMsgBuf) LocalFree(lpMsgBuf);
        lpMsgBuf = NULL;
    }
    return FLastErr;
}
//---------------------------------------------------------------------------

