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

  \author Satofumi KAMIMURA

  $Id: resetComPort.cpp 1433 2009-10-20 15:53:11Z 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
}
