#include "StdAfx.h"

SerialPort::SerialPort(wstring LinkName, PnPControl* ptr)
{
    Name = LinkName;
    Notify = NULL;
    NotificationPair.first = NULL;
    NotificationPair.second = NULL;
    Reference = 0;
    Handle = CreateFile(Name.c_str(),
        GENERIC_WRITE | GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (Handle == INVALID_HANDLE_VALUE)
    {
        return;
    }
    ULONG   ulReturnedLength = 0;
    BYTE buf[512];
    PVIRTIO_PORT_INFO inf = (PVIRTIO_PORT_INFO)buf;
    BOOL res = DeviceIoControl(
        Handle,
        IOCTL_GET_INFORMATION,
        NULL,
        0,
        buf,
        sizeof(buf),
        &ulReturnedLength,
        NULL);
    if (res == FALSE)
    {
        printf ("Error. DeviceIoControl failed %d.\n", GetLastError());
        return;
    }
    HostConnected = inf->HostConnected;
    string s = inf->Name;

    SymbolicName.resize(s.length(),L' ');
    copy(s.begin(), s.end(), SymbolicName.begin());

    Control = ptr;
    CloseHandle(Handle);
    Handle = INVALID_HANDLE_VALUE;
}
SerialPort::~SerialPort()
{
    ClosePort();
}
void SerialPort::AddRef() {
    InterlockedIncrement(&Reference);
}
void SerialPort::Release() {
    if (InterlockedDecrement(&Reference) == 0) {
      delete this;
    }
}
BOOL SerialPort::OpenPort()
{
    Handle = CreateFile(Name.c_str(),
        GENERIC_WRITE | GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL);
    if (Handle == INVALID_HANDLE_VALUE)
    {
        return FALSE;
    }
    ULONG   ulReturnedLength = 0;
    BYTE buf[512];
    PVIRTIO_PORT_INFO inf = (PVIRTIO_PORT_INFO)buf;
    BOOL res = DeviceIoControl(
        Handle,
        IOCTL_GET_INFORMATION,
        NULL,
        0,
        buf,
        sizeof(buf),
        &ulReturnedLength,
        NULL);
    if (res == FALSE)
    {
        printf ("Error. DeviceIoControl failed %d.\n", GetLastError());
        return FALSE;
    }
    HostConnected = inf->HostConnected;
    Notify = Control->RegisterHandleNotify(Handle);
    return TRUE;
}
void SerialPort::ClosePort()
{
    if (Notify != NULL)
    {
        UnregisterDeviceNotification(Notify);
        Notify = NULL;
    }
    if (Handle != INVALID_HANDLE_VALUE)
    {
        CloseHandle(Handle);
        Handle = INVALID_HANDLE_VALUE;
    }
}

BOOL SerialPort::ReadPort(PVOID buf, size_t *len)
{
    BOOL res = FALSE;
    DWORD ret;
    DWORD bytes = (DWORD)(*len);
    OVERLAPPED  ol = {0};

    if (buf == NULL || *len == 0)
    {
        *len = 0;
        return TRUE;
    }

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if( ol.hEvent == NULL)
    {
        return FALSE;
    }

    res = ReadFile ( Handle,
        buf,
        bytes,
        &ret,
        &ol
        );
    if (!res)
    {
        DWORD err = GetLastError();
        if ( err != ERROR_IO_PENDING)
        {
            printf("Read failed but isn't delayed. Error = %d\n", err);
            res = FALSE;
        }
        else
        {
            if (!GetOverlappedResult(Handle, &ol, &ret, TRUE))
            {
                res = FALSE;
            }
            else
            {
                *len = ret;
                res = TRUE;
            }
        }
    }
    else
    {
        *len = ret;
        res = TRUE;
    }

    CloseHandle( ol.hEvent );
    return res;
}

BOOL SerialPort::WritePort(PVOID buf, size_t *len)
{
    BOOL res = FALSE;
    ULONG ret = 0;
    DWORD bytes = (DWORD)(*len);
    OVERLAPPED  ol = {0};

    if (buf == NULL || *len == 0)
    {
        *len = 0;
        return TRUE;
    }

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

    if( ol.hEvent == NULL)
    {
        return FALSE;
    }

    res = WriteFile ( Handle,
        buf,
        bytes,
        &ret,
        &ol
        );
    if (!res)
    {
        DWORD err = GetLastError();
        if (err != ERROR_IO_PENDING)
        {
            printf("Write failed but isn't delayed. Error = %d\n", err);
            res = FALSE;
        }
        else
        {
            if (!GetOverlappedResult(Handle, &ol, &ret, TRUE))
            {
                res = FALSE;
            }
            else
            {
                *len = ret;
                res = TRUE;
            }
        }
    }
    else
    {
        *len = ret;
        res = TRUE;
    }

    CloseHandle( ol.hEvent );
    return res;
}


void SerialPort::handleEvent(const PnPControl& ref)
{
    PnPNotification Notification = ref.GetNotification();
    switch (Notification.wParam)
    {
    case DBT_CUSTOMEVENT: {
        PDEV_BROADCAST_HANDLE pHdr;
        pHdr = (PDEV_BROADCAST_HANDLE)Notification.lParam;
        if (IsEqualGUID(GUID_VIOSERIAL_PORT_CHANGE_STATUS, pHdr->dbch_eventguid) &&
            (pHdr->dbch_handle == Handle))
        {
            PVIRTIO_PORT_STATUS_CHANGE pEventInfo = (PVIRTIO_PORT_STATUS_CHANGE) pHdr->dbch_data;
            HostConnected = pEventInfo->Reason;
            if (NotificationPair.first)
            {
                NotificationPair.first(NotificationPair.second);
            }
        }
                          }
                          break;
    }
}
