#include <iostream>
#include <sstream>

#include "../plot/plotwidget.h"

#include "imagepropertieswindow.h"
#include "maskedheatmap.h"

ImagePropertiesWindow::ImagePropertiesWindow(PlotWidget& imageWidget,
                                             const std::string& title)
    : Gtk::Window(),
      _plotWidget(imageWidget),
      _applyButton("_Apply", true),
      _exportButton("_Export", true),
      _exportDataButton("Export _data", true),
      _closeButton("_Close", true),

      _colorMapFrame("Color map"),

      _scaleFrame("Scale"),
      _minMaxScaleButton("From min to max"),
      _winsorizedScaleButton("Winsorized min and max"),
      _specifiedScaleButton("Specified:"),
      _scaleMinLabel("Scale minimum:"),
      _scaleMaxLabel("Scale maximum:"),
      _scaleMinEntry(),
      _scaleMaxEntry(),

      _optionsFrame("Options"),
      _normalOptionsButton("Normal scale"),
      _logScaleButton("Logarithmic scale"),

      _filterFrame("Interpolation"),
      _bestFilterButton("Best"),
      _nearestFilterButton("Nearest"),

      _axesFrame("Title & axes"),
      _showXYAxes("Show XY axes"),
      _showColorScale("Show color scale"),
      _showTitleButton("Show title"),
      _showXAxisDescriptionButton("x-axis desc"),
      _showYAxisDescriptionButton("y-axis desc"),
      _showZAxisDescriptionButton("z-axis desc"),
      _manualXAxisDescription("manual"),
      _manualYAxisDescription("manual"),
      _manualZAxisDescription("manual") {
  _maskedHeatMap = dynamic_cast<MaskedHeatMap*>(&_plotWidget.Plot());
  if (!_maskedHeatMap) {
    throw std::invalid_argument(
        "ImagePropertiesWindow called for a plot that is not a heat map");
  }

  set_title(title);

  initColorMapButtons();
  initScaleWidgets();
  initOptionsWidgets();
  initFilterWidgets();
  _framesHBox.append(_filterAndOptionsBox);
  initAxisWidgets();

  _applyButton.signal_clicked().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onApplyClicked));
  _bottomButtonBox.append(_applyButton);

  _exportButton.signal_clicked().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onExportClicked));
  _bottomButtonBox.append(_exportButton);

  _exportDataButton.signal_clicked().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onExportDataClicked));
  _bottomButtonBox.append(_exportDataButton);

  _closeButton.signal_clicked().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onCloseClicked));
  _bottomButtonBox.append(_closeButton);

  _topVBox.append(_framesHBox);

  _bottomButtonBox.set_homogeneous(true);
  _topVBox.append(_bottomButtonBox);

  set_child(_topVBox);
}

void ImagePropertiesWindow::initColorMapButtons() {
  _colorMapStore = Gtk::ListStore::create(_colorMapColumns);

  addColorMap("Grayscale", ColorMap::Grayscale);
  addColorMap("Inverted grayscale", ColorMap::Inverted);
  addColorMap("Hot/cold", ColorMap::HotCold);
  addColorMap("Red/blue", ColorMap::RedBlue);
  addColorMap("Black/red", ColorMap::BlackRed);
  addColorMap("Red/Yellow/Blue", ColorMap::RedYellowBlue);
  addColorMap("Fire", ColorMap::Fire);
  addColorMap("Cool", ColorMap::Cool);
  addColorMap("Cubehelix", ColorMap::CubeHelix);
  addColorMap("Cubehelix+", ColorMap::CubeHelixColourful);
  addColorMap("Viridis", ColorMap::Viridis);
  addColorMap("Rainbow", ColorMap::Rainbow);

  _colorMapCombo.set_model(_colorMapStore);
  _colorMapCombo.pack_start(_colorMapColumns.name);

  const ColorMap::Type selectedMap = _maskedHeatMap->GetColorMap();
  const Gtk::ListStore::Children children = _colorMapStore->children();
  for (auto row : children) {
    if (row[_colorMapColumns.colorMap] == selectedMap) {
      _colorMapCombo.set_active(row.get_iter());
      break;
    }
  }

  _colorMapBox.append(_colorMapCombo);

  _colorMapFrame.set_child(_colorMapBox);

  _framesHBox.append(_colorMapFrame);
}

