/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
/*****************************************************************************
*
*   $/Plasma20/Sources/Plasma/NucleusLib/pnAsyncCoreExe/pnAceIo.cpp
*   
***/

#include "Pch.h"
#pragma hdrstop


/****************************************************************************
*
*   ISocketConnHash
*
***/

// socket notification procedures

// connection data format:
//      byte    connType;
//      dword   buildId;    [optional]
//      dword   branchId;   [optional]
//      dword   buildType;  [optional]
//      Uuid    productId;  [optional]
const unsigned kConnHashFlagsIgnore     = 0x01;
const unsigned kConnHashFlagsExactMatch = 0x02;
struct ISocketConnHash {
    unsigned    connType;
    unsigned    buildId;
    unsigned    buildType;
    unsigned    branchId;
    Uuid        productId;
    unsigned    flags;

    unsigned GetHash () const;
    bool operator== (const ISocketConnHash & rhs) const;
};

struct ISocketConnType : ISocketConnHash {
    HASHLINK(ISocketConnType)   hashlink;
    FAsyncNotifySocketProc      notifyProc;
};


static CLock s_notifyProcLock;
static HASHTABLEDECL(
    ISocketConnType,
    ISocketConnHash,
    hashlink
) s_notifyProcs;


//===========================================================================
unsigned ISocketConnHash::GetHash () const {
    CHashValue hash;
    hash.Hash32(connType);
/*
    if (buildId)
        hash.Hash32(buildId);
    if (buildType)
        hash.Hash32(buildType);
    if (branchId)
        hash.Hash32(branchId);
    if (productId != kNilGuid)
        hash.Hash(&productId, sizeof(productId));
*/
    return hash.GetHash();
}

//===========================================================================
bool ISocketConnHash::operator== (const ISocketConnHash & rhs) const {
    ASSERT(flags & kConnHashFlagsIgnore);

    for (;;) {
        // Check connType
        if (connType != rhs.connType)
            break;

        // Check buildId
        if (buildId != rhs.buildId) {
            if (rhs.flags & kConnHashFlagsExactMatch)
                break;
            if (buildId)
                break;
        }

        // Check buildType
        if (buildType != rhs.buildType) {
            if (rhs.flags & kConnHashFlagsExactMatch)
                break;
            if (buildType)
                break;
        }

        // Check branchId
        if (branchId != rhs.branchId) {
            if (rhs.flags & kConnHashFlagsExactMatch)
                break;
            if (branchId)
                break;
        }

        // Check productId
        if (productId != rhs.productId) {
            if (rhs.flags & kConnHashFlagsExactMatch)
                break;
            if (productId != kNilGuid)
                break;
        }

        // Success!
        return true;
    }

    // Failed!
    return false;
}

//===========================================================================
static unsigned GetConnHash (
    ISocketConnHash *   hash,
    const byte          buffer[],
    unsigned            bytes
) {
    if (!bytes)
        return 0;

    if (IS_TEXT_CONNTYPE(buffer[0])) {
        hash->connType  = buffer[0];
        hash->buildId   = 0;
        hash->buildType = 0;
        hash->branchId  = 0;
        hash->productId = 0;
        hash->flags     = 0;

        // one byte consumed
        return 1;
    }
    else {
        if (bytes < sizeof(AsyncSocketConnectPacket))
            return 0;

        const AsyncSocketConnectPacket & connect = * (const AsyncSocketConnectPacket *) buffer;
        if (connect.hdrBytes < sizeof(connect))
            return 0;
        
        hash->connType  = connect.connType;
        hash->buildId   = connect.buildId;
        hash->buildType = connect.buildType;
        hash->branchId  = connect.branchId;
        hash->productId = connect.productId;
        hash->flags     = 0;

        return connect.hdrBytes;
    }
}


/****************************************************************************
*
*   Public exports
*
***/

//===========================================================================
EFileError AsyncGetLastFileError () {
    const unsigned error = GetLastError();
    switch (error) {

        case NO_ERROR:
        return kFileSuccess;

        case ERROR_FILE_NOT_FOUND:
        return kFileErrorFileNotFound;

        case ERROR_ACCESS_DENIED:
        case ERROR_FILE_EXISTS:
        case ERROR_ALREADY_EXISTS:
        return kFileErrorAccessDenied;

        case ERROR_SHARING_VIOLATION:
        return kFileErrorSharingViolation;

        case ERROR_BAD_NETPATH:
        case ERROR_PATH_NOT_FOUND:
        case ERROR_INVALID_NAME:
        case ERROR_BAD_NET_NAME:
        case ERROR_CANT_ACCESS_DOMAIN_INFO:
        case ERROR_NETWORK_UNREACHABLE:
        case ERROR_HOST_UNREACHABLE:
        return kFileErrorPathNotFound;
    }

    LogMsg(kLogPerf, "Unexpected Win32 error [%#x]", error);

    return kFileErrorPathNotFound;
}

