#include <QDebug>
#include <QHBoxLayout>
#include <QLabel>
#include <QPixmap>
#include <QPushButton>
#include <QLineEdit>
#include <QRandomGenerator>
#include <QtConcurrent>

#include "common/params.h"
#include "networking.hpp"
#include "util.h"

void clearLayout(QLayout* layout) {
  while (QLayoutItem* item = layout->takeAt(0)) {
    if (QWidget* widget = item->widget()) {
      widget->deleteLater();
    }
    if (QLayout* childLayout = item->layout()) {
      clearLayout(childLayout);
    }
    delete item;
  }
}

QWidget* layoutToWidget(QLayout* l, QWidget* parent){
  QWidget* q = new QWidget(parent);
  q->setLayout(l);
  return q;
}

// https://stackoverflow.com/questions/478898/how-do-i-execute-a-command-and-get-the-output-of-the-command-within-c-using-po
std::string exec(const char* cmd) {
  std::array<char, 128> buffer;
  std::string result;
  std::unique_ptr<FILE, decltype(&pclose)> pipe(popen(cmd, "r"), pclose);
  if (!pipe) {
    throw std::runtime_error("popen() failed!");
  }
  while (fgets(buffer.data(), buffer.size(), pipe.get()) != nullptr) {
    result += buffer.data();
  }
  return result;
}

// Networking functions

Networking::Networking(QWidget* parent, bool show_advanced) : QWidget(parent), show_advanced(show_advanced){
  s = new QStackedLayout;

  QLabel* warning = new QLabel("Network manager is inactive!");
  warning->setStyleSheet(R"(font-size: 65px;)");

  s->addWidget(warning);
  setLayout(s);

  QTimer* timer = new QTimer(this);
  QObject::connect(timer, SIGNAL(timeout()), this, SLOT(refresh()));
  timer->start(5000);
  attemptInitialization();
}

void Networking::attemptInitialization(){
  // Checks if network manager is active
  try {
    wifi = new WifiManager(this);
  } catch (std::exception &e) {
    return;
  }

  connect(wifi, SIGNAL(wrongPassword(QString)), this, SLOT(wrongPassword(QString)));

  QVBoxLayout* vlayout = new QVBoxLayout;

  if (show_advanced) {
    QPushButton* advancedSettings = new QPushButton("Advanced");
    advancedSettings->setStyleSheet(R"(margin-right: 30px)");
    advancedSettings->setFixedSize(350, 100);
    connect(advancedSettings, &QPushButton::released, [=](){s->setCurrentWidget(an);});
    vlayout->addSpacing(10);
    vlayout->addWidget(advancedSettings, 0, Qt::AlignRight);
    vlayout->addSpacing(10);
  }

  wifiWidget = new WifiUI(0, 5, wifi);
  connect(wifiWidget, SIGNAL(connectToNetwork(Network)), this, SLOT(connectToNetwork(Network)));
  vlayout->addWidget(wifiWidget, 1);

  wifiScreen = layoutToWidget(vlayout, this);
  s->addWidget(wifiScreen);

  an = new AdvancedNetworking(this, wifi);
  connect(an, &AdvancedNetworking::backPress, [=](){s->setCurrentWidget(wifiScreen);});
  s->addWidget(an);

  setStyleSheet(R"(
    QPushButton {
      font-size: 50px;
      margin: 0px;
      padding: 15px;
      border-width: 0;
      border-radius: 30px;
      color: #dddddd;
      background-color: #444444;
    }
    QPushButton:disabled {
      color: #777777;
      background-color: #222222;
    }
  )");
  s->setCurrentWidget(wifiScreen);
  ui_setup_complete = true;
}

void Networking::refresh(){
  if (!this->isVisible()) {
    return;
  }
  if (!ui_setup_complete) {
    attemptInitialization();
    if (!ui_setup_complete) {
      return;
    }
  }
  wifiWidget->refresh();
  an->refresh();
}

void Networking::connectToNetwork(Network n) {
  if (n.security_type == SecurityType::OPEN) {
    wifi->connect(n);
  } else if (n.security_type == SecurityType::WPA) {
    QString pass = InputDialog::getText("Enter password for \"" + n.ssid + "\"");
    wifi->connect(n, pass);
  }
}

void Networking::wrongPassword(QString ssid) {
  return; // TODO: add this back
  /*
  for (Network n : wifi->seen_networks) {
    if (n.ssid == ssid) {
      inputField->setPromptText("Wrong password for \"" + n.ssid +"\"");
      s->setCurrentIndex(0);
      emit openKeyboard();
      return;
    }
  }
  */
}

QFrame* hline(QWidget* parent = 0){
  QFrame* line = new QFrame(parent);
  line->setFrameShape(QFrame::StyledPanel);
  line->setStyleSheet("margin-left: 40px; margin-right: 40px; border-width: 1px; border-bottom-style: solid; border-color: gray;");
  line->setFixedHeight(2);
  return line;
}

// AdvancedNetworking functions

