#include "StdAfx.h"

PnPControl* PnPControl::Instance = NULL;
PnPControl::Reference = 0;

void PnPControl::FindControllers()
{
    wstring name;
    for (int i = 0; FindInstance(GUID_VIOSERIAL_CONTROLLER, i, name); ++i)
    {
        AddController(name.c_str());
    }
}

void PnPControl::FindPorts()
{
    wstring name;
    for (int i = 0; FindInstance(GUID_VIOSERIAL_PORT, i, name); ++i)
    {
        AddPort(name.c_str());
    }
}


BOOL PnPControl::FindInstance(GUID guid, DWORD idx, wstring& name)
{
    HDEVINFO HardwareDeviceInfo;
    SP_DEVICE_INTERFACE_DATA DeviceInterfaceData;
    HardwareDeviceInfo = SetupDiGetClassDevs(
        &guid,
        NULL,
        NULL,
        (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)
        );

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

    DeviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    if (SetupDiEnumDeviceInterfaces(HardwareDeviceInfo, NULL, &guid, idx, &DeviceInterfaceData))
    {
        DWORD RequiredLength = 0;
        PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData = NULL;
        SP_DEVINFO_DATA DevInfoData = {sizeof(SP_DEVINFO_DATA)};

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

        DeviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) malloc(RequiredLength);

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

        DeviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

        SetupDiGetDeviceInterfaceDetail(
            HardwareDeviceInfo,
            &DeviceInterfaceData,
            DeviceInterfaceDetailData,
            RequiredLength,
            NULL,
            &DevInfoData
            );
        name = DeviceInterfaceDetailData->DevicePath;
        free ((PVOID)DeviceInterfaceDetailData);
        SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
        return TRUE;
    }
    SetupDiDestroyDeviceInfoList(HardwareDeviceInfo);
    return FALSE;
}

void PnPControl::Init( )
{
    DWORD id;
    Thread = CreateThread(
        NULL,
        0,
        (LPTHREAD_START_ROUTINE) ServiceThread,
        (LPVOID)this,
        0,
        &id);

    if (Thread == NULL)
    {
        printf("Cannot create thread Error = %d.\n", GetLastError());
    }

    if ( !InitializeCriticalSectionAndSpinCount(&PortsCS, 0x4000))
    {
        printf("Cannot initialize critical section Error = %d.\n", GetLastError());
    }

}

void PnPControl::Close( )
{
    if (PortNotify != NULL)
    {
        UnregisterDeviceNotification(PortNotify);
        PortNotify = NULL;
    }
    if (ControllerNotify != NULL)
    {
        UnregisterDeviceNotification(ControllerNotify);
        ControllerNotify = NULL;
    }
    if (Wnd && Thread)
    {
        SendMessage(Wnd, WM_DESTROY, 0, 0);
        if (WAIT_TIMEOUT == WaitForSingleObject(Thread, 1000))
        {
            printf("Cannot close thread after 1 sec\n");
            TerminateThread(Thread, 0);
        }
        Thread = NULL;
    }
    DeleteCriticalSection(&PortsCS);
}

DWORD WINAPI PnPControl::ServiceThread(PnPControl* ptr)
{
    ptr->Run();
    return 0;
}

void PnPControl::Run()
{
    MSG Msg;
    WNDCLASSEX wc;

    wc.cbSize        = sizeof(WNDCLASSEX);
    wc.style         = 0;
    wc.lpfnWndProc   = GlobalWndProc;
    wc.cbClsExtra    = 0;
    wc.cbWndExtra    = 0;
    wc.hInstance     = reinterpret_cast<HINSTANCE>(GetModuleHandle(0));
    wc.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
    wc.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
    wc.lpszMenuName  = NULL;
    wc.lpszClassName = L"VioSerialLib";
    wc.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

    if(!RegisterClassEx(&wc))
    {
        printf("Window Registration Failed!\n");
    }

    Wnd = CreateWindowEx(
        WS_EX_CLIENTEDGE,
        L"VioSerialLib",
        NULL,
        WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
        NULL, NULL, NULL, NULL
        );

    if(Wnd == NULL)
    {
        printf("Window Creation Failed!\n");
    }
    SetWindowLongPtr(Wnd, GWLP_USERDATA, (LONG_PTR)(this));
    ControllerNotify = RegisterInterfaceNotify(GUID_VIOSERIAL_CONTROLLER);
    PortNotify = RegisterInterfaceNotify(GUID_VIOSERIAL_PORT);
    while(GetMessage(&Msg, NULL, 0, 0) > 0)
    {
        TranslateMessage(&Msg);
        DispatchMessage(&Msg);
    }
}

LRESULT CALLBACK PnPControl::GlobalWndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
    switch (msg)
    {
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    case WM_DEVICECHANGE:
        ProcessPnPNotification((PnPControl*)(GetWindowLongPtr(hWnd, GWLP_USERDATA)), PnPNotification(msg, wParam, lParam));
        break;
    default:
        return DefWindowProc(hWnd, msg, wParam, lParam);
    }
    return 0;
}