void ImagePropertiesWindow::initScaleWidgets() {
  _scaleFrame.set_child(_scaleBox);

  _scaleBox.append(_minMaxScaleButton);
  _minMaxScaleButton.signal_toggled().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onScaleChanged));

  _scaleBox.append(_winsorizedScaleButton);
  _winsorizedScaleButton.set_group(_minMaxScaleButton);
  _winsorizedScaleButton.signal_toggled().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onScaleChanged));
  _scaleBox.append(_specifiedScaleButton);

  _specifiedScaleButton.set_group(_minMaxScaleButton);
  _specifiedScaleButton.signal_toggled().connect(
      sigc::mem_fun(*this, &ImagePropertiesWindow::onScaleChanged));

  switch (_maskedHeatMap->ZRange().minimum) {
    default:
    case RangeLimit::Extreme:
      _minMaxScaleButton.set_active(true);
      break;
    case RangeLimit::Winsorized:
      _winsorizedScaleButton.set_active(true);
      break;
    case RangeLimit::Specified:
      _specifiedScaleButton.set_active(true);
      break;
  }
  onScaleChanged();

  updateMinMaxEntries();

  _scaleBox.append(_scaleMinLabel);
  _scaleBox.append(_scaleMinEntry);

  _scaleBox.append(_scaleMaxLabel);
  _scaleBox.append(_scaleMaxEntry);

  _framesHBox.append(_scaleFrame);
}

void ImagePropertiesWindow::initOptionsWidgets() {
  _optionsBox.append(_normalOptionsButton);
  _optionsBox.append(_logScaleButton);
  _logScaleButton.set_group(_normalOptionsButton);

  if (_maskedHeatMap->LogZScale()) {
    _logScaleButton.set_active(true);
  } else {
    _normalOptionsButton.set_active(true);
  }

  _optionsFrame.set_child(_optionsBox);

  _filterAndOptionsBox.append(_optionsFrame);
}

void ImagePropertiesWindow::initFilterWidgets() {
  _filterBox.append(_bestFilterButton);

  _filterBox.append(_nearestFilterButton);
  _nearestFilterButton.set_group(_bestFilterButton);

  switch (_maskedHeatMap->CairoFilter()) {
    default:
    case Cairo::SurfacePattern::Filter::BEST:
      _bestFilterButton.set_active(true);
      break;
    case Cairo::SurfacePattern::Filter::NEAREST:
      _nearestFilterButton.set_active(true);
      break;
  }

  _filterFrame.set_child(_filterBox);

  _filterAndOptionsBox.append(_filterFrame);
}

