// Copyright 2010 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.

#include <SFML/Network/Http.hpp>
#include <sstream>
#include <string>
#include <vector>
#include <wx/button.h>
#include <wx/checklst.h>
#include <wx/listbox.h>
#include <wx/msgdlg.h>
#include <wx/panel.h>
#include <wx/sizer.h>
#include <wx/stattext.h>
#include <wx/textctrl.h>

#include "Common/CommonTypes.h"
#include "Common/StringUtil.h"
#include "Core/Core.h"
#include "Core/GeckoCode.h"
#include "Core/GeckoCodeConfig.h"
#include "DolphinWX/Cheats/GeckoCodeDiag.h"
#include "DolphinWX/WxUtils.h"

wxDEFINE_EVENT(DOLPHIN_EVT_GECKOCODE_TOGGLED, wxCommandEvent);

namespace Gecko
{
static const char str_name[] = wxTRANSLATE("Name: ");
static const char str_notes[] = wxTRANSLATE("Notes: ");
static const char str_creator[] = wxTRANSLATE("Creator: ");

CodeConfigPanel::CodeConfigPanel(wxWindow* const parent) : wxPanel(parent)
{
  m_listbox_gcodes = new wxCheckListBox(this, wxID_ANY);
  m_listbox_gcodes->Bind(wxEVT_LISTBOX, &CodeConfigPanel::UpdateInfoBox, this);
  m_listbox_gcodes->Bind(wxEVT_CHECKLISTBOX, &CodeConfigPanel::ToggleCode, this);

  m_infobox.label_name = new wxStaticText(this, wxID_ANY, wxGetTranslation(str_name));
  m_infobox.label_creator = new wxStaticText(this, wxID_ANY, wxGetTranslation(str_creator));
  m_infobox.label_notes = new wxStaticText(this, wxID_ANY, wxGetTranslation(str_notes));
  m_infobox.textctrl_notes = new wxTextCtrl(this, wxID_ANY, wxEmptyString, wxDefaultPosition,
                                            wxDefaultSize, wxTE_MULTILINE | wxTE_READONLY);
  m_infobox.listbox_codes =
      new wxListBox(this, wxID_ANY, wxDefaultPosition, wxDLG_UNIT(this, wxSize(-1, 48)));

  // TODO: buttons to add/edit codes

  // sizers
  const int space5 = FromDIP(5);
  wxBoxSizer* const sizer_infobox = new wxBoxSizer(wxVERTICAL);
  sizer_infobox->Add(m_infobox.label_name);
  sizer_infobox->Add(m_infobox.label_creator, 0, wxTOP, space5);
  sizer_infobox->Add(m_infobox.label_notes, 0, wxTOP, space5);
  sizer_infobox->Add(m_infobox.textctrl_notes, 0, wxEXPAND | wxTOP, space5);
  sizer_infobox->Add(m_infobox.listbox_codes, 1, wxEXPAND | wxTOP, space5);

  // button sizer
  wxBoxSizer* const sizer_buttons = new wxBoxSizer(wxHORIZONTAL);
  btn_download = new wxButton(this, wxID_ANY, _("Download Codes (WiiRD Database)"));
  btn_download->Disable();
  btn_download->Bind(wxEVT_BUTTON, &CodeConfigPanel::DownloadCodes, this);
  sizer_buttons->AddStretchSpacer(1);
  sizer_buttons->Add(WxUtils::GiveMinSizeDIP(btn_download, wxSize(128, -1)), 1, wxEXPAND);

  wxBoxSizer* const sizer_main = new wxBoxSizer(wxVERTICAL);
  sizer_main->AddSpacer(space5);
  sizer_main->Add(m_listbox_gcodes, 1, wxEXPAND | wxLEFT | wxRIGHT, space5);
  sizer_main->AddSpacer(space5);
  sizer_main->Add(sizer_infobox, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
  sizer_main->AddSpacer(space5);
  sizer_main->Add(sizer_buttons, 0, wxEXPAND | wxLEFT | wxRIGHT, space5);
  sizer_main->AddSpacer(space5);

  SetSizerAndFit(sizer_main);
}

void CodeConfigPanel::UpdateCodeList(bool checkRunning)
{
  // disable the button if it doesn't have an effect
  btn_download->Enable((!checkRunning || Core::IsRunning()) && !m_gameid.empty());

  m_listbox_gcodes->Clear();
  // add the codes to the listbox
  for (const GeckoCode& code : m_gcodes)
  {
    m_listbox_gcodes->Append(m_listbox_gcodes->EscapeMnemonics(StrToWxStr(code.name)));
    if (code.enabled)
    {
      m_listbox_gcodes->Check(m_listbox_gcodes->GetCount() - 1, true);
    }
  }

  wxCommandEvent evt;
  UpdateInfoBox(evt);
}

void CodeConfigPanel::LoadCodes(const IniFile& globalIni, const IniFile& localIni,
                                const std::string& gameid, bool checkRunning)
{
  m_gameid = gameid;

  m_gcodes.clear();
  if (!checkRunning || Core::IsRunning())
    Gecko::LoadCodes(globalIni, localIni, m_gcodes);

  UpdateCodeList(checkRunning);
}

void CodeConfigPanel::ToggleCode(wxCommandEvent& evt)
{
  const int sel = evt.GetInt();  // this right?
  if (sel > -1)
  {
    m_gcodes[sel].enabled = m_listbox_gcodes->IsChecked(sel);

    wxCommandEvent toggle_event(DOLPHIN_EVT_GECKOCODE_TOGGLED, GetId());
    toggle_event.SetClientData(&m_gcodes[sel]);
    GetEventHandler()->ProcessEvent(toggle_event);
  }
}

void CodeConfigPanel::UpdateInfoBox(wxCommandEvent&)
{
  m_infobox.listbox_codes->Clear();
  const int sel = m_listbox_gcodes->GetSelection();

  if (sel > -1)
  {
    m_infobox.label_name->SetLabel(wxGetTranslation(str_name) + StrToWxStr(m_gcodes[sel].name));

    // notes textctrl
    m_infobox.textctrl_notes->Clear();
    for (const std::string& note : m_gcodes[sel].notes)
    {
      m_infobox.textctrl_notes->AppendText(StrToWxStr(note));
    }
    m_infobox.textctrl_notes->ScrollLines(-99);  // silly

    m_infobox.label_creator->SetLabel(wxGetTranslation(str_creator) +
                                      StrToWxStr(m_gcodes[sel].creator));

    // add codes to info listbox
    for (const GeckoCode::Code& code : m_gcodes[sel].codes)
    {
      m_infobox.listbox_codes->Append(wxString::Format("%08X %08X", code.address, code.data));
    }
  }
  else
  {
    m_infobox.label_name->SetLabel(wxGetTranslation(str_name));
    m_infobox.textctrl_notes->Clear();
    m_infobox.label_creator->SetLabel(wxGetTranslation(str_creator));
  }
}

void CodeConfigPanel::DownloadCodes(wxCommandEvent&)
{
  if (m_gameid.empty())
    return;

  std::string gameid = m_gameid;

  switch (m_gameid[0])
  {
  case 'R':
  case 'S':
  case 'G':
    break;
  default:
    // All channels (WiiWare, VirtualConsole, etc) are identified by their first four characters
    gameid = m_gameid.substr(0, 4);
    break;
  }

  sf::Http::Request req;
  req.setUri("/txt.php?txt=" + gameid);

  sf::Http http;
  http.setHost("geckocodes.org");

  const sf::Http::Response resp = http.sendRequest(req, sf::seconds(5));

  if (sf::Http::Response::Ok == resp.getStatus())
  {
    // temp vector containing parsed codes
    std::vector<GeckoCode> gcodes;

    // parse the codes
    std::istringstream ss(resp.getBody());

    std::string line;

    // seek past the header, get to the first code
    std::getline(ss, line);
    std::getline(ss, line);
    std::getline(ss, line);

    int read_state = 0;
    GeckoCode gcode;

    while ((std::getline(ss, line).good()))
    {
      // Remove \r at the end of the line for files using windows line endings, std::getline only
      // removes \n
      line = StripSpaces(line);

      if (line.empty())
      {
        // add the code
        if (gcode.codes.size())
          gcodes.push_back(gcode);
        gcode = GeckoCode();
        read_state = 0;
        continue;
      }

      switch (read_state)
      {
      // read new code
      case 0:
      {
        std::istringstream ssline(line);
        // stop at [ character (beginning of contributor name)
        std::getline(ssline, gcode.name, '[');
        gcode.name = StripSpaces(gcode.name);
        gcode.user_defined = true;
        // read the code creator name
        std::getline(ssline, gcode.creator, ']');
        read_state = 1;
      }
      break;

      // read code lines
      case 1:
      {
        std::istringstream ssline(line);
        std::string addr, data;
        ssline >> addr >> data;
        ssline.seekg(0);

        // check if this line a code, silly, but the dumb txt file comment lines can start with
        // valid hex chars :/
        if (8 == addr.length() && 8 == data.length())
        {
          GeckoCode::Code new_code;
          new_code.original_line = line;
          ssline >> std::hex >> new_code.address >> new_code.data;
          gcode.codes.push_back(new_code);
        }
        else
        {
          gcode.notes.push_back(line);
          read_state = 2;  // start reading comments
        }
      }
      break;

      // read comment lines
      case 2:
        // append comment line
        gcode.notes.push_back(line);
        break;
      }
    }

    // add the last code
    if (gcode.codes.size())
      gcodes.push_back(gcode);

    if (gcodes.size())
    {
      unsigned long added_count = 0;

      // append the codes to the code list
      for (const GeckoCode& code : gcodes)
      {
        // only add codes which do not already exist
        auto existing_gcodes_iter = m_gcodes.begin();
        auto existing_gcodes_end = m_gcodes.end();
        for (;; ++existing_gcodes_iter)
        {
          if (existing_gcodes_end == existing_gcodes_iter)
          {
            m_gcodes.push_back(code);
            ++added_count;
            break;
          }

          // code exists
          if (*existing_gcodes_iter == code)
            break;
        }
      }

      wxMessageBox(wxString::Format(_("Downloaded %lu codes. (added %lu)"),
                                    (unsigned long)gcodes.size(), added_count));

      // refresh the list
      UpdateCodeList();
    }
    else
    {
      wxMessageBox(_("File contained no codes."));
    }
  }
  else
  {
    WxUtils::ShowErrorDialog(_("Failed to download codes."));
  }
}
}