HDEVNOTIFY PnPControl::RegisterInterfaceNotify(
    GUID InterfaceClassGuid
    )
{
    DEV_BROADCAST_DEVICEINTERFACE NotificationFilter;
    HDEVNOTIFY Notify;

    ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
    NotificationFilter.dbcc_size =
        sizeof(DEV_BROADCAST_DEVICEINTERFACE);
    NotificationFilter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE;
    NotificationFilter.dbcc_classguid = InterfaceClassGuid;

    Notify = RegisterDeviceNotification( Wnd,
        &NotificationFilter,
        DEVICE_NOTIFY_WINDOW_HANDLE
        );

    if (Notify == NULL)
    {
        printf("RegisterDeviceNotification failed: %d\n",
            GetLastError());
    }
    return Notify;
}

HDEVNOTIFY PnPControl::RegisterHandleNotify(HANDLE handle)
{
    HDEVNOTIFY Notify;
    DEV_BROADCAST_HANDLE NotificationFilter;

    ZeroMemory( &NotificationFilter, sizeof(NotificationFilter) );
    NotificationFilter.dbch_size =
        sizeof(DEV_BROADCAST_HANDLE);
    NotificationFilter.dbch_devicetype = DBT_DEVTYP_HANDLE;
    NotificationFilter.dbch_handle = handle;
    NotificationFilter.dbch_eventguid = GUID_VIOSERIAL_PORT_CHANGE_STATUS;


    Notify = RegisterDeviceNotification( Wnd,
        &NotificationFilter,
        DEVICE_NOTIFY_WINDOW_HANDLE
        );

    if (Notify == NULL)
    {
        printf("RegisterDeviceNotification failed: %d\n",
            GetLastError());
    }
    return Notify;
}

void PnPControl::ProcessPnPNotification(PnPControl* ptr, PnPNotification Notification)
{
    switch (Notification.wParam)
    {
    case DBT_DEVICEARRIVAL: {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)Notification.lParam;
        if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
            PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
            if (IsEqualGUID(GUID_VIOSERIAL_CONTROLLER, pDevInf->dbcc_classguid))
            {
                ptr->AddController(pDevInf->dbcc_name);
            }
            else if (IsEqualGUID(GUID_VIOSERIAL_PORT, pDevInf->dbcc_classguid))
            {
                ptr->AddPort(pDevInf->dbcc_name);
            }
        }
                            }
                            break;
    case DBT_DEVICEREMOVECOMPLETE: {
        PDEV_BROADCAST_HDR pHdr = (PDEV_BROADCAST_HDR)Notification.lParam;
        if (pHdr->dbch_devicetype == DBT_DEVTYP_DEVICEINTERFACE) {
            PDEV_BROADCAST_DEVICEINTERFACE pDevInf = (PDEV_BROADCAST_DEVICEINTERFACE)pHdr;
            if (IsEqualGUID(GUID_VIOSERIAL_CONTROLLER, pDevInf->dbcc_classguid))
            {
                ptr->RemoveController(pDevInf->dbcc_name);
            }
            else if (IsEqualGUID(GUID_VIOSERIAL_PORT, pDevInf->dbcc_classguid))
            {
                ptr->RemovePort(pDevInf->dbcc_name);
            }
        }
                                   }
                                   break;
    default:
        ptr->DispatchPnpMessage(Notification);
        break;
    }
}

void PnPControl::AddController(const wchar_t* name)
{
    printf ("Add Controller %ws\n", name);
    Controllers.push_back((new SerialController(name)));
}

void PnPControl::RemoveController(wchar_t* name)
{
    printf ("Remove Controller %ws\n", name);
    for(Iterator it = Controllers.begin(); it != Controllers.end(); it++)
    {
        if (_wcsnicmp((*it)->Name.c_str(), name, (*it)->Name.size()) == 0)
        {
            delete *it;
            Controllers.remove(*it);
            break;
        }
    }
}

void PnPControl::AddPort(const wchar_t* name)
{
    printf ("Add Port %ws\n", name);
    EnterCriticalSection(&PortsCS);
    SerialPort* port = new SerialPort(name, this);
    port->AddRef();
    Ports.push_back(port);
    LeaveCriticalSection(&PortsCS);
}

void PnPControl::RemovePort(wchar_t* name)
{
    printf ("Remove Port %ws\n", name);
    EnterCriticalSection(&PortsCS);
    for(Iterator it = Ports.begin(); it != Ports.end(); it++)
    {
        if (_wcsnicmp((*it)->Name.c_str(), name, (*it)->Name.size()) == 0)
        {
            ((SerialPort*)(*it))->Release();
            Ports.remove(*it);
            break;
        }
    }
    LeaveCriticalSection(&PortsCS);
}