AdvancedNetworking::AdvancedNetworking(QWidget* parent, WifiManager* wifi): QWidget(parent), wifi(wifi){
  s = new QStackedLayout; // mainPage, SSH settings

  QVBoxLayout* vlayout = new QVBoxLayout;

  // Back button
  QHBoxLayout* backLayout = new QHBoxLayout;
  QPushButton* back = new QPushButton("Back");
  back->setFixedSize(500, 100);
  connect(back, &QPushButton::released, [=](){emit backPress();});
  backLayout->addWidget(back, 0, Qt::AlignLeft);
  vlayout->addWidget(layoutToWidget(backLayout, this), 0, Qt::AlignLeft);

  // Enable tethering layout
  QHBoxLayout* tetheringToggleLayout = new QHBoxLayout;
  tetheringToggleLayout->addWidget(new QLabel("Enable tethering"));
  Toggle* toggle_switch = new Toggle;
  toggle_switch->setFixedSize(150, 100);
  tetheringToggleLayout->addWidget(toggle_switch);
  tetheringToggleLayout->addSpacing(40);
  if (wifi->tetheringEnabled()) {
    toggle_switch->togglePosition();
  }
  QObject::connect(toggle_switch, SIGNAL(stateChanged(int)), this, SLOT(toggleTethering(int)));
  vlayout->addWidget(layoutToWidget(tetheringToggleLayout, this), 0);
  vlayout->addWidget(hline(), 0);

  // Change tethering password
  QHBoxLayout *tetheringPassword = new QHBoxLayout;
  tetheringPassword->addWidget(new QLabel("Edit tethering password"), 1);
  editPasswordButton = new QPushButton("EDIT");
  editPasswordButton->setFixedWidth(500);
  connect(editPasswordButton, &QPushButton::released, [=](){
    QString pass = InputDialog::getText("Enter new tethering password");
    if (pass.size()) {
      wifi->changeTetheringPassword(pass);
    }
  });
  tetheringPassword->addWidget(editPasswordButton, 1, Qt::AlignRight);
  vlayout->addWidget(layoutToWidget(tetheringPassword, this), 0);
  vlayout->addWidget(hline(), 0);

  // IP adress
  QHBoxLayout* IPlayout = new QHBoxLayout;
  IPlayout->addWidget(new QLabel("IP address"), 0);
  ipLabel = new QLabel(wifi->ipv4_address);
  ipLabel->setStyleSheet("color: #aaaaaa");
  IPlayout->addWidget(ipLabel, 0, Qt::AlignRight);
  vlayout->addWidget(layoutToWidget(IPlayout, this), 0);
  vlayout->addWidget(hline(), 0);

  // Enable SSH
  QHBoxLayout* enableSSHLayout = new QHBoxLayout(this);
  enableSSHLayout->addWidget(new QLabel("Enable SSH", this));
  toggle_switch_SSH = new Toggle(this);
  toggle_switch_SSH->setFixedSize(150, 100);
  if (isSSHEnabled()) {
    toggle_switch_SSH->togglePosition();
  }
  QObject::connect(toggle_switch_SSH, SIGNAL(stateChanged(int)), this, SLOT(toggleSSH(int)));
  enableSSHLayout->addWidget(toggle_switch_SSH);
  vlayout->addWidget(layoutToWidget(enableSSHLayout, this));
  vlayout->addWidget(hline(), 0);

  // SSH keys
  QHBoxLayout* authSSHLayout = new QHBoxLayout(this);
  authSSHLayout->addWidget(new QLabel("SSH keys", this));
  QPushButton* editAuthSSHButton = new QPushButton("EDIT", this);
  editAuthSSHButton->setFixedWidth(500);
  connect(editAuthSSHButton, &QPushButton::released, [=](){s->setCurrentWidget(ssh);});
  authSSHLayout->addWidget(editAuthSSHButton);
  vlayout->addWidget(layoutToWidget(authSSHLayout, this));
  vlayout->addSpacing(50);

  // //Disconnect or delete connections
  // QHBoxLayout* dangerZone = new QHBoxLayout(this);
  // QPushButton* disconnect = new QPushButton("Disconnect from WiFi", this);
  // dangerZone->addWidget(disconnect);
  // QPushButton* deleteAll = new QPushButton("DELETE ALL NETWORKS", this);
  // dangerZone->addWidget(deleteAll);
  // vlayout->addWidget(layoutToWidget(dangerZone, this));

  // vlayout to widget
  QWidget* settingsWidget = layoutToWidget(vlayout, this);
  settingsWidget->setStyleSheet("margin-left: 40px; margin-right: 40px;");
  s->addWidget(settingsWidget);

  ssh = new SSH;
  connect(ssh, &SSH::closeSSHSettings, [=](){s->setCurrentWidget(settingsWidget);});
  s->addWidget(ssh);

  setLayout(s);
}

bool AdvancedNetworking::isSSHEnabled(){
  QString response = QString::fromStdString(exec("systemctl is-active ssh"));
  return response.startsWith("active");
}

void AdvancedNetworking::refresh(){
  ipLabel->setText(wifi->ipv4_address);
  // Don't refresh while changing SSH state
  if(!toggle_switch_SSH->getEnabled()){
    return;
  }
  if (toggle_switch_SSH->on != isSSHEnabled()) {
    toggle_switch_SSH->togglePosition();
  }
  // Qt can be lazy
  repaint();
}

void AdvancedNetworking::toggleTethering(int enable) {
  if (enable) {
    wifi->enableTethering();
  } else {
    wifi->disableTethering();
  }
  editPasswordButton->setEnabled(!enable);
}

void enableSSH(Toggle* toggle_switch_SSH){
  Params().write_db_value("SshEnabled", "1");
  toggle_switch_SSH->setEnabled(true);
}

void disableSSH(Toggle* toggle_switch_SSH){
  Params().write_db_value("SshEnabled", "0");
  toggle_switch_SSH->setEnabled(true);
}

void AdvancedNetworking::toggleSSH(int enable) {
  toggle_switch_SSH->setEnabled(false);
  if (enable) {
    QtConcurrent::run(enableSSH, toggle_switch_SSH);
  } else {
    QtConcurrent::run(disableSSH, toggle_switch_SSH);
  }
}


// WifiUI functions

WifiUI::WifiUI(QWidget *parent, int page_length, WifiManager* wifi) : QWidget(parent), networks_per_page(page_length), wifi(wifi) {
  vlayout = new QVBoxLayout;

  // Scan on startup
  QLabel *scanning = new QLabel("Scanning for networks");
  scanning->setStyleSheet(R"(font-size: 65px;)");
  vlayout->addWidget(scanning, 0, Qt::AlignCenter);
  vlayout->setSpacing(25);

  setLayout(vlayout);
  page = 0;
}

void WifiUI::refresh() {
  wifi->request_scan();
  wifi->refreshNetworks();
  clearLayout(vlayout);

  connectButtons = new QButtonGroup(this); // TODO check if this is a leak
  QObject::connect(connectButtons, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(handleButton(QAbstractButton*)));

  int i = 0;
  int countWidgets = 0;
  for (Network &network : wifi->seen_networks) {
    QHBoxLayout *hlayout = new QHBoxLayout;
    if (page * networks_per_page <= i && i < (page + 1) * networks_per_page) {
      // SSID
      hlayout->addSpacing(50);
      QString ssid = QString::fromUtf8(network.ssid);
      if(ssid.length() > 20){
        ssid = ssid.left(20 - 3) + "…";
      }
      
      QLabel *ssid_label = new QLabel(ssid);
      ssid_label->setStyleSheet(R"(
        font-size: 55px;
      )");
      ssid_label->setFixedWidth(this->width()*0.5);
      hlayout->addWidget(ssid_label, 0, Qt::AlignLeft);

      // TODO: don't use images for this
      // strength indicator
      unsigned int strength_scale = network.strength / 17;
      QPixmap pix("../assets/images/network_" + QString::number(strength_scale) + ".png");
      QLabel *icon = new QLabel();
      icon->setPixmap(pix.scaledToWidth(100, Qt::SmoothTransformation));
      icon->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Fixed));
      hlayout->addWidget(icon, 0, Qt::AlignRight);

      // connect button
      QPushButton* btn = new QPushButton(network.security_type == SecurityType::UNSUPPORTED ? "Unsupported" : (network.connected == ConnectedType::CONNECTED ? "Connected" : (network.connected == ConnectedType::CONNECTING ? "Connecting" : "Connect")));
      btn->setDisabled(network.connected == ConnectedType::CONNECTED || network.connected == ConnectedType::CONNECTING || network.security_type == SecurityType::UNSUPPORTED);
      btn->setFixedWidth(350);
      hlayout->addWidget(btn, 0, Qt::AlignRight);

      connectButtons->addButton(btn, i);

      vlayout->addLayout(hlayout, 1);
      // Don't add the last horizontal line
      if (page * networks_per_page <= i+1 && i+1 < (page + 1) * networks_per_page && i+1 < wifi->seen_networks.size()) {
        vlayout->addWidget(hline(), 0);
      }
      countWidgets++;
    }
    i++;
  }
  vlayout->addStretch(3);


  // Setup buttons for pagination
  QHBoxLayout *prev_next_buttons = new QHBoxLayout;

  QPushButton* prev = new QPushButton("Previous");
  prev->setEnabled(page);
  QObject::connect(prev, SIGNAL(released()), this, SLOT(prevPage()));
  prev_next_buttons->addWidget(prev);

  QPushButton* next = new QPushButton("Next");
  next->setEnabled(wifi->seen_networks.size() > (page + 1) * networks_per_page);
  QObject::connect(next, SIGNAL(released()), this, SLOT(nextPage()));
  prev_next_buttons->addWidget(next);

  vlayout->addLayout(prev_next_buttons, 2);
}

void WifiUI::handleButton(QAbstractButton* button) {
  QPushButton* btn = static_cast<QPushButton*>(button);
  Network n = wifi->seen_networks[connectButtons->id(btn)];
  emit connectToNetwork(n);
}

void WifiUI::prevPage() {
  page--;
  refresh();
}

void WifiUI::nextPage() {
  page++;
  refresh();
}
