#include "pipe.h"
#include "log.h"

PipeServer::PipeServer(const std::wstring& sName) : m_sPipeName(sName),
                                                m_hPipe(INVALID_HANDLE_VALUE),
                                                m_hThread(NULL),
                                                m_buffer(NULL)
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);
    m_buffer = new wchar_t[DATA_BUFFER_LENGTH];
}

PipeServer::~PipeServer(void)
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);
    delete[] m_buffer;
    m_buffer = NULL;
}

void PipeServer::SetData(std::wstring& sData)
{
    PrintMessage(L"%ws data = %ws\n", __FUNCTIONW__, sData.c_str());
    memset(&m_buffer[0], 0, DATA_BUFFER_SIZE);
    wcsncpy_s(&m_buffer[0], DATA_BUFFER_LENGTH, sData.c_str(), __min(DATA_BUFFER_LENGTH, sData.size()));
}

void PipeServer::GetData(std::wstring& sData)
{
    sData.clear();
    sData.append(m_buffer);
    PrintMessage(L"%ws data = %ws\n", __FUNCTIONW__, sData.c_str());
}

bool PipeServer::Init()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    if(m_sPipeName.empty()) {
        PrintMessage(L"Error: Invalid pipe name\n");
        return false;
    }

    m_hPipe = ::CreateNamedPipe(
            m_sPipeName.c_str(),
            PIPE_ACCESS_DUPLEX,
            PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
            PIPE_UNLIMITED_INSTANCES,
            DATA_BUFFER_SIZE,
            DATA_BUFFER_SIZE, 
            NMPWAIT_USE_DEFAULT_WAIT,
            NULL);

    if(INVALID_HANDLE_VALUE == m_hPipe) {
        PrintMessage(L"CreateNamedPipe failed with error 0x%x\n", GetLastError());
        return false;
    }

    m_hThread = ::CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)ServerThread,
        (LPVOID)this,
        0,
        NULL);

    if (NULL == m_hThread) {
        PrintMessage(L"CreateThread failed with error 0x%x\n", GetLastError());
        return false;
    }
    return true;
}

void PipeServer::Run()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    WaitForClient();
    while (Read()) {
        std::wstring command;
        GetData(command);
        if (!command.empty()) {
            
            if (!_wcsnicmp(CONNECTED, command.c_str(), command.size())) {

            } else if (!_wcsnicmp(CLOSED, command.c_str(), command.size())) {

            } else {

            }
       }
    }
}

DWORD WINAPI PipeServer::ServerThread(LPVOID ptr)
{
    PipeServer* pServer = reinterpret_cast<PipeServer*>(ptr);
    pServer->Run();
    return 0;
}

bool PipeServer::WaitForClient()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    if(!ConnectNamedPipe(m_hPipe, NULL)) {
        if (ERROR_PIPE_CONNECTED != GetLastError()) {
            PrintMessage(L"GetLastError failed 0x%x\n", GetLastError());
            return false;
        }
    }
    return true;
}

void PipeServer::Close()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    ::FlushFileBuffers(m_hPipe);
    ::DisconnectNamedPipe(m_hPipe);
    ::CloseHandle(m_hPipe);
    m_hPipe = INVALID_HANDLE_VALUE;
    ::CloseHandle(m_hThread);
    m_hThread = NULL;
}

bool PipeServer::Read()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    DWORD drBytes = 0;
    BOOL bFinishedRead = FALSE;
    int read = 0;
    int left = DATA_BUFFER_SIZE * sizeof(wchar_t);
    do {

        bFinishedRead = ::ReadFile(
            m_hPipe,
            &m_buffer[read],
            left,
            &drBytes,
            NULL);

        if(!bFinishedRead && ERROR_MORE_DATA != GetLastError()) {
            bFinishedRead = FALSE;
            break;
        }

        read += drBytes;
        left -= drBytes;

    } while(!bFinishedRead);

    if(FALSE == bFinishedRead || 0 == drBytes)
    {
        PrintMessage(L"ReadFile failed\n");
        return false;
    }
    return true;
}

bool PipeServer::Write()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    DWORD dwBytes;
    BOOL bResult = ::WriteFile(
        m_hPipe,
        m_buffer,
        (DWORD)(::wcslen(m_buffer)*sizeof(wchar_t) + 1),
        &dwBytes,
        NULL);

    if(FALSE == bResult || wcslen(m_buffer)*sizeof(wchar_t) + 1 != dwBytes)
    {
        PrintMessage(L"WriteFile failed\n");
        return false;
    }
    return true;
}