//============================================================================
const wchar * FileErrorToString (EFileError error) {
    
    static wchar * s_fileErrorStrings[] = {
        L"FileSuccess",
        L"FileErrorInvalidParameter",
        L"FileErrorFileNotFound",
        L"FileErrorPathNotFound",
        L"FileErrorAccessDenied",
        L"FileErrorSharingViolation",
    };
    COMPILER_ASSERT(kNumFileErrors == arrsize(s_fileErrorStrings));
    
    return s_fileErrorStrings[error];
}

//============================================================================
AsyncFile AsyncFileOpen (
    const wchar             fullPath[],
    FAsyncNotifyFileProc    notifyProc,
    EFileError *            error,
    unsigned                desiredAccess,
    unsigned                openMode,
    unsigned                shareModeFlags,
    void *                  userState,
    qword *                 fileSize,
    qword *                 fileLastWriteTime
) {
    ASSERT(g_api.fileOpen);
    return g_api.fileOpen(
        fullPath,
        notifyProc,
        error,
        desiredAccess,
        openMode,
        shareModeFlags,
        userState,
        fileSize,
        fileLastWriteTime
    );
}

//============================================================================
void AsyncFileClose (
    AsyncFile   file,
    qword       truncateSize
) {
    ASSERT(g_api.fileClose);
    g_api.fileClose(file, truncateSize);
}

//============================================================================
void AsyncFileSetLastWriteTime (
    AsyncFile   file,
    qword       lastWriteTime
) {
    ASSERT(g_api.fileSetLastWriteTime);
    g_api.fileSetLastWriteTime(file, lastWriteTime);
}

//============================================================================
qword AsyncFileGetLastWriteTime (
    const wchar fileName[]
) {
    ASSERT(g_api.fileGetLastWriteTime);
    return g_api.fileGetLastWriteTime(fileName);
}

//============================================================================
AsyncId AsyncFileFlushBuffers (
    AsyncFile   file, 
    qword       truncateSize,
    bool        notify,
    void *      param
) {
    ASSERT(g_api.fileFlushBuffers);
    return g_api.fileFlushBuffers(file, truncateSize, notify, param);
}

//============================================================================
AsyncId AsyncFileRead (
    AsyncFile       file,
    qword           offset,
    void *          buffer,
    unsigned        bytes,
    unsigned        flags,
    void *          param
) {
    ASSERT(g_api.fileRead);
    return g_api.fileRead(
        file,
        offset,
        buffer,
        bytes,
        flags,
        param
    );
}

//============================================================================
AsyncId AsyncFileWrite (
    AsyncFile       file,
    qword           offset,
    const void *    buffer,
    unsigned        bytes,
    unsigned        flags,
    void *          param
) {
    ASSERT(g_api.fileWrite);
    return g_api.fileWrite(
        file,
        offset,
        buffer,
        bytes,
        flags,
        param
    );
}

//============================================================================
AsyncId AsyncFileCreateSequence (
    AsyncFile       file, 
    bool            notify, 
    void *          param
) {
    ASSERT(g_api.fileCreateSequence);
    return g_api.fileCreateSequence(file, notify, param);
}

//============================================================================
bool AsyncFileSeek (
    AsyncFile       file,
    qword           distance,
    EFileSeekFrom   seekFrom
) {
    ASSERT(g_api.fileSeek);
    return g_api.fileSeek(file, distance, seekFrom);
}

//===========================================================================
void AsyncSocketConnect (
    AsyncCancelId *         cancelId,
    const NetAddress &      netAddr,
    FAsyncNotifySocketProc  notifyProc,
    void *                  param,
    const void *            sendData,
    unsigned                sendBytes,
    unsigned                connectMs,
    unsigned                localPort
) {
    ASSERT(g_api.socketConnect);
    g_api.socketConnect(
        cancelId,
        netAddr,
        notifyProc,
        param,
        sendData,
        sendBytes,
        connectMs,
        localPort
    );
}

//===========================================================================
void AsyncSocketConnectCancel (
    FAsyncNotifySocketProc  notifyProc,
    AsyncCancelId           cancelId
) {
    ASSERT(g_api.socketConnectCancel);
    g_api.socketConnectCancel(notifyProc, cancelId);
}

//===========================================================================
void AsyncSocketDisconnect (
    AsyncSocket             sock,
    bool                    hardClose
) {
    ASSERT(g_api.socketDisconnect);
    g_api.socketDisconnect(sock, hardClose);
}

//===========================================================================
void AsyncSocketDelete (AsyncSocket sock) {

    ASSERT(g_api.socketDelete);
    g_api.socketDelete(sock);
}

//===========================================================================
bool AsyncSocketSend (
    AsyncSocket             sock,
    const void *            data,
    unsigned                bytes
) {
    ASSERT(g_api.socketSend);
    return g_api.socketSend(sock, data, bytes);
}

