/*!
  \file
  \brief COM ポートのリセット

  \author Satofumi KAMIMURA

  $Id: resetComPort.cpp 1811 2010-04-30 16:12:05Z satofumi $
*/

#include "resetComPort.h"
#include "DetectOS.h"
#if defined(WINDOWS_OS)
#include <windows.h>
#include <setupapi.h>
#include <stdio.h>
#endif


namespace
{
#if defined(WINDOWS_OS)
    //4D36E978-E325-11CE-BFC1-08002BE10318
    GUID GUID_DEVINTERFACE_COM_DEVICE = {
        0x4D36E978L, 0xE325, 0x11CE,
        {
            0xBF, 0xC1, 0x08, 0x00, 0x2B, 0xE1, 0x03, 0x18
        }
    };


    int deviceId(const char* com_device, HDEVINFO hdi)
    {
        // com_name_ と同じものがあれば、返す
        SP_DEVINFO_DATA sDevInfo;
        sDevInfo.cbSize = sizeof(SP_DEVINFO_DATA);
        for (DWORD i = 0; SetupDiEnumDeviceInfo(hdi, i, &sDevInfo); ++i){

            enum { BufferSize = 128 };
            char buffer[BufferSize];
            DWORD dwRegType;
            DWORD dwSize = 0;

            // フレンドリーネームを取得し COM 番号とマッチするかで検出を判定する
            SetupDiGetDeviceRegistryProperty(hdi, &sDevInfo, SPDRP_FRIENDLYNAME,
                                             &dwRegType, (BYTE*)buffer,
                                             BufferSize, &dwSize);
            if (dwSize < 7) {
                continue;
            }
            char* p = strstr(&buffer[dwSize - 7], "COM");
            if (! p) {
                continue;
            }

            if (! strncmp(com_device, p, strlen(com_device))) {
                return i;
            }
        }

        return -1;
    }


    void setDeviceState(int state, int device_id, HDEVINFO hdi)
    {
        SP_DEVINFO_DATA sDevInfo;
        sDevInfo.cbSize = sizeof(SP_DEVINFO_DATA);
        if (! SetupDiEnumDeviceInfo(hdi, device_id, &sDevInfo)) {
            return;
        }

        SP_PROPCHANGE_PARAMS sPropChange;
        sPropChange.ClassInstallHeader.cbSize =
            sizeof(sPropChange.ClassInstallHeader);

        sPropChange.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
        sPropChange.Scope = DICS_FLAG_GLOBAL;
        sPropChange.StateChange = state;
        if (! SetupDiSetClassInstallParams(hdi, &sDevInfo,
                                           &sPropChange.ClassInstallHeader,
                                           sizeof(sPropChange))) {
            return;
        }
        if (! SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hdi, &sDevInfo)) {
            return;
        }

        sPropChange.ClassInstallHeader.InstallFunction = DIF_PROPERTYCHANGE;
        sPropChange.Scope = DICS_FLAG_CONFIGSPECIFIC;
        sPropChange.StateChange = state;
        if (! SetupDiSetClassInstallParams(hdi, &sDevInfo,
                                           &sPropChange.ClassInstallHeader,
                                           sizeof(sPropChange))) {
            return;
        }
        if (! SetupDiCallClassInstaller(DIF_PROPERTYCHANGE, hdi, &sDevInfo)) {
            return;
        }

        return;
    }


    void disableDevice(int device_id, HDEVINFO hdi)
    {
        setDeviceState(DICS_DISABLE, device_id, hdi);
    }


    void enableDevice(int device_id, HDEVINFO hdi)
    {
        setDeviceState(DICS_ENABLE, device_id, hdi);
    }
#endif
}


bool qrk::resetComPort(const char* com_device)
{
#if defined(WINDOWS_OS)
    HDEVINFO hdi = SetupDiGetClassDevs(&GUID_DEVINTERFACE_COM_DEVICE, 0, 0,
                                       DIGCF_PRESENT | DIGCF_INTERFACEDEVICE);
    if (hdi == INVALID_HANDLE_VALUE) {
        return false;
    }


    int id = deviceId(com_device, hdi);
    if (id >= 0) {
        disableDevice(id, hdi);
        enableDevice(id, hdi);
    }

    SetupDiDestroyDeviceInfoList(hdi);

    return true;
#else
    // Linux では実装しない
    static_cast<void>(com_device);

    return false;
#endif
}