void ImagePropertiesWindow::initAxisWidgets() {
  const MaskedHeatMap& maskedHeatMap =
      static_cast<const MaskedHeatMap&>(_plotWidget.Plot());
  _showXYAxes.set_active(maskedHeatMap.ShowXAxis() ||
                         maskedHeatMap.ShowYAxis());
  _axesGeneralBox.append(_showXYAxes);

  _showColorScale.set_active(maskedHeatMap.ShowColorScale());
  _axesGeneralBox.append(_showColorScale);

  _axesHBox.append(_axesGeneralBox);

  _showTitleButton.set_active(maskedHeatMap.ShowTitle());
  _titleBox.append(_showTitleButton);
  _titleEntry.set_text(maskedHeatMap.TitleText());
  _titleBox.append(_titleEntry);

  _axesVisibilityBox.append(_titleBox);

  _showXAxisDescriptionButton.set_active(maskedHeatMap.ShowXAxisDescription());
  _xAxisBox.append(_showXAxisDescriptionButton);

  _manualXAxisDescription.set_active(maskedHeatMap.ManualXAxisDescription());
  _xAxisBox.append(_manualXAxisDescription);

  _xAxisDescriptionEntry.set_text(maskedHeatMap.XAxisDescription());
  _xAxisBox.append(_xAxisDescriptionEntry);

  _axesVisibilityBox.append(_xAxisBox);

  _showYAxisDescriptionButton.set_active(maskedHeatMap.ShowYAxisDescription());
  _yAxisBox.append(_showYAxisDescriptionButton);

  _manualYAxisDescription.set_active(maskedHeatMap.ManualYAxisDescription());
  _yAxisBox.append(_manualYAxisDescription);

  _yAxisDescriptionEntry.set_text(maskedHeatMap.YAxisDescription());
  _yAxisBox.append(_yAxisDescriptionEntry);

  _axesVisibilityBox.append(_yAxisBox);

  _showZAxisDescriptionButton.set_active(maskedHeatMap.ShowZAxisDescription());
  _zAxisBox.append(_showZAxisDescriptionButton);

  _manualZAxisDescription.set_active(maskedHeatMap.ManualZAxisDescription());
  _zAxisBox.append(_manualZAxisDescription);

  _zAxisDescriptionEntry.set_text(maskedHeatMap.ZAxisDescription());
  _zAxisBox.append(_zAxisDescriptionEntry);

  _axesVisibilityBox.append(_zAxisBox);

  _axesHBox.append(_axesVisibilityBox);

  _axesFrame.set_child(_axesHBox);
  _topVBox.append(_axesFrame);
}

void ImagePropertiesWindow::updateMinMaxEntries() {
  std::stringstream minStr;
  minStr << _maskedHeatMap->Min();
  _scaleMinEntry.set_text(minStr.str());

  std::stringstream maxStr;
  maxStr << _maskedHeatMap->Max();
  _scaleMaxEntry.set_text(maxStr.str());
}

void ImagePropertiesWindow::onApplyClicked() {
  MaskedHeatMap& maskedHeatMap =
      static_cast<MaskedHeatMap&>(_plotWidget.Plot());
  maskedHeatMap.SetColorMap(
      (*_colorMapCombo.get_active())[_colorMapColumns.colorMap]);

  if (_minMaxScaleButton.get_active()) {
    maskedHeatMap.SetZRange(FullRange());
  } else if (_winsorizedScaleButton.get_active()) {
    maskedHeatMap.SetZRange(WinsorizedRange());
  } else if (_specifiedScaleButton.get_active()) {
    RangeConfiguration range;
    range.minimum = RangeLimit::Specified;
    range.maximum = RangeLimit::Specified;
    range.specified_min = atof(_scaleMinEntry.get_text().c_str());
    range.specified_max = atof(_scaleMaxEntry.get_text().c_str());
    maskedHeatMap.SetZRange(range);
  }

  maskedHeatMap.SetLogZScale(_logScaleButton.get_active());

  if (_bestFilterButton.get_active())
    maskedHeatMap.SetCairoFilter(Cairo::SurfacePattern::Filter::BEST);
  else if (_nearestFilterButton.get_active())
    maskedHeatMap.SetCairoFilter(Cairo::SurfacePattern::Filter::NEAREST);

  maskedHeatMap.SetShowTitle(_showTitleButton.get_active());
  maskedHeatMap.SetTitleText(_titleEntry.get_text());
  maskedHeatMap.SetShowXAxis(_showXYAxes.get_active());
  maskedHeatMap.SetShowYAxis(_showXYAxes.get_active());
  maskedHeatMap.SetShowXAxisDescription(
      _showXAxisDescriptionButton.get_active());
  maskedHeatMap.SetShowYAxisDescription(
      _showYAxisDescriptionButton.get_active());
  maskedHeatMap.SetShowZAxisDescription(
      _showZAxisDescriptionButton.get_active());

  maskedHeatMap.SetManualXAxisDescription(_manualXAxisDescription.get_active());
  if (_manualXAxisDescription.get_active())
    maskedHeatMap.SetXAxisDescription(_xAxisDescriptionEntry.get_text());

  maskedHeatMap.SetManualYAxisDescription(_manualYAxisDescription.get_active());
  if (_manualYAxisDescription.get_active())
    maskedHeatMap.SetYAxisDescription(_yAxisDescriptionEntry.get_text());

  maskedHeatMap.SetManualZAxisDescription(_manualZAxisDescription.get_active());
  if (_manualZAxisDescription.get_active())
    maskedHeatMap.SetZAxisDescription(_zAxisDescriptionEntry.get_text());

  maskedHeatMap.SetShowColorScale(_showColorScale.get_active());

  _plotWidget.Update();

  updateMinMaxEntries();
}