PipeClient::PipeClient(std::wstring& sName) : m_sPipeName(sName),
                                                m_hPipe(INVALID_HANDLE_VALUE),
                                                m_hThread(NULL),
                                                m_buffer(NULL)
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);
    m_buffer = new wchar_t[DATA_BUFFER_LENGTH];
}

PipeClient::~PipeClient(void)
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);
    delete[] m_buffer;
    m_buffer = NULL;
}

void PipeClient::SetData(std::wstring& sData)
{
    PrintMessage(L"%ws data = %ws\n", __FUNCTIONW__, sData.c_str());
    memset(&m_buffer[0], 0, DATA_BUFFER_SIZE);
    wcsncpy_s(&m_buffer[0], DATA_BUFFER_LENGTH, sData.c_str(), __min(DATA_BUFFER_LENGTH, sData.size()));
}

void PipeClient::GetData(std::wstring& sData)
{
    sData.clear();
    sData.append(m_buffer);
    PrintMessage(L"%ws data = %ws\n", __FUNCTIONW__, sData.c_str());
}

bool PipeClient::Init()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    if(m_sPipeName.empty())
    {
        return false;
    }

    m_hThread = ::CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE)ClientThread,
        (LPVOID)this,
        0,
        NULL);

    if (NULL == m_hThread)
    {
        PrintMessage(L"Cannot create thread Error = %d.\n", GetLastError());
        return false;
    }
    return true;
}

DWORD WINAPI PipeClient::ClientThread(LPVOID ptr)
{
    PipeClient* pClient = reinterpret_cast<PipeClient*>(ptr);
    pClient->Run();
    return 0;
}

void PipeClient::Run()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    ConnectToServer();
    while (Read()) {
        std::wstring command;
        GetData(command);
        if (!command.empty()) {
            
            if (!_wcsnicmp(CLOSE, command.c_str(), command.size()))
            {
                std::wstring command(CLOSED);
                SetData(command);
                Write();
                return;
            } else
            {
                PrintMessage(L"Invalid command = %ws.\n", command.c_str());
            }
       }
    }
}

void PipeClient::WaitRunning()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    if (NULL != m_hThread) {
        WaitForSingleObject(m_hThread, INFINITE);
    }
}

void PipeClient::ConnectToServer()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    m_hPipe = ::CreateFile(
        m_sPipeName.c_str(),
        GENERIC_READ | GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        0,
        NULL);

    if(INVALID_HANDLE_VALUE == m_hPipe)
    {
        PrintMessage(L"Could not connect to pipe server\n");
    }
    else
    {
        PrintMessage(L"Connected to the Pipe Server\n");
    }
}

void PipeClient::Close()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    ::FlushFileBuffers(m_hPipe);
    ::DisconnectNamedPipe(m_hPipe);
    ::CloseHandle(m_hPipe);
    m_hPipe = INVALID_HANDLE_VALUE;
    ::CloseHandle(m_hThread);
    m_hThread = NULL;
}

bool PipeClient::Read()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    DWORD drBytes = 0;
    BOOL bFinishedRead = FALSE;
    int read = 0;
    do
    {
        bFinishedRead = ::ReadFile(
            m_hPipe,
            &m_buffer[read],
            DATA_BUFFER_SIZE,
            &drBytes,
            NULL);

        if(!bFinishedRead && ERROR_MORE_DATA != GetLastError())
        {
            bFinishedRead = FALSE;
            break;
        }
        read += drBytes;

    }while(!bFinishedRead);

    if(FALSE == bFinishedRead || 0 == drBytes)
    {
        PrintMessage(L"ReadFile failed\n");
        return false;
    }
    return true;
}

bool PipeClient::Write()
{
    PrintMessage(L"%ws\n", __FUNCTIONW__);

    DWORD dwBytes;
    BOOL bResult = ::WriteFile(
        m_hPipe,
        m_buffer,
        (DWORD)(::wcslen(m_buffer)*sizeof(wchar_t) + 1),
        &dwBytes,
        NULL);

    if(FALSE == bResult || wcslen(m_buffer)*sizeof(wchar_t) + 1 != dwBytes)
    {
        PrintMessage(L"WriteFile failed\n");
        return false;
    }

    return true;
}
