#include "device.h"
#include "assert.h"

CDevice::CDevice()
{
    m_hDevice = INVALID_HANDLE_VALUE;
}

CDevice::~CDevice()
{
    if (m_hDevice != INVALID_HANDLE_VALUE)
    {
        CloseHandle(m_hDevice);
        m_hDevice = INVALID_HANDLE_VALUE;
    }
}

BOOL CDevice::Init(BOOL ovrl, UINT index)
{
    PWCHAR DevicePath = NULL;
    if ((DevicePath = GetDevicePath(index, (LPGUID)&GUID_VIOSERIAL_PORT)) != NULL)
    {
        m_hDevice = CreateFile(DevicePath,
                             GENERIC_WRITE | GENERIC_READ,
                             0,
                             NULL,
                             OPEN_EXISTING,
                             ovrl ? FILE_FLAG_OVERLAPPED : FILE_ATTRIBUTE_NORMAL,
                             NULL );

        if (m_hDevice != INVALID_HANDLE_VALUE)
        {
            printf("Open vioserial device  %S.\n", DevicePath);
            return TRUE;
        }

    }
    DWORD err = GetLastError();
    printf("Cannot find vioserial device. %S , error = %d\n", DevicePath, err );
    return FALSE;
}

BOOL CDevice::Write(PVOID buf, size_t *size)
{
    BOOL res = FALSE;
    ULONG ret = 0;
    DWORD bytes = *size;

    if (!buf) return FALSE;

    res = WriteFile ( m_hDevice,
                      buf,
                      bytes,
                      &ret,
                      NULL
                     );
    if (!res)
    {
        printf("Cannot write vioserial device.\n");
    }
    else if ( ret != bytes)
    {
        printf("Write vioserial device error. written = 0x%x, expected = 0x%x\n", ret, bytes);
        *size = ret;
        ret = FALSE;
    }
    return res;
}

BOOL CDevice::WriteEx(PVOID buf, size_t *size)
{
    BOOL res = FALSE;
    ULONG ret = 0;
    DWORD bytes = *size;
    OVERLAPPED  ol = {0};

    assert( buf );

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ol.hEvent)
    {
        printf("Event creation failed.\n");
        return FALSE;
    }

    res = WriteFile ( m_hDevice,
                      buf,
                      bytes,
                      &ret,
                      &ol
                     );
    if (!res)
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
           printf("Write failed but isn't delayed.\n");
           res = FALSE;
        }
        else
        {
           if (!GetOverlappedResult(m_hDevice, &ol, &ret, TRUE))
           {
              res = FALSE;
           }
           else
           {
              *size = ret;
              res = TRUE;
           }
        }
    }
    else
    {
        *size = ret;
        res = TRUE;
    }

    CloseHandle( ol.hEvent );
    return res;
}

BOOL CDevice::Read(PVOID buf, size_t *size)
{
    BOOL res = FALSE;
    DWORD ret;
    DWORD bytes = *size;

    if (!buf) return FALSE;

    memset(buf, '\0', bytes);

    res = ReadFile ( m_hDevice,
                    buf,
                    bytes,
                    &ret,
                    NULL
                   );
    if (!res)
    {

        printf ("PerformReadTest: ReadFile failed: "
                "Error %d\n", GetLastError());
    }
    else if ( ret != bytes)
    {
        printf("Read vioserial device error. get = 0x%x, expected = 0x%x\n", ret, bytes);
        *size = ret;
        ret = FALSE;
    }

    return res;
}

BOOL CDevice::ReadEx(PVOID buf, size_t *size)
{
    BOOL res = FALSE;
    DWORD ret;
    DWORD bytes = *size;
    OVERLAPPED  ol = {0};

    assert(buf);

    memset(buf, '\0', bytes);

    ol.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (!ol.hEvent)
    {
        printf("Event creation failed.\n");
        return FALSE;
    }

    res = ReadFile ( m_hDevice,
                    buf,
                    bytes,
                    &ret,
                    &ol
                   );
    if (!res)
    {
        if (GetLastError() != ERROR_IO_PENDING)
        {
           printf("Write failed but isn't delayed.\n");
           res = FALSE;
        }
        else
        {
           if (!GetOverlappedResult(m_hDevice, &ol, &ret, TRUE))
           {
              res = FALSE;
           }
           else
           {
              *size = ret;
              res = TRUE;
           }
        }
    }
    else
    {
        *size = ret;
        res = TRUE;
    }

    CloseHandle( ol.hEvent );
    return res;













    if (!res)
    {

        printf ("PerformReadTest: ReadFile failed: "
                "Error %d\n", GetLastError());
    }
    else if ( ret != bytes)
    {
        printf("Read vioserial device error. get = 0x%x, expected = 0x%x\n", ret, bytes);
        *size = ret;
        ret = FALSE;
    }

    return res;
}

BOOL CDevice::GetInfo(PVOID buf, size_t *size)
{
    BOOL    res = FALSE;
    DWORD   ulOutLength = *size;
    ULONG   ulReturnedLength = 0;
    PVOID   pBuffer = NULL;
    DWORD   err;

    printf ("%s, buf = %p, size = %zd\n", __FUNCTION__, buf, *size);
    res = DeviceIoControl(
                             m_hDevice,
                             IOCTL_GET_INFORMATION,
                             NULL,
                             0,
                             buf,
                             ulOutLength,
                             &ulReturnedLength,
                             NULL
                             );

    if ( !res )
    {   err = GetLastError();
        if (err != ERROR_MORE_DATA)
        {
           printf("Ioctl failed with code %d\n", err );
        }
    }
    *size = ulReturnedLength;
    return res;
}

PTCHAR CDevice::GetDevicePath(UINT index, IN  LPGUID InterfaceGuid )
{
    HDEVINFO HardwareDeviceInfo;
    SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
    PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData = NULL;
    ULONG Length, RequiredLength = 0;
    BOOL bResult;

    HardwareDeviceInfo = SetupDiGetClassDevs(
                             InterfaceGuid,
                             NULL,
                             NULL,
                             (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)
                             );

    if (HardwareDeviceInfo == INVALID_HANDLE_VALUE)
    {
        printf("Cannot get class devices.\n");
        return NULL;
    }

    DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    bResult = SetupDiEnumDeviceInterfaces(HardwareDeviceInfo,
                             0,
                             InterfaceGuid,
                             index,
                             &DeviceInterfaceData
                             );

    if (bResult == FALSE) {
        printf("Cannot get enumerate device interfaces.\n");
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        return NULL;
    }

    SetupDiGetDeviceInterfaceDetail(
        HardwareDeviceInfo,
        &DeviceInterfaceData,
        NULL,
        0,
        &RequiredLength,
        NULL
        );

    DeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) LocalAlloc(LMEM_FIXED, RequiredLength);

    if (DeviceInterfaceDetailData == NULL)
    {
        printf("Cannot allocate memory.\n");
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        return NULL;
    }

    DeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

    Length = RequiredLength;

    bResult = SetupDiGetDeviceInterfaceDetail(
                             HardwareDeviceInfo,
                             &DeviceInterfaceData,
                             DeviceInterfaceDetailData,
                             Length,
                             &RequiredLength,
                             NULL
                             );

    if (bResult == FALSE)
    {
        printf("Cannot get device interface details.\n");
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        LocalFree(DeviceInterfaceDetailData);
        return NULL;
    }

    return DeviceInterfaceDetailData->DevicePath;
}