void ImagePropertiesWindow::onCloseClicked() { hide(); }

void ImagePropertiesWindow::onExportClicked() {
  if (_maskedHeatMap->HasImage()) {
    dialog_ = std::make_unique<Gtk::FileChooserDialog>(
        "Specify image filename", Gtk::FileChooser::Action::SAVE);
    dialog_->set_transient_for(*this);

    // Add response buttons the the dialog:
    dialog_->add_button("_Cancel", Gtk::ResponseType::CANCEL);
    dialog_->add_button("_Save", Gtk::ResponseType::OK);

    const Glib::RefPtr<Gtk::FileFilter> pdfFilter = Gtk::FileFilter::create();
    static constexpr char pdfName[] = "Portable Document Format (*.pdf)";
    pdfFilter->set_name(pdfName);
    pdfFilter->add_pattern("*.pdf");
    pdfFilter->add_mime_type("application/pdf");
    dialog_->add_filter(pdfFilter);

    const Glib::RefPtr<Gtk::FileFilter> svgFilter = Gtk::FileFilter::create();
    static constexpr char svgName[] = "Scalable Vector Graphics (*.svg)";
    svgFilter->set_name(svgName);
    svgFilter->add_pattern("*.svg");
    svgFilter->add_mime_type("image/svg+xml");
    dialog_->add_filter(svgFilter);

    const Glib::RefPtr<Gtk::FileFilter> pngFilter = Gtk::FileFilter::create();
    static constexpr char pngName[] = "Portable Network Graphics (*.png)";
    pngFilter->set_name(pngName);
    pngFilter->add_pattern("*.png");
    pngFilter->add_mime_type("image/png");
    dialog_->add_filter(pngFilter);

    dialog_->signal_response().connect([this](int response) {
      if (response == Gtk::ResponseType::OK) {
        const Glib::RefPtr<const Gtk::FileFilter> filter =
            dialog_->get_filter();
        if (filter->get_name() == pdfName)
          _plotWidget.SavePdf(dialog_->get_file()->get_path());
        else if (filter->get_name() == svgName)
          _plotWidget.SaveSvg(dialog_->get_file()->get_path());
        else
          _plotWidget.SavePng(dialog_->get_file()->get_path());
      }
    });

    dialog_->show();
  }
}

void ImagePropertiesWindow::onExportDataClicked() {
  if (_maskedHeatMap->HasImage()) {
    dialog_ = std::make_unique<Gtk::FileChooserDialog>(
        "Specify data filename", Gtk::FileChooser::Action::SAVE);
    dialog_->set_transient_for(*this);

    // Add response buttons the the dialog:
    dialog_->add_button("_Cancel", Gtk::ResponseType::CANCEL);
    dialog_->add_button("_Save", Gtk::ResponseType::OK);

    const Glib::RefPtr<Gtk::FileFilter> pdfFilter = Gtk::FileFilter::create();
    const std::string pdfName =
        "Text format: width; height; data1; data2... (*.txt)";
    pdfFilter->set_name(pdfName);
    pdfFilter->add_pattern("*.txt");
    pdfFilter->add_mime_type("text/plain");
    dialog_->add_filter(pdfFilter);

    dialog_->signal_response().connect([this](int response) {
      if (response == Gtk::ResponseType::OK) {
        const MaskedHeatMap& maskedHeatMap =
            static_cast<MaskedHeatMap&>(_plotWidget.Plot());
        maskedHeatMap.SaveText(dialog_->get_file()->get_path());
      }
    });

    dialog_->show();
  }
}