//===========================================================================
bool AsyncSocketWrite (
    AsyncSocket             sock,
    const void *            buffer,
    unsigned                bytes,
    void *                  param
) {
    ASSERT(g_api.socketWrite);
    return g_api.socketWrite(sock, buffer, bytes, param);
}

//===========================================================================
void AsyncSocketSetNotifyProc (
    AsyncSocket             sock,
    FAsyncNotifySocketProc  notifyProc
) {
    ASSERT(g_api.socketSetNotifyProc);
    g_api.socketSetNotifyProc(sock, notifyProc);
}

//===========================================================================
void AsyncSocketSetBacklogAlloc (
    AsyncSocket             sock,
    unsigned                bufferSize
) {
    ASSERT(g_api.socketSetBacklogAlloc);
    g_api.socketSetBacklogAlloc(sock, bufferSize);
}

//===========================================================================
unsigned AsyncSocketStartListening (
    const NetAddress &      listenAddr,
    FAsyncNotifySocketProc  notifyProc
) {
    ASSERT(g_api.socketStartListening);
    return g_api.socketStartListening(listenAddr, notifyProc);
}

//===========================================================================
void AsyncSocketStopListening (
    const NetAddress &      listenAddr,
    FAsyncNotifySocketProc  notifyProc
) {
    ASSERT(g_api.socketStopListening);
    g_api.socketStopListening(listenAddr, notifyProc);
}

//============================================================================
void AsyncSocketEnableNagling (
    AsyncSocket             sock,
    bool                    enable
) {
    ASSERT(g_api.socketEnableNagling);
    g_api.socketEnableNagling(sock, enable);
}

//===========================================================================
void AsyncSocketRegisterNotifyProc (
    byte                    connType, 
    FAsyncNotifySocketProc  notifyProc,
    unsigned                buildId,
    unsigned                buildType,
    unsigned                branchId,
    const Uuid &            productId
) {
    ASSERT(connType != kConnTypeNil);
    ASSERT(notifyProc);

    // Perform memory allocation outside lock
    ISocketConnType * ct    = NEW(ISocketConnType);
    ct->notifyProc          = notifyProc;
    ct->connType            = connType;
    ct->buildId             = buildId;
    ct->buildType           = buildType;
    ct->branchId            = branchId;
    ct->productId           = productId;
    ct->flags               = kConnHashFlagsIgnore;

    s_notifyProcLock.EnterWrite();
    {
        s_notifyProcs.Add(ct);
    }
    s_notifyProcLock.LeaveWrite();
}

//===========================================================================
void AsyncSocketUnregisterNotifyProc (
    byte                    connType, 
    FAsyncNotifySocketProc  notifyProc,
    unsigned                buildId,
    unsigned                buildType,
    unsigned                branchId,
    const Uuid &            productId
) {
    ISocketConnHash hash;
    hash.connType   = connType;
    hash.buildId    = buildId;
    hash.buildType  = buildType;
    hash.branchId   = branchId;
    hash.productId  = productId;
    hash.flags      = kConnHashFlagsExactMatch;

    ISocketConnType * scan;
    s_notifyProcLock.EnterWrite();
    {
        scan = s_notifyProcs.Find(hash);
        for (; scan; scan = s_notifyProcs.FindNext(hash, scan)) {
            if (scan->notifyProc != notifyProc)
                continue;

            // Unlink the object so it can be deleted outside the lock
            s_notifyProcs.Unlink(scan);
            break;
        }
    }
    s_notifyProcLock.LeaveWrite();

    // perform memory deallocation outside the lock
    DEL(scan);
}

//===========================================================================
FAsyncNotifySocketProc AsyncSocketFindNotifyProc (
    const byte  buffer[],
    unsigned    bytes,
    unsigned *  bytesProcessed,
    unsigned *  connType,
    unsigned *  buildId,
    unsigned *  buildType,
    unsigned *  branchId,
    Uuid *      productId
) {
    for (;;) {
        // Get the connType
        ISocketConnHash hash;
        *bytesProcessed = GetConnHash(&hash, buffer, bytes);
        if (!*bytesProcessed)
            break;

        // Lookup notifyProc based on connType
        FAsyncNotifySocketProc proc;
        s_notifyProcLock.EnterRead();
        if (const ISocketConnType * scan = s_notifyProcs.Find(hash))
            proc = scan->notifyProc;
        else
            proc = nil;
        s_notifyProcLock.LeaveRead();
        if (!proc)
            break;

        // Success!
        *connType   = hash.connType;
        *buildId    = hash.buildId;
        *buildType  = hash.buildType;
        *branchId   = hash.branchId;
        *productId  = hash.productId;
        return proc;
    }

    // Failure!
    PerfAddCounter(kAsyncPerfSocketDisconnectInvalidConnType, 1);
    *bytesProcessed = 0;
    *connType       = 0;
    *buildId        = 0;
    *buildType      = 0;
    *branchId       = 0;
    *productId      = 0;
    return nil;
}