#ifdef _WIN32
/*
 * Headers on Windows can define `min` and `max` as macros which causes
 * problem when using `std::min` and `std::max`
 * -> Define `NOMINMAX` to prevent the definition of these macros
 */
#define NOMINMAX
#endif

#include <sstream>
#include <iostream>
#include <QTimer>
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
#include <QIcon>
#else
#include <QScreen>
#include <QGuiApplication>
#endif
#include "GRPlotMainWindow.hxx"
#include "GRPlotDockWidget.hxx"

const int LEFT_AREA_WIDTH = 300;
const int RIGHT_AREA_WIDTH = 250;

GRPlotMainWindow::GRPlotMainWindow(int argc, char **argv, int width, int height, bool listen_mode, int listen_port,
                                   bool test_mode, QString test_commands_file_path, bool help_mode)
    : QMainWindow(), grplot_widget_(nullptr)
{
  bool hide_colormap = false;
  // there is no colormap or each colormap gets created -> check for 1 colormap is enough
  if (QPixmap(":preview_images/colormaps/afmhot").isNull()) hide_colormap = true;

  if (help_mode)
    {
      auto *w = new QWidget(this);
      std::string kind;
      auto path = util::getEnvVar(W("GRDIR"), W(GRDIR)) + W("/lib");

      this->help_mode = help_mode;
      find_line_edit = new QLineEdit(w);
      find_line_edit->setPlaceholderText("Search in document");
      auto button = new QPushButton("Find", w);
      QObject::connect(button, SIGNAL(clicked()), this, SLOT(findButtonClickedSlot()));
      auto form = new QGridLayout;
      form->addWidget(find_line_edit, 0, 0);
      form->addWidget(button, 0, 1);

      message = new QTextBrowser(w);
#ifdef _WIN32
      message->setSearchPaths(QStringList(QString::fromStdWString(path)));
#else
      message->setSearchPaths(QStringList(QString::fromStdString(path)));
#endif
      message->setSource(QUrl("../share/doc/grplot/grplot.man.md"));
      message->setReadOnly(true);
      message->setOpenExternalLinks(true);
      message->setAlignment(Qt::AlignTop);
      if (argc >= 3)
        {
          kind = argv[2];
          std::transform(kind.begin(), kind.end(), kind.begin(), ::toupper);
          if (!message->find(QString(kind.c_str()), QTextDocument::FindCaseSensitively))
            fprintf(stderr, "No plot type with the name %s was found.\n", kind.c_str());
        }
      form->addWidget(message, 1, 0, 1, 2);
      w->setLayout(form);
      setCentralWidget(w);
      resize(width, height);
    }
  else
    {
      grplot_widget_ = new GRPlotWidget(this, argc, argv, listen_mode, listen_port, test_mode, test_commands_file_path);
      setCentralWidget(grplot_widget_);
      grplot_widget_->resize(width, height);
      grplot_widget_->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
    }

  setWindowTitle("GR Plot");
  if (!listen_mode)
    {
      if (grplot_widget_)
        {
          resizeGRPlotWidget(width, height);
        }
      else
        {
          resize(width, height);
        }
    }
  if (test_mode && grplot_widget_->getTestCommandsStream())
    {
      QTimer::singleShot(100, grplot_widget_, &GRPlotWidget::processTestCommandsFile);
    }

  if (grplot_widget_ && !test_mode && !grplot_widget_->getTestCommandsStream() && !help_mode)
    {
      menu = menuBar();
      file_menu = new QMenu("&File");
      export_menu = file_menu->addMenu("&Export");
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
      export_menu->menuAction()->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::CameraPhoto));
      export_menu->menuAction()->setIconVisibleInMenu(true);
#endif
      modi_menu = new QMenu("&Modi");

      export_menu->addAction(grplot_widget_->getPdfAct());
      export_menu->addAction(grplot_widget_->getPngAct());
      export_menu->addAction(grplot_widget_->getJpegAct());
      export_menu->addAction(grplot_widget_->getSvgAct());
      modi_menu->addAction(grplot_widget_->getMovableModeAct());
      if (hide_colormap) grplot_widget_->getColormapAct()->setVisible(false);

      menu->addMenu(file_menu);
      menu->addMenu(modi_menu);

      if (!getenv("GRDISPLAY") || (getenv("GRDISPLAY") && strcmp(getenv("GRDISPLAY"), "view") != 0))
        {
          editor_menu = new QMenu(tr("&Editor"));
          context_menu = new QMenu("&Data");
          add_context_data = new QMenu("&Add Data-Context");
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
          add_context_data->menuAction()->setIcon(QIcon::fromTheme(QIcon::ThemeIcon::ListAdd));
          add_context_data->menuAction()->setIconVisibleInMenu(true);
#endif

          editor_menu->addAction(grplot_widget_->getEditorAct());
          file_menu->addAction(grplot_widget_->getLoadFileAct());
          editor_menu->addAction(grplot_widget_->getShowContainerAct());
          editor_menu->addAction(grplot_widget_->getAdvancedEditorAct());
          editor_menu->addAction(grplot_widget_->getAddElementAct());
          editor_menu->addAction(grplot_widget_->getUndoAct());
          editor_menu->addAction(grplot_widget_->getRedoAct());
          context_menu->addAction(grplot_widget_->getShowContextAct());
          add_context_data->addAction(grplot_widget_->getAddContextAct());
          add_context_data->addAction(grplot_widget_->getAddGRplotDataContextAct());
          add_context_data->addAction(grplot_widget_->getGenerateLinearContextAct());
          modi_menu->addAction(grplot_widget_->getSelectableGridAct());
          modi_menu->addAction(grplot_widget_->getIconBarAct());

          menu->addMenu(editor_menu);
          menu->addMenu(context_menu);
          context_menu->addMenu(add_context_data);

          edit_element_dock_widget = new GRPlotDockWidget("Edit Element:", RIGHT_AREA_WIDTH, height, this);
          edit_element_dock_widget->setWidget(grplot_widget_->getEditElementWidget());
          edit_element_dock_widget->setAllowedAreas(Qt::RightDockWidgetArea);
          edit_element_dock_widget->hide();
          addDockWidget(Qt::RightDockWidgetArea, edit_element_dock_widget);
          QObject::connect(grplot_widget_->getHideEditElementAct(), SIGNAL(triggered()), this,
                           SLOT(hideEditElementDockSlot()));
          QObject::connect(grplot_widget_->getShowEditElementAct(), SIGNAL(triggered()), this,
                           SLOT(showEditElementDockSlot()));
          QObject::connect(edit_element_dock_widget, SIGNAL(resizeMainWindow()), this,
                           SLOT(closeEditElementDockSlot()));
          QObject::connect(grplot_widget_->getUpdateEditElementTitleAct(), SIGNAL(triggered()), this,
                           SLOT(updateEditElementDockTitleSlot()));

          add_element_dock_widget = new GRPlotDockWidget("Add Element:", RIGHT_AREA_WIDTH, height, this);
          add_element_dock_widget->setWidget(grplot_widget_->getAddElementWidget());
          add_element_dock_widget->setAllowedAreas(Qt::RightDockWidgetArea);
          add_element_dock_widget->hide();
          addDockWidget(Qt::RightDockWidgetArea, add_element_dock_widget);
          QObject::connect(grplot_widget_->getHideAddElementAct(), SIGNAL(triggered()), this,
                           SLOT(hideAddElementDockSlot()));
          QObject::connect(grplot_widget_->getShowAddElementAct(), SIGNAL(triggered()), this,
                           SLOT(showAddElementDockSlot()));
          QObject::connect(add_element_dock_widget, SIGNAL(resizeMainWindow()), this, SLOT(closeAddElementDockSlot()));

          tree_dock_widget = new GRPlotDockWidget("Element Tree:", LEFT_AREA_WIDTH, height, this);
          tree_dock_widget->setWidget(grplot_widget_->getTreeWidget());
          tree_dock_widget->setAllowedAreas(Qt::LeftDockWidgetArea);
          tree_dock_widget->hide();
          addDockWidget(Qt::LeftDockWidgetArea, tree_dock_widget);
          QObject::connect(grplot_widget_->getShowTreeWidgetAct(), SIGNAL(triggered()), this,
                           SLOT(showTreeWidgetDockSlot()));
          QObject::connect(grplot_widget_->getHideTreeWidgetAct(), SIGNAL(triggered()), this,
                           SLOT(hideTreeWidgetDockSlot()));
          QObject::connect(tree_dock_widget, SIGNAL(resizeMainWindow()), this, SLOT(closeTreeWidgetDockSlot()));

          table_dock_widget = new GRPlotDockWidget("Data-Context Viewer:", LEFT_AREA_WIDTH, height, this);
          table_dock_widget->setWidget(grplot_widget_->getTableWidget());
          table_dock_widget->setAllowedAreas(Qt::LeftDockWidgetArea);
          table_dock_widget->hide();
          addDockWidget(Qt::LeftDockWidgetArea, table_dock_widget);
          QObject::connect(grplot_widget_->getHideTableWidgetAct(), SIGNAL(triggered()), this,
                           SLOT(hideTableWidgetDockSlot()));
          QObject::connect(grplot_widget_->getShowTableWidgetAct(), SIGNAL(triggered()), this,
                           SLOT(showTableWidgetDockSlot()));
          QObject::connect(table_dock_widget, SIGNAL(resizeMainWindow()), this, SLOT(closeTableWidgetDockSlot()));

          text_preview_dock_widget = new GRPlotDockWidget("Text Preview:", RIGHT_AREA_WIDTH, 30, this);
          text_preview_dock_widget->setWidget(grplot_widget_->getTextPreviewWidget());
          text_preview_dock_widget->setAllowedAreas(Qt::RightDockWidgetArea);
          text_preview_dock_widget->hide();
          addDockWidget(Qt::RightDockWidgetArea, text_preview_dock_widget);
          QObject::connect(grplot_widget_->getHideTextPreviewAct(), SIGNAL(triggered()), this,
                           SLOT(hideTextPreviewDockSlot()));
          QObject::connect(grplot_widget_->getShowTextPreviewAct(), SIGNAL(triggered()), this,
                           SLOT(showTextPreviewDockSlot()));
          QObject::connect(text_preview_dock_widget, SIGNAL(resizeMainWindow()), this,
                           SLOT(closeTextPreviewDockSlot()));

          selection_list_dock_widget = new GRPlotDockWidget("Selection List:", LEFT_AREA_WIDTH, 100, this);
          selection_list_dock_widget->setWidget(grplot_widget_->getSelectionListWidget());
          selection_list_dock_widget->setAllowedAreas(Qt::LeftDockWidgetArea);
          selection_list_dock_widget->hide();
          addDockWidget(Qt::LeftDockWidgetArea, selection_list_dock_widget);
          QObject::connect(grplot_widget_->getShowSelectionListWidgetAct(), SIGNAL(triggered()), this,
                           SLOT(showSelectionListDockSlot()));
          QObject::connect(grplot_widget_->getHideSelectionListWidgetAct(), SIGNAL(triggered()), this,
                           SLOT(hideSelectionListDockSlot()));
          QObject::connect(selection_list_dock_widget, SIGNAL(resizeMainWindow()), this,
                           SLOT(closeSelectionListDockSlot()));

          icon_bar_dock_widget = new GRPlotDockWidget("", 300, 28, this);
          icon_bar_dock_widget->setWidget(grplot_widget_->getIconBarWidget());
          icon_bar_dock_widget->setTitleBarWidget(new QWidget());
          icon_bar_dock_widget->setAllowedAreas(Qt::TopDockWidgetArea);
          icon_bar_dock_widget->setFixedHeight(28);
          icon_bar_dock_widget->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
          icon_bar_dock_widget->setMinimumWidth(0);
          icon_bar_dock_widget->setFeatures(QDockWidget::DockWidgetFloatable);
          addDockWidget(Qt::TopDockWidgetArea, icon_bar_dock_widget);
          QObject::connect(grplot_widget_->getHideIconBarAct(), SIGNAL(triggered()), this, SLOT(hideIconBarDockSlot()));
          QObject::connect(grplot_widget_->getShowIconBarAct(), SIGNAL(triggered()), this, SLOT(showIconBarDockSlot()));
          QObject::connect(icon_bar_dock_widget, SIGNAL(resizeMainWindow()), this, SLOT(closeIconBarDockSlot()));

          this->tabifyDockWidget(tree_dock_widget, table_dock_widget);
          this->tabifyDockWidget(edit_element_dock_widget, add_element_dock_widget);

          // this way the existing tabs gets shown on the top side of the DockWidgetArea instead of the bottom side
          this->setTabPosition(Qt::RightDockWidgetArea, QTabWidget::North);
          this->setTabPosition(Qt::BottomDockWidgetArea, QTabWidget::North);
          this->setTabPosition(Qt::LeftDockWidgetArea, QTabWidget::North);
          this->setTabPosition(Qt::TopDockWidgetArea, QTabWidget::North);

          // the top corners shouldn't be part of the top-area so that the icon-menu stays where it is regardless if the
          // left- and right-area are shown
          this->setCorner(Qt::TopRightCorner, Qt::RightDockWidgetArea);
          this->setCorner(Qt::TopLeftCorner, Qt::LeftDockWidgetArea);

          resizeGRPlotWidget(width, height);
        }
    }
  center();
}

