/***

  Olive - Non-Linear Video Editor
  Copyright (C) 2022 Olive Team

  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.

***/

#include "colorvalueswidget.h"

#include <QGridLayout>
#include <QMouseEvent>
#include <QTabWidget>

#include "config/config.h"
#include "core.h"
#include "ui/icons/icons.h"

namespace olive {

ColorValuesWidget::ColorValuesWidget(ColorManager *manager, QWidget *parent) :
  QWidget(parent),
  manager_(manager),
  input_to_ref_(nullptr),
  ref_to_display_(nullptr),
  display_to_ref_(nullptr),
  ref_to_input_(nullptr)
{
  QVBoxLayout* layout = new QVBoxLayout(this);

  // Create preview box
  {
    QHBoxLayout* preview_layout = new QHBoxLayout();

    preview_layout->setContentsMargins(0, 0, 0, 0);

    preview_layout->addWidget(new QLabel(tr("Preview")));

    preview_ = new ColorPreviewBox();
    preview_->setFixedHeight(fontMetrics().height() * 3 / 2);
    preview_layout->addWidget(preview_);

    color_picker_btn_ = new QPushButton();
    color_picker_btn_->setIcon(icon::ColorPicker);
    color_picker_btn_->setFixedWidth(color_picker_btn_->sizeHint().height());
    color_picker_btn_->setCheckable(true);
    connect(color_picker_btn_, &QPushButton::toggled, this, &ColorValuesWidget::ColorPickedBtnToggled);
    connect(Core::instance(), &Core::ColorPickerColorEmitted, this, &ColorValuesWidget::SetReferenceColor);
    preview_layout->addWidget(color_picker_btn_);

    layout->addLayout(preview_layout);
  }

  // Create value tabs
  {
    QTabWidget* tabs = new QTabWidget();

    input_tab_ = new ColorValuesTab(true);
    tabs->addTab(input_tab_, tr("Input"));
    connect(input_tab_, &ColorValuesTab::ColorChanged, this, &ColorValuesWidget::UpdateValuesFromInput);
    connect(input_tab_, &ColorValuesTab::ColorChanged, this, &ColorValuesWidget::ColorChanged);
    connect(input_tab_, &ColorValuesTab::ColorChanged, preview_, &ColorPreviewBox::SetColor);

    reference_tab_ = new ColorValuesTab();
    tabs->addTab(reference_tab_, tr("Reference"));
    connect(reference_tab_, &ColorValuesTab::ColorChanged, this, &ColorValuesWidget::UpdateValuesFromRef);

    display_tab_ = new ColorValuesTab();
    tabs->addTab(display_tab_, tr("Display"));
    connect(display_tab_, &ColorValuesTab::ColorChanged, this, &ColorValuesWidget::UpdateValuesFromDisplay);

    // FIXME: Display -> Ref temporarily disabled due to OCIO crash (see ColorDialog::ColorSpaceChanged for more info)
    display_tab_->setEnabled(false);

    layout->addWidget(tabs);
  }
}

Color ColorValuesWidget::GetColor() const
{
  return reference_tab_->GetColor();
}

void ColorValuesWidget::SetColorProcessor(ColorProcessorPtr input_to_ref, ColorProcessorPtr ref_to_display, ColorProcessorPtr display_to_ref, ColorProcessorPtr ref_to_input)
{
  input_to_ref_ = input_to_ref;
  ref_to_display_ = ref_to_display;
  display_to_ref_ = display_to_ref;
  ref_to_input_ = ref_to_input;

  UpdateValuesFromInput();

  preview_->SetColorProcessor(input_to_ref_, ref_to_display_);
}

bool ColorValuesWidget::eventFilter(QObject *watcher, QEvent *event)
{
  if (event->type() == QEvent::MouseButtonPress) {
    // Should signal to Core to stop pixel sampling and to us to remove our event filter
    bool use_this_color = true;

    foreach (QWidget *w, ignore_pick_from_) {
      if (w->underMouse()) {
        use_this_color = false;
        break;
      }
    }

    if (use_this_color) {
      picker_end_color_ = GetColor();
    }
    color_picker_btn_->setChecked(false);
    return true;
  } else if (event->type() == QEvent::KeyPress) {
    QKeyEvent *key_ev = static_cast<QKeyEvent*>(event);

    if (key_ev->key() == Qt::Key_Escape) {
      color_picker_btn_->setChecked(false);
      return true;
    }
  }

  return QWidget::eventFilter(watcher, event);
}

void ColorValuesWidget::SetColor(const Color &c)
{
  input_tab_->SetColor(c);
  preview_->SetColor(c);

  UpdateValuesFromInput();
}

void ColorValuesWidget::SetReferenceColor(const Color &c)
{
  reference_tab_->SetColor(c);

  UpdateValuesFromRef();
}

void ColorValuesWidget::UpdateValuesFromInput()
{
  UpdateRefFromInput();
  UpdateDisplayFromRef();
}

void ColorValuesWidget::UpdateValuesFromRef()
{
  UpdateInputFromRef();
  UpdateDisplayFromRef();
}

void ColorValuesWidget::UpdateValuesFromDisplay()
{
  UpdateRefFromDisplay();
  UpdateInputFromRef();
}

void ColorValuesWidget::ColorPickedBtnToggled(bool e)
{
  Core::instance()->RequestPixelSamplingInViewers(e);

  if (e) {
    qApp->installEventFilter(this);

    // Store current color in case it needs to be restored
    picker_end_color_ = GetColor();
  } else {
    qApp->removeEventFilter(this);

    // Restore original color (or use overridden color from eventFilter)
    SetReferenceColor(picker_end_color_);
    emit ColorChanged(input_tab_->GetColor());
  }
}

void ColorValuesWidget::UpdateInputFromRef()
{
  if (ref_to_input_) {
    input_tab_->SetColor(ref_to_input_->ConvertColor(reference_tab_->GetColor()));
  } else {
    input_tab_->SetColor(reference_tab_->GetColor());
  }

  preview_->SetColor(input_tab_->GetColor());
  emit ColorChanged(input_tab_->GetColor());
}

void ColorValuesWidget::UpdateDisplayFromRef()
{
  if (ref_to_display_) {
    display_tab_->SetColor(ref_to_display_->ConvertColor(reference_tab_->GetColor()));
  } else {
    display_tab_->SetColor(reference_tab_->GetColor());
  }
}

void ColorValuesWidget::UpdateRefFromInput()
{
  if (input_to_ref_) {
    reference_tab_->SetColor(input_to_ref_->ConvertColor(input_tab_->GetColor()));
  } else {
    reference_tab_->SetColor(input_tab_->GetColor());
  }
}

void ColorValuesWidget::UpdateRefFromDisplay()
{
  if (display_to_ref_) {
    reference_tab_->SetColor(display_to_ref_->ConvertColor(display_tab_->GetColor()));
  } else {
    reference_tab_->SetColor(display_tab_->GetColor());
  }
}

const double ColorValuesTab::kLegacyMultiplier = 255.0;

ColorValuesTab::ColorValuesTab(bool with_legacy_option, QWidget *parent) :
  QWidget(parent)
{
  QGridLayout* layout = new QGridLayout(this);

  int row = 0;

  if (with_legacy_option) {
    legacy_box_ = new QCheckBox(tr("Use legacy (8-bit) values"));
    legacy_box_->setChecked(OLIVE_CONFIG("UseLegacyColorInInputTab").toBool());
    connect(legacy_box_, &QCheckBox::clicked, this, &ColorValuesTab::LegacyChanged);
    layout->addWidget(legacy_box_, row, 0, 1, 2);
    row++;
  } else {
    legacy_box_ = nullptr;
  }

  sliders_.resize(3);

  layout->addWidget(new QLabel(tr("Red")), row, 0);

  red_slider_ = CreateColorSlider();
  sliders_[0] = red_slider_;
  layout->addWidget(red_slider_, row, 1);

  row++;

  layout->addWidget(new QLabel(tr("Green")), row, 0);

  green_slider_ = CreateColorSlider();
  sliders_[1] = green_slider_;
  layout->addWidget(green_slider_, row, 1);

  row++;

  layout->addWidget(new QLabel(tr("Blue")), row, 0);

  blue_slider_ = CreateColorSlider();
  sliders_[2] = blue_slider_;
  layout->addWidget(blue_slider_, row, 1);

  row++;

  hex_lbl_ = new QLabel(tr("Web"));
  layout->addWidget(hex_lbl_, row, 0);

  hex_slider_ = new StringSlider();
  connect(hex_slider_, &StringSlider::ValueChanged, this, &ColorValuesTab::HexChanged);
  layout->addWidget(hex_slider_, row, 1);

  if (legacy_box_) {
    LegacyChanged(AreSlidersLegacyValues());
  }
}

Color ColorValuesTab::GetColor() const
{
  return Color(GetRed(), GetGreen(), GetBlue());
}

void ColorValuesTab::SetColor(const Color &c)
{
  SetRed(c.red());
  SetGreen(c.green());
  SetBlue(c.blue());
}

double ColorValuesTab::GetRed() const
{
  return GetValueInternal(red_slider_);
}

double ColorValuesTab::GetGreen() const
{
  return GetValueInternal(green_slider_);
}

double ColorValuesTab::GetBlue() const
{
  return GetValueInternal(blue_slider_);
}

void ColorValuesTab::SetRed(double r)
{
  SetValueInternal(red_slider_, r);
}

void ColorValuesTab::SetGreen(double g)
{
  SetValueInternal(green_slider_, g);
}

void ColorValuesTab::SetBlue(double b)
{
  SetValueInternal(blue_slider_, b);
}

double ColorValuesTab::GetValueInternal(FloatSlider *slider) const
{
  double d = slider->GetValue();

  if (AreSlidersLegacyValues()) {
    d /= kLegacyMultiplier;
  }

  return d;
}

void ColorValuesTab::SetValueInternal(FloatSlider *slider, double v)
{
  if (AreSlidersLegacyValues()) {
    v *= kLegacyMultiplier;
  }

  slider->SetValue(v);
  UpdateHex();
}

FloatSlider *ColorValuesTab::CreateColorSlider()
{
  FloatSlider* fs = new FloatSlider();
  fs->SetLadderElementCount(1);
  connect(fs, &FloatSlider::ValueChanged, this, &ColorValuesTab::SliderChanged);
  return fs;
}

void ColorValuesTab::SliderChanged()
{
  emit ColorChanged(GetColor());
  UpdateHex();
}

void ColorValuesTab::LegacyChanged(bool legacy)
{
  OLIVE_CONFIG("UseLegacyColorInInputTab") = legacy;

  double legacy_multiplier = legacy ? kLegacyMultiplier : 1.0/kLegacyMultiplier;
  int decimal_places = legacy ? 0 : 5;
  double drag_multiplier = legacy ? 1.0 : 0.01;

  foreach (FloatSlider *s, sliders_) {
    s->SetValue(s->GetValue() * legacy_multiplier);
    s->SetDecimalPlaces(decimal_places);
    s->SetDragMultiplier(drag_multiplier);
  }

  UpdateHex();
}

QString RGBValToString(double d)
{
  QString s = QString::number(d);

  if (!s.contains('.')) {
    s.append(QStringLiteral(".0"));
  }

  return s;
}

void ColorValuesTab::UpdateHex()
{
  if (AreSlidersLegacyValues()) {
    double r = red_slider_->GetValue();
    double g = green_slider_->GetValue();
    double b = blue_slider_->GetValue();

    if (r > kLegacyMultiplier || g > kLegacyMultiplier || b > kLegacyMultiplier) {
      hex_slider_->SetValue(tr("(Invalid)"));
    } else {
      uint32_t rgb = (uint8_t(r) << 16) | (uint8_t(g) << 8) | uint8_t(b);

      hex_slider_->SetValue(QStringLiteral("%1").arg(rgb, 6, 16, QLatin1Char('0')).toUpper());
    }
  } else {
    hex_slider_->SetValue(QStringLiteral("rgb(%1, %2, %3)").arg(RGBValToString(red_slider_->GetValue()), RGBValToString(green_slider_->GetValue()), RGBValToString(blue_slider_->GetValue())));
  }
}

bool ParseRGBString(QString s, double *r, double *g, double *b)
{
  // Trim whitespace
  s = s.trimmed();

  s.remove(QStringLiteral("rgba"), Qt::CaseInsensitive);
  s.remove(QStringLiteral("rgb"), Qt::CaseInsensitive);
  s.remove('(');
  s.remove(')');

  QStringList vals = s.split(',');
  if (vals.size() < 3) {
    return false;
  }

  bool ok;
  *r = vals.at(0).toDouble(&ok);
  if (!ok) return false;

  *g = vals.at(1).toDouble(&ok);
  if (!ok) return false;

  *b = vals.at(2).toDouble(&ok);
  if (!ok) return false;

  return true;
}

void ColorValuesTab::HexChanged(const QString &s)
{
  bool ok;
  uint32_t hex = s.toULong(&ok, 16);

  if (ok) {
    if (hex >= 0x1000000) {
      hex >>= 8;
    }

    uint32_t r = (hex & 0xFF0000) >> 16;
    uint32_t g = (hex & 0x00FF00) >> 8;
    uint32_t b = (hex & 0x0000FF);

    if (AreSlidersLegacyValues()) {
      red_slider_->SetValue(r);
      green_slider_->SetValue(g);
      blue_slider_->SetValue(b);
    } else {
      red_slider_->SetValue(double(r)/kLegacyMultiplier);
      green_slider_->SetValue(double(g)/kLegacyMultiplier);
      blue_slider_->SetValue(double(b)/kLegacyMultiplier);
    }

    emit ColorChanged(GetColor());
  } else {
    // Attempt to parse rgb/rgba
    double r, g, b;
    if (ParseRGBString(s, &r, &g, &b)) {
      if (AreSlidersLegacyValues()) {
        red_slider_->SetValue(r*kLegacyMultiplier);
        green_slider_->SetValue(g*kLegacyMultiplier);
        blue_slider_->SetValue(b*kLegacyMultiplier);
      } else {
        red_slider_->SetValue(r);
        green_slider_->SetValue(g);
        blue_slider_->SetValue(b);
      }

      emit ColorChanged(GetColor());
    }
  }

  // Conform string to our formatting
  UpdateHex();
}

bool ColorValuesTab::AreSlidersLegacyValues() const
{
  return legacy_box_ && legacy_box_->isChecked();
}

}
