#include "PlatformDialog.h"

#include "xhex.h"
#if defined(__WINDOWS__)
#include <wx/msw/registry.h>
#endif
#include <wx/config.h>
#include <wx/dir.h>

//(*InternalHeaders(PlatformDialog)
#include <wx/intl.h>
#include <wx/button.h>
#include <wx/string.h>
//*)

//(*IdInit(PlatformDialog)
const long PlatformDialog::ID_COMBOBOX_PLATFORM = wxNewId();
const long PlatformDialog::ID_COMBOBOX_MCU = wxNewId();
const long PlatformDialog::ID_COMBOBOX_BITCLOCK = wxNewId();
const long PlatformDialog::ID_COMBOBOX_PROGRAMMER = wxNewId();
const long PlatformDialog::ID_COMBOBOX_INTERFACE = wxNewId();
const long PlatformDialog::ID_COMBOBOX_BAUDRATE = wxNewId();
//*)

BEGIN_EVENT_TABLE(PlatformDialog,wxDialog)
  //(*EventTable(PlatformDialog)
  //*)
END_EVENT_TABLE()

PlatformDialog::PlatformDialog(wxWindow* parent,wxWindowID id) {
  //(*Initialize(PlatformDialog)
  wxStaticText* StaticTextMCU;
  wxStaticText* StaticTextPlatform;
  wxBoxSizer* BoxSizer1;
  wxFlexGridSizer* FlexGridSizer1;

  Create(parent, id, _("Platform Configuration"), wxDefaultPosition, wxDefaultSize, wxCAPTION|wxDEFAULT_DIALOG_STYLE|wxRESIZE_BORDER|wxCLOSE_BOX, _T("id"));
  SetMinSize(wxSize(250,250));
  BoxSizer1 = new wxBoxSizer(wxVERTICAL);
  FlexGridSizer1 = new wxFlexGridSizer(0, 2, 0, 0);
  FlexGridSizer1->AddGrowableCol(1);
  StaticTextPlatform = new wxStaticText(this, wxID_ANY, _("Platform:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
  FlexGridSizer1->Add(StaticTextPlatform, 0, wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
  ComboBoxPlatform = new wxComboBox(this, ID_COMBOBOX_PLATFORM, wxEmptyString, wxDefaultPosition, wxSize(220,-1), 0, 0, 0, wxDefaultValidator, _T("ID_COMBOBOX_PLATFORM"));
  FlexGridSizer1->Add(ComboBoxPlatform, 1, wxALL|wxEXPAND|wxALIGN_LEFT|wxALIGN_TOP, 5);
  StaticTextMCU = new wxStaticText(this, wxID_ANY, _("MCU:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
  FlexGridSizer1->Add(StaticTextMCU, 0, wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
  ComboBoxMCU = new wxComboBox(this, ID_COMBOBOX_MCU, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_COMBOBOX_MCU"));
  ComboBoxMCU->Disable();
  FlexGridSizer1->Add(ComboBoxMCU, 1, wxALL|wxEXPAND|wxALIGN_LEFT|wxALIGN_TOP, 5);
  StaticTextBitclock = new wxStaticText(this, wxID_ANY, _("Bitclock:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
  FlexGridSizer1->Add(StaticTextBitclock, 0, wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
  ComboBoxBitclock = new wxComboBox(this, ID_COMBOBOX_BITCLOCK, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_COMBOBOX_BITCLOCK"));
  ComboBoxBitclock->Append(_("default"));
  ComboBoxBitclock->Append(_("1"));
  ComboBoxBitclock->Append(_("2"));
  ComboBoxBitclock->Append(_("3"));
  ComboBoxBitclock->Append(_("4"));
  ComboBoxBitclock->Append(_("5"));
  ComboBoxBitclock->Append(_("6"));
  ComboBoxBitclock->Append(_("7"));
  ComboBoxBitclock->Append(_("8"));
  ComboBoxBitclock->Append(_("10"));
  ComboBoxBitclock->Append(_("15"));
  ComboBoxBitclock->Append(_("20"));
  ComboBoxBitclock->Append(_("50"));
  ComboBoxBitclock->Append(_("100"));
  ComboBoxBitclock->Disable();
  FlexGridSizer1->Add(ComboBoxBitclock, 1, wxALL|wxEXPAND|wxALIGN_LEFT|wxALIGN_TOP, 5);
  StaticTextProgrammer = new wxStaticText(this, wxID_ANY, _("Programmer:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
  FlexGridSizer1->Add(StaticTextProgrammer, 0, wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
  ComboBoxProgrammer = new wxComboBox(this, ID_COMBOBOX_PROGRAMMER, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_COMBOBOX_PROGRAMMER"));
  ComboBoxProgrammer->Disable();
  FlexGridSizer1->Add(ComboBoxProgrammer, 1, wxALL|wxEXPAND|wxALIGN_LEFT|wxALIGN_TOP, 5);
  StaticTextInterface = new wxStaticText(this, wxID_ANY, _("Interface:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
  FlexGridSizer1->Add(StaticTextInterface, 0, wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
  ComboBoxInterface = new wxComboBox(this, ID_COMBOBOX_INTERFACE, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_COMBOBOX_INTERFACE"));
  ComboBoxInterface->Disable();
  FlexGridSizer1->Add(ComboBoxInterface, 1, wxALL|wxEXPAND|wxALIGN_LEFT|wxALIGN_TOP, 5);
  StaticTextBaudrate = new wxStaticText(this, wxID_ANY, _("Baudrate:"), wxDefaultPosition, wxDefaultSize, 0, _T("wxID_ANY"));
  FlexGridSizer1->Add(StaticTextBaudrate, 0, wxALL|wxALIGN_RIGHT|wxALIGN_CENTER_VERTICAL, 5);
  ComboBoxBaudrate = new wxComboBox(this, ID_COMBOBOX_BAUDRATE, wxEmptyString, wxDefaultPosition, wxDefaultSize, 0, 0, 0, wxDefaultValidator, _T("ID_COMBOBOX_BAUDRATE"));
  ComboBoxBaudrate->Append(_("default"));
  ComboBoxBaudrate->Append(_("110"));
  ComboBoxBaudrate->Append(_("300"));
  ComboBoxBaudrate->Append(_("600"));
  ComboBoxBaudrate->Append(_("1200"));
  ComboBoxBaudrate->Append(_("2400"));
  ComboBoxBaudrate->Append(_("4800"));
  ComboBoxBaudrate->Append(_("9600"));
  ComboBoxBaudrate->Append(_("14400"));
  ComboBoxBaudrate->Append(_("19200"));
  ComboBoxBaudrate->Append(_("28800"));
  ComboBoxBaudrate->Append(_("38400"));
  ComboBoxBaudrate->Append(_("56000"));
  ComboBoxBaudrate->Append(_("57600"));
  ComboBoxBaudrate->Append(_("115200"));
  FlexGridSizer1->Add(ComboBoxBaudrate, 1, wxALL|wxEXPAND|wxALIGN_LEFT|wxALIGN_TOP, 5);
  BoxSizer1->Add(FlexGridSizer1, 1, wxEXPAND|wxFIXED_MINSIZE|wxALIGN_LEFT|wxALIGN_TOP, 0);
  StdDialogButtonSizer1 = new wxStdDialogButtonSizer();
  StdDialogButtonSizer1->AddButton(new wxButton(this, wxID_OK, wxEmptyString));
  StdDialogButtonSizer1->AddButton(new wxButton(this, wxID_NO, _("Reset")));
  StdDialogButtonSizer1->AddButton(new wxButton(this, wxID_CANCEL, wxEmptyString));
  StdDialogButtonSizer1->Realize();
  BoxSizer1->Add(StdDialogButtonSizer1, 0, wxALL|wxEXPAND|wxFIXED_MINSIZE|wxALIGN_LEFT|wxALIGN_TOP, 5);
  SetSizer(BoxSizer1);
  BoxSizer1->Fit(this);
  BoxSizer1->SetSizeHints(this);
  Center();

  Connect(ID_COMBOBOX_PLATFORM,wxEVT_COMMAND_COMBOBOX_SELECTED,(wxObjectEventFunction)&PlatformDialog::OnComboBoxPlatformSelected);
  Connect(ID_COMBOBOX_PROGRAMMER,wxEVT_COMMAND_COMBOBOX_SELECTED,(wxObjectEventFunction)&PlatformDialog::OnComboBoxProgrammerSelected);
  //*)

  wxButton * btnReset = StdDialogButtonSizer1->GetNegativeButton();
  btnReset->Bind(wxEVT_BUTTON, &PlatformDialog::OnReset, this);
}

PlatformDialog::~PlatformDialog() {
  //(*Destroy(PlatformDialog)
  //*)
}

void PlatformDialog::applyXhex(xhex * xhexfile) {
  if (xhexfile==0) {
    m_isXhex=false;
    return;
  }
  m_isXhex=true;

  if (xhexfile->platformPtr) {
    m_xhex_platform = xhexfile->platformPtr->content.c_str();
  }
  if (xhexfile->programmerPtr) {
    m_xhex_programmer=xhexfile->programmerPtr->type.c_str();
    m_xhex_baudrate=xhexfile->programmerPtr->baudrate.c_str();
    m_xhex_bitclock=xhexfile->programmerPtr->bitclock.c_str();
    if ((m_xhex_programmer=="usbasp")||((m_xhex_programmer=="usbasp-clone"))) {
      m_xhex_interface="usb";
      m_xhex_baudrate="none";
    } else {
      m_xhex_interface="COM1";
    }
    if (m_xhex_baudrate=="") m_xhex_baudrate="default";
    if (m_xhex_bitclock=="") m_xhex_bitclock="default";
  }
  if (xhexfile->devicePtr) {
    wxString devicePart = xhexfile->devicePtr->part.c_str();
    devicePart = devicePart.Upper();
    devicePart.Replace("ATMEGA", "ATmega");
    devicePart.Replace("ATXMEGA", "ATxmega");
    devicePart.Replace("ATTINY", "ATtiny");
    if (devicePart!="_") {
      m_xhex_mcu = devicePart;
    }
  }
  loadPlatform();
}

void PlatformDialog::initPlatform() {
  wxConfig *config = new wxConfig("BobDude", wxEmptyString, wxEmptyString, wxEmptyString, wxCONFIG_USE_LOCAL_FILE);
  wxString strValue = "";
  config->Read("defaultPlatform", &m_xhex_platform);
  delete config;
  loadPlatform();
}


void PlatformDialog::loadPlatform() {
  m_platform = m_xhex_platform;
  m_mcu = m_xhex_mcu;
  m_bitclock = m_xhex_bitclock;
  m_programmer = m_xhex_programmer;
  m_interface = m_xhex_interface;
  m_baudrate = m_xhex_baudrate;

  wxString platform = m_platform;
  if (platform!="") {
    wxConfig *config = new wxConfig("BobDude", wxEmptyString, wxEmptyString, wxEmptyString, wxCONFIG_USE_LOCAL_FILE);

    if (config->HasGroup("Platforms/"+platform)) {
      config->SetPath("Platforms/"+platform);
      if ((platform!="NIBObee") && (platform!="NIBObee Tuning") && (platform!="NIBO burger Tuning")&& (platform!="NIBO burger Tuning")) {
        config->Read("programmer", &m_programmer);
        config->Read("port", &m_interface);
        config->Read("part", &m_mcu);
        config->Read("bitclock", &m_bitclock);
        config->Read("baudrate", &m_baudrate);
      }
    }
    delete config;
  }

  bool isUSB = (m_programmer=="usbasp")||(m_programmer=="usbasp-clone");
  if (isUSB) {
    m_interface = "usb";
    m_baudrate = "none";
  }
  initGui();
}

void PlatformDialog::savePlatform() {
  wxConfig *config = new wxConfig("BobDude", wxEmptyString, wxEmptyString, wxEmptyString, wxCONFIG_USE_LOCAL_FILE);
  config->Write("defaultPlatform", m_platform);

  if (m_platform!="") {
    config->SetPath("Platforms/"+m_platform);
    config->Write("programmer", m_programmer);
    config->Write("port", m_interface);
    config->Write("part", m_mcu);
    config->Write("bitclock", m_bitclock);
    config->Write("baudrate", m_baudrate);
  }
  delete config;
}

static wxArrayString loc_StringArrayAnd(wxArrayString a0, wxArrayString a1) {
  wxArrayString res;
  a0.Sort();
  a1.Sort();
  unsigned int n0=a0.GetCount();
  unsigned int n1=a1.GetCount();
  unsigned int i0=0;
  unsigned int i1=0;

  while ((i0<n0) && (i1<n1)) {
    int comp = a0[i0].Cmp(a1[i1]);
    if (comp<0) {
      i0++;
    } else if (comp>0) {
      i1++;
    } else {
      res.Add(a0[i0]);
      i0++;
      i1++;
    }
  }
  return res;
}

static
wxArrayString getAvailableComPorts() {
  wxArrayString result;
#if defined(__WINDOWS__)
  wxRegKey *pRegKey = new wxRegKey("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM");
  if (pRegKey->Exists()) {
    if (pRegKey->Open(wxRegKey::Read)) {
      ;
      long int index = 0;
      wxString value_name = "                                                         ";
      if (pRegKey->GetFirstValue(value_name, index)) {
        ;
        do {
          wxString port = "                                                         ";
          pRegKey->QueryValue(value_name, port);
          port = port.Trim(); // workaround wx registry bug
          result.Add(port);
        } while (pRegKey->GetNextValue(value_name, index));
      }
      pRegKey->Close();
    }
  }
  delete pRegKey;
#endif
  return result;
}

static
wxArrayString getExistingUSBComPorts(const wxString & vid, const wxString & pid) {
  wxArrayString result;
#if defined(__WINDOWS__)
  wxRegKey *pRegKey = new wxRegKey("HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Enum\\USB\\VID_"+vid+"&PID_"+pid);
  if (pRegKey->Exists()) {
    if (pRegKey->Open(wxRegKey::Read)) {
      long int index = 0;
      wxString value_name = "                                                         ";
      if (pRegKey->GetFirstKey(value_name, index)) {
        ;
        do {
          wxRegKey *pSubKey = new wxRegKey(*pRegKey, value_name+"\\Device Parameters");
          if (pSubKey->Exists()) {
            wxString port = "                                                         ";
            pSubKey->QueryValue("PortName", port);
            port = port.Trim(); // workaround wx registry bug
            result.Add(port);
          }
        } while (pRegKey->GetNextKey(value_name, index));
      }
      pRegKey->Close();
    }
  }
  delete pRegKey;
#endif
  return result;
}

static
wxArrayString listDevices(const wxString & pattern) {
  wxArrayString result;
  const wxString devdir = _T("/dev");
  const int flags = wxDIR_FILES;
  if (wxDirExists(devdir)) {
    wxDir::GetAllFiles(devdir, &result, pattern, flags);
  }
  return result;
}


wxArrayString PlatformDialog::getUSBComPorts(const wxString & vid, const wxString & pid) {
#if defined(__WINDOWS__)
  return loc_StringArrayAnd(getExistingUSBComPorts(vid, pid), getAvailableComPorts());
#elif defined(__LINUX__)
  return listDevices("ttyACM*");
#else
  return listDevices("tty.usb*");
#endif
}



void PlatformDialog::initGui() {

  wxArrayString comports = getUSBComPorts("16C0","0933");

  ComboBoxInterface->Clear();
  for (unsigned int i=0; i<comports.GetCount(); i++) {
    ComboBoxInterface->Append(comports[i]);
  }

  // copy member values to GUI elements...
  ComboBoxPlatform->SetValue(m_platform);
  ComboBoxMCU->SetValue(m_mcu);
  ComboBoxBitclock->SetValue(m_bitclock);
  ComboBoxProgrammer->SetValue(m_programmer);
  ComboBoxInterface->SetValue(m_interface);
  ComboBoxBaudrate->SetValue(m_baudrate);

  ComboBoxPlatform->Enable(!m_isXhex);
  ComboBoxMCU->Enable(true);
  ComboBoxBitclock->Enable(true);
  ComboBoxProgrammer->Enable(true);

  wxCommandEvent dummyEvent(0);
  OnComboBoxProgrammerSelected(dummyEvent);
}

int PlatformDialog::ShowModal () {
  initGui();
  return wxDialog::ShowModal();
}

void PlatformDialog::OnReset(wxCommandEvent& event) {
  ComboBoxPlatform->SetValue(m_xhex_platform);
  ComboBoxMCU->SetValue(m_xhex_mcu);
  ComboBoxBitclock->SetValue(m_xhex_bitclock);
  ComboBoxProgrammer->SetValue(m_xhex_programmer);
  ComboBoxInterface->SetValue(m_xhex_interface);
  ComboBoxBaudrate->SetValue(m_xhex_baudrate);

  OnComboBoxPlatformSelected(event);
  OnComboBoxProgrammerSelected(event);
}


wxString PlatformDialog::getHTML() {
  wxString myinterface=m_interface;
  wxString bitclock=m_bitclock;
  wxString baudrate=m_baudrate;
  if (bitclock=="default") bitclock="<em>default</em>";
  if (myinterface=="usb") myinterface="<em>usb</em>";
  if (baudrate=="default") baudrate="<em>default</em>";
  if (baudrate=="none") baudrate="<em>none</em>";

  wxString res = "<font size=\"2\"><table cellspacing=\"0\" cellpadding=\"0\">";
  res += "<tr><td width=\"80\">Platform:</td><td><b>"+m_platform+"</b></td></tr>";
  res += "<tr><td>MCU:</td><td>"+m_mcu+"</td></tr>";
  res += "<tr><td>Bitclock:</td><td>"+bitclock+"</td></tr>";
  res += "<tr><td>Programmer:</td><td>"+m_programmer+"</td></tr>";
  res += "<tr><td>Interface:</td><td>"+myinterface+"</td></tr>";
  res += "<tr><td>Baudrate:</td><td>"+baudrate+"</td></tr>";
  res += "</table></font>";
  return res;
}

void PlatformDialog::EndModal (int retCode) {
  if (retCode== wxID_OK) {
    m_platform =ComboBoxPlatform->GetValue();
    m_mcu =ComboBoxMCU->GetValue();
    m_bitclock=ComboBoxBitclock->GetValue();
    m_programmer=ComboBoxProgrammer->GetValue();
    m_interface=ComboBoxInterface->GetValue();
    m_baudrate=ComboBoxBaudrate->GetValue();
    savePlatform();
  }
  wxDialog::EndModal(retCode);
}


void PlatformDialog::setProgrammers(const std::vector<ProgrammerInfo> & pgi) {
  std::vector<wxString> progNames;
  for (unsigned int i=0; i<pgi.size(); ++i) {
    progNames.push_back(pgi[i].name);
  }
  std::sort(progNames.begin(), progNames.end());
  for (unsigned int i=0; i<progNames.size(); ++i) {
    ComboBoxProgrammer->Append(progNames[i]);
  }
}


void PlatformDialog::setParts(const std::vector<PartInfo> & pai) {
  std::vector<wxString> partNames;
  for (unsigned int i=0; i<pai.size(); ++i) {
    wxString name = pai[i].desc;
    name = name.Upper();
    name.Replace("ATMEGA", "ATmega");
    name.Replace("ATXMEGA", "ATxmega");
    name.Replace("ATTINY", "ATtiny");
    partNames.push_back(name);
  }
  std::sort(partNames.begin(), partNames.end());
  for (unsigned int i=0; i<partNames.size(); ++i) {
    wxString name = partNames[i];
    ComboBoxMCU->Append(name);
  }
}

void PlatformDialog::OnComboBoxPlatformSelected(wxCommandEvent& event) {

}

void PlatformDialog::updateSerialPort(bool change) {
  wxArrayString comports = getUSBComPorts("16C0","0933");
  bool valid=false;
  ComboBoxInterface->Clear();
  for (unsigned int i=0; i<comports.GetCount(); i++) {
    ComboBoxInterface->Append(comports[i]);
    if (m_interface==comports[i]) {
      valid=true;
    }
  }
  if (change && !valid && !comports.IsEmpty()) {
    ComboBoxInterface->SetValue(comports[0]);
    m_interface=comports[0];
  }
}


void PlatformDialog::OnComboBoxProgrammerSelected(wxCommandEvent& event) {
  wxString programmer = ComboBoxProgrammer->GetValue();
  bool hasSerial = (programmer!="usbasp")&&(programmer!="usbasp-clone");
  ComboBoxInterface->Enable(hasSerial);
  ComboBoxBaudrate->Enable(hasSerial);
  if (hasSerial) {
    wxString myinterface = ComboBoxInterface->GetValue();
    if ((myinterface=="usb")||(myinterface=="")) {
      myinterface = m_interface;
    }
    if ((myinterface=="usb")||(myinterface=="")) {
      myinterface = "COM1";
    }
    ComboBoxInterface->SetValue(myinterface);

    wxString baudrate = ComboBoxBaudrate->GetValue();
    if (baudrate=="none") {
      baudrate = m_baudrate;
    }
    if (baudrate=="none") {
      baudrate = "default";
    }
    ComboBoxBaudrate->SetValue(baudrate);
  } else {
    ComboBoxInterface->SetValue("usb");
    ComboBoxBaudrate->SetValue("none");
  }
}