GRPlotMainWindow::~GRPlotMainWindow() = default;

void GRPlotMainWindow::findButtonClickedSlot()
{
  QString search_string = find_line_edit->text();
  message->find(search_string, QTextDocument::FindWholeWords);
}

void GRPlotMainWindow::closeEvent(QCloseEvent *event)
{
  grm_finalize();
}

void GRPlotMainWindow::keyPressEvent(QKeyEvent *event)
{
  if (this->help_mode)
    {
      if (event->key() == Qt::Key_Enter || event->key() == Qt::Key_Return) findButtonClickedSlot();
    }
}

void GRPlotMainWindow::resizeGRPlotWidget(int w, int h)
{
  // the addition of a DockWidget will lead to a smaller grplotWidget
  // -> resize the grplotWidget to its desired size
  // -> GrPlotMainWindow will grow
  if (grplot_widget_ != nullptr)
    {
      grplot_widget_->setMinimumSize(w, h);
      adjustSize();
      grplot_widget_->setMinimumSize(0, 0);
    }
}

void GRPlotMainWindow::showEditElementDockSlot()
{
  if (!edit_element_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      edit_element_dock_widget->show();
      edit_element_dock_widget->setWindowTitle(grplot_widget_->getEditElementWidget()->windowTitle());
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::showTreeWidgetDockSlot()
{
  if (!tree_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      tree_dock_widget->show();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::showTableWidgetDockSlot()
{
  if (!table_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      table_dock_widget->show();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::showTextPreviewDockSlot()
{
  if (!text_preview_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      text_preview_dock_widget->show();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::showSelectionListDockSlot()
{
  if (!selection_list_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      selection_list_dock_widget->show();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::showIconBarDockSlot()
{
  if (!icon_bar_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      icon_bar_dock_widget->show();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::showAddElementDockSlot()
{
  if (!add_element_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      add_element_dock_widget->show();
      add_element_dock_widget->setWindowTitle(grplot_widget_->getAddElementWidget()->windowTitle());
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideEditElementDockSlot()
{
  if (edit_element_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      edit_element_dock_widget->hide();
      edit_element_dock_widget->setWindowTitle(grplot_widget_->getEditElementWidget()->windowTitle());
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideTreeWidgetDockSlot()
{
  if (tree_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      tree_dock_widget->hide();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideTableWidgetDockSlot()
{
  if (table_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      table_dock_widget->hide();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideTextPreviewDockSlot()
{
  if (text_preview_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      text_preview_dock_widget->hide();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideSelectionListDockSlot()
{
  if (selection_list_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      selection_list_dock_widget->hide();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideIconBarDockSlot()
{
  if (icon_bar_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      icon_bar_dock_widget->hide();
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::hideAddElementDockSlot()
{
  if (add_element_dock_widget->isVisible())
    {
      auto w = grplot_widget_->width();
      auto h = grplot_widget_->height();
      add_element_dock_widget->hide();
      add_element_dock_widget->setWindowTitle(grplot_widget_->getAddElementWidget()->windowTitle());
      resizeGRPlotWidget(w, h);
      keepInDisplay();
    }
}

void GRPlotMainWindow::closeEditElementDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  edit_element_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::closeTreeWidgetDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  tree_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::closeTableWidgetDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  table_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::closeTextPreviewDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  text_preview_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::closeSelectionListDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  selection_list_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::closeIconBarDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  icon_bar_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::closeAddElementDockSlot()
{
  auto w = grplot_widget_->width();
  auto h = grplot_widget_->height();

  add_element_dock_widget->hide();
  resizeGRPlotWidget(w, h);
  keepInDisplay();
}

void GRPlotMainWindow::center()
{
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
  auto new_dimensions = this->screen()->availableSize();
  this->move((new_dimensions.width() - this->width()) / 2, (new_dimensions.height() - this->height()) / 2);
#else
  QScreen *screen = QGuiApplication::primaryScreen();
  QRect screen_geometry = screen->geometry();
  this->move((screen_geometry.width() - this->width()) / 2, (screen_geometry.height() - this->height()) / 2);
#endif
}

void GRPlotMainWindow::updateEditElementDockTitleSlot()
{
  if (edit_element_dock_widget->isVisible())
    edit_element_dock_widget->setWindowTitle(grplot_widget_->getEditElementWidget()->windowTitle());
}

void GRPlotMainWindow::keepInDisplay()
{
  int width, height;
#if QT_VERSION >= QT_VERSION_CHECK(6, 7, 0)
  width = this->screen()->availableSize().width();
  height = this->screen()->availableSize().height();
#else
  QScreen *screen = QGuiApplication::primaryScreen();
  width = screen->geometry().width();
  height = screen->geometry().height();
#endif
  if (this->x() + this->frameGeometry().width() > width)
    move(this->x() - std::min((this->x() + this->frameGeometry().width() - width), RIGHT_AREA_WIDTH), this->y());

  if (this->frameGeometry().width() > width || this->frameGeometry().height() > height)
    {
      auto new_width = std::min(this->frameGeometry().width(), width);
      auto new_height = std::min(this->frameGeometry().height(), height);

      // trim the width/height to the display width/height and then apply possible dock widgets to the grplotwidget
      if (edit_element_dock_widget->isVisible() || text_preview_dock_widget->isVisible() ||
          add_element_dock_widget->isVisible())
        new_width -= RIGHT_AREA_WIDTH;
      if (tree_dock_widget->isVisible() || table_dock_widget->isVisible() || selection_list_dock_widget->isVisible())
        new_width -= LEFT_AREA_WIDTH;
      if (icon_bar_dock_widget->isVisible()) new_height -= 28;
      resizeGRPlotWidget(new_width, new_height);
      center();
    }
}