BOOL PnPControl::FindPort(const wchar_t* name)
{
    wstring tmp = name;
    EnterCriticalSection(&PortsCS);
    BOOL ret = FALSE;
    for(Iterator it = Ports.begin(); it != Ports.end(); it++)
    {
        if (_wcsnicmp((*it)->SymbolicName.c_str(), name, (*it)->Name.size()) == 0)
        {
            ret = TRUE;
            break;
        }
    }
    LeaveCriticalSection(&PortsCS);
    return ret;
}
PVOID PnPControl::OpenPortByName(const wchar_t* name)
{
    wstring tmp = name;
    PVOID ret = NULL;
    EnterCriticalSection(&PortsCS);
    for(Iterator it = Ports.begin(); it != Ports.end(); it++)
    {
        if (_wcsnicmp((*it)->SymbolicName.c_str(), name, (*it)->Name.size()) == 0)
        {
            SerialPort* port = (SerialPort*)(*it);
            if (port->OpenPort() == TRUE)
            {
                port->AddRef();
                ret = port;
                break;
            }
        }
    }
    LeaveCriticalSection(&PortsCS);
    return ret;
}
PVOID PnPControl::OpenPortById(UINT id)
{
    PVOID ret = NULL;
    EnterCriticalSection(&PortsCS);
    if ((size_t)(id) < NumPorts())
    {
        Iterator it = Ports.begin();
        advance(it, id);
        SerialPort* port = (SerialPort*)(*it);
        if (port->OpenPort() == TRUE)
        {
            port->AddRef();
            ret = port;
        }
    }
    LeaveCriticalSection(&PortsCS);
    return ret;
}
BOOL PnPControl::ReadPort(PVOID port, PVOID buf, PULONG size)
{
    Iterator it;
    EnterCriticalSection(&PortsCS);
    for(it = Ports.begin(); it != Ports.end(); it++)
    {
        if (*it == port)
        {
            break;
        }
    }
    LeaveCriticalSection(&PortsCS);
    return (*it != port) ? FALSE : ((SerialPort*)(*it))->ReadPort(buf, (size_t*)(size));
}
BOOL PnPControl::WritePort(PVOID port, PVOID buf, ULONG size)
{
    Iterator it;
    EnterCriticalSection(&PortsCS);
    for(it = Ports.begin(); it != Ports.end(); it++)
    {
        if (*it == port)
        {
            break;
        }
    }
    LeaveCriticalSection(&PortsCS);
    return (*it != port) ? FALSE : ((SerialPort*)(*it))->WritePort(buf, (size_t*)(&size));
}
VOID PnPControl::ClosePort(PVOID port)
{
    EnterCriticalSection(&PortsCS);
    for(Iterator it = Ports.begin(); it != Ports.end(); it++)
    {
        if (*it == port)
        {
            SerialPort* port = (SerialPort*)(*it);
            port->ClosePort();
            port->Release();
            break;
        }
    }
    LeaveCriticalSection(&PortsCS);
}

wchar_t* PnPControl::PortSymbolicName(int index)
{
    wchar_t* ret = NULL;
    EnterCriticalSection(&PortsCS);
    if ((size_t)(index) < NumPorts())
    {
        Iterator it = Ports.begin();
        advance(it, index);
        ret = (wchar_t*)((*it)->SymbolicName.c_str());
    }
    LeaveCriticalSection(&PortsCS);
    return ret;
}

VOID PnPControl::RegisterNotification(PVOID port, VIOSERIALNOTIFYCALLBACK pfn, PVOID ptr)
{
    for(Iterator it = Ports.begin(); it != Ports.end(); it++)
    {
        if (*it == port)
        {
            ((SerialPort*)(*it))->NotificationPair.first = pfn;
            ((SerialPort*)(*it))->NotificationPair.second = ptr;
            return;
        }
    }
}

BOOL PnPControl::IsRunningAsService()
{
    wchar_t wstrPath[_MAX_FNAME];
    if (!GetModuleFileName( NULL, wstrPath, _MAX_FNAME))
    {
        printf ("Error getting module file name (%d)\n", GetLastError());
        return FALSE;
    }
    printf ("Module File Name is %ws\n", wstrPath);

    wstring fullname(wstrPath);
    size_t pos_begin = fullname.rfind(L'\\') + 1;
    size_t pos_end   = fullname.rfind(L'.');
    wstring filename(fullname, pos_begin, pos_end-pos_begin);
    printf ("File name = %ws\n", filename.c_str());
    SERVICE_STATUS_PROCESS status;
    DWORD                  needed;

    SC_HANDLE scm = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);
    if (scm == NULL)
    {
        printf ("OpenSCManager failed (%d)\n", GetLastError());
        return FALSE;
    }
    SC_HANDLE svc = OpenService (scm, filename.c_str(), SERVICE_ALL_ACCESS);
    if (svc == NULL)
    {
        printf ("OpenService failed (%d)\n", GetLastError());
        return FALSE;
    }
    if(!QueryServiceStatusEx(svc, SC_STATUS_PROCESS_INFO, (LPBYTE) &status, sizeof(SERVICE_STATUS_PROCESS), &needed))
    {
        printf ("QueryServiceStatusEx failed (%d)\n", GetLastError());
        CloseServiceHandle (svc);
        CloseServiceHandle (scm);
        return FALSE;
    }

    CloseServiceHandle (svc);
    CloseServiceHandle (scm);
    return (status.dwProcessId == GetCurrentProcessId());
}
