//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "TransferFunctionViewer.hpp"

#include <QPalette>
#include <QBrush>
#include <QPixmap>
#include <QPainter>
#include <QAction>
#include <QLinearGradient>
#include <QPaintEvent>
#include <QRect>
#include <QPolygonF>
#include <QMouseEvent>
#include <QColorDialog>
#include <QBrush>
#include <QPen>

#include <math.h>
#include <algorithm>

#include <QTextStream>
#include <stdio.h>

#include "EditMarkersDlg.hpp"

static QTextStream out(stdout);
static QTextStream err(stderr);

QTransferFunctionViewer::QTransferFunctionViewer(QWidget *parent, Qt::WindowFlags f)
  : QWidget(parent, f), minValue(0), maxValue(0), sticking(false), marker_size(5), 
		select_pos(false), current_pos(-1), bg_size(20), bg_bright(150), bg_dark(100), 
		saved_pos(-1), bg_type(BG_CHECKS), _nb_values(512)
{
  setEnabled(true);
  setFocusPolicy(Qt::ClickFocus);
  setMouseTracking(true);
  activ_pos_color = QColor(200, 200, 200, 200);
  createBackground();
  use_histogram = false;
  reverse_act = new QAction("Reverse function", this);
  edit_markers = new QAction("Edit markers", this);
  connect(reverse_act, SIGNAL(triggered()), this, SLOT(reverseFunction()));
  connect(edit_markers, SIGNAL(triggered()), this, SLOT(editMarkers()));
  addAction(reverse_act);
  addAction(edit_markers);
  setContextMenuPolicy(Qt::ActionsContextMenu);
}

void QTransferFunctionViewer::reverseFunction()
{
  out << "Reverse function" << endl;
  transfer_fct.reverse();
  setupGradient();
  emit changedTransferFunction(transfer_fct);
  update();
}

void QTransferFunctionViewer::createBackground()
{
  QPalette pal = palette();
  switch (bg_type)
  {
    case BG_WHITE:
      pal.setColor(backgroundRole(), QColor(Qt::white));
      break;
    case BG_BLACK:
      pal.setColor(backgroundRole(), QColor(Qt::black));
      break;
    case BG_CHECKS:
      {
        QPixmap bg_pix = createChecks();
        pal.setBrush(backgroundRole(), QBrush(bg_pix));
      }
      break;
  }
  setPalette(pal);
}

QPixmap QTransferFunctionViewer::createChecks()
{
  QPixmap bg_pix(2*bg_size, 2*bg_size);
  bg_pix.fill(QColor(bg_bright, bg_bright, bg_bright));
  {
    QPainter paint_bg(&bg_pix);
    paint_bg.setPen(Qt::NoPen);
    paint_bg.setBrush(QColor(bg_dark, bg_dark, bg_dark));
    paint_bg.drawRect(0, bg_size, bg_size, bg_size);
    paint_bg.drawRect(bg_size, 0, bg_size, bg_size);
  }
  return bg_pix;
}

void QTransferFunctionViewer::setupGradient()
{
  if(transfer_fct.empty())
  {
    gradient = QLinearGradient();
    return;
  }
  QColor brush_color;
  QLinearGradient gr(QPointF(0, 0), QPointF(1, 0));
  double n = nbValues() - 1;
  for(size_t i = 0 ; i < nbValues() ; ++i)
  {
    Colorf col = transfer_fct.rgba(i/n);
    brush_color = (QColor)col;
    gr.setColorAt(i/n, brush_color);
  }
  gradient = gr;
}

void QTransferFunctionViewer::changeTransferFunction(const TransferFunction& fct)
{
  transfer_fct = fct;
  setupGradient();
  emit changedTransferFunction(transfer_fct);
  update();
}

void QTransferFunctionViewer::setHistogram(const std::vector<double>& h)
{
  histogram = h;
  if(not histogram.empty())
  {
    use_histogram = true;
    prepareHistogram();
  }
  else
  {
    use_histogram = false;
  }
}

void QTransferFunctionViewer::prepareHistogram()
{
  size_t nb_values = histogram.size();
  hist_values.clear();
  double min_value = HUGE_VAL;
  double max_value = 0;
  for(size_t i = 0 ; i < nb_values ; ++i)
  {
    if(0 < histogram[i] and histogram[i] < min_value) min_value = histogram[i];
    if(histogram[i] > max_value) max_value = histogram[i];
  }
  if(min_value > max_value)
  {
    histogram.clear();
    use_histogram = false;
    return;
  }
  double log_max = log(max_value);
  double log_min = (min_value>0)?log(min_value)-1:0;
  double log_delta = log_max - log_min;
  hist_values.resize(nb_values, 0.0);
  for(size_t i = 0 ; i < nb_values ; ++i)
  {
    double val = histogram[i];
    if(val > 0)
      val = (log(val)-log_min)/log_delta;
    else
      val = 0;
    hist_values[i] = val;
  }
  hist_shape = QPainterPath(QPointF(0,1));
  double dx = 1.0/(nb_values-1);
  for(size_t i = 0 ; i < nb_values ; ++i)
  {
    double v = hist_values[i] + 0.05;
    double x = i*dx;
    double y = 1-v;
    hist_shape.lineTo(x,y);
  }
  hist_shape.lineTo(1,0.95);
  hist_shape.lineTo(1,1);
  hist_shape.lineTo(0,1);
  hist_shape.lineTo(0,0.95);
  update();
}

void QTransferFunctionViewer::setStickers(const std::vector<double>& s)
{
  stickers = s;
  std::sort(stickers.begin(), stickers.end());
}

size_t QTransferFunctionViewer::nbValues() const
{
  return _nb_values;
}

void QTransferFunctionViewer::changeNbValues(int n)
{
  if(n > 10 and n != int(_nb_values))
  {
    _nb_values = n;
    setupGradient();
  }
}

void QTransferFunctionViewer::paintEvent(QPaintEvent *)
{
  if(transfer_fct.empty() or (use_histogram and hist_values.empty()))
  {
    return;
  }
  double w = width(), h = height();
  QPainter painter(this);
  painter.setPen(Qt::NoPen);
  // Paint histogram
  painter.setBrush(gradient);
  painter.save();
  painter.scale(w,h);
  gradient.setFinalStop(1,0);
  if(use_histogram)
    painter.drawPath(hist_shape);
  else
    painter.drawRect(QRect(0,0,1,1));
  painter.restore();
  // Now, paint markers
  painter.setBrush(QBrush(Qt::black));
  painter.setPen(QPen(Qt::black));
  double marker_size = this->marker_size-1;
  QPolygonF tr(3);
  tr[0] = QPointF(-marker_size, 0);
  tr[1] = QPointF(0, marker_size);
  tr[2] = QPointF(marker_size, 0);
  for(size_t i = 0 ; i < transfer_fct.size() ; ++i)
  {
    QPolygonF pos_tr = tr;
    double pos = transfer_fct[i];
    pos_tr.translate(w*pos, 0);
    painter.drawConvexPolygon(pos_tr);
  }
  // Now, paint stickers
  painter.setBrush(QBrush(Qt::white));
  for(size_t i = 0 ; i < stickers.size() ; ++i)
  {
    QPolygonF pos_tr = tr;
    double pos = stickers[i];
    pos_tr.translate(w*pos, 0);
    painter.drawConvexPolygon(pos_tr);
  }
  // At last, draw the line for the current position of the marker
  if(select_pos)
  {
    double x = current_pos * w;
    painter.setPen(QPen(activ_pos_color));
    painter.drawLine(x, marker_size+1, x, h);
  }
}

void QTransferFunctionViewer::mouseDoubleClickEvent(QMouseEvent *event)
{
  if((use_histogram and hist_values.empty()) or event->button() != Qt::LeftButton)
  {
    QWidget::mouseDoubleClickEvent(event);
    return;
  }
  double w = width(), h = height();
  current_pos = -1;
  for(size_t i = 0 ; i < transfer_fct.size()+stickers.size() ; ++i)
  {
    double pos;
    if(i < transfer_fct.size())
      pos = transfer_fct[i];
    else
      pos = stickers[i-transfer_fct.size()];
    double x = pos*w;
    if(fabs(event->x() - x) < marker_size)
    {
      current_pos = pos;
      event->accept();
      update(x-marker_size, 0, x+marker_size, h);
      break;
    }
  }
  double pos;
  if(current_pos >= 0)
    pos = current_pos;
  else
    pos = event->x() / w;
  Colorf col = transfer_fct.rgba(pos);
  QColor qcol = col;
  qcol = QColorDialog::getColor(qcol, this, "Change color", QColorDialog::ShowAlphaChannel);
  current_pos = -1;
  if(qcol.isValid())
  {
    int n1 = transfer_fct.size();
    transfer_fct.add_rgba_point(pos, Colorf(qcol));
    int n2 = transfer_fct.size();
    if(n2 != n1+1)
    {
      out << "Error, added " << n2-n1 << " points" << endl;
    }
    setupGradient();
    emit changedTransferFunction(transfer_fct);
  }
}

void QTransferFunctionViewer::mousePressEvent(QMouseEvent *event)
{
  resetMouseInteraction();
  if((use_histogram and hist_values.empty()) or event->button() != Qt::LeftButton or event->modifiers() != Qt::NoModifier)
  {
    QWidget::mousePressEvent(event);
    return;
  }
  double w = width(), h = height();
  double best_dist = w;
  double best_pos = -1;
  for(size_t i = 0 ; i < transfer_fct.size() ; ++i)
  {
    double pos = transfer_fct[i];
    double x = pos*w;
    double dist = fabs(event->x() - x);
    if(dist < marker_size and dist < best_dist)
    {
      best_pos = pos;
      best_dist = dist;
    }
  }
  if(best_pos >= 0)
  {
    select_pos = true;
    current_pos = best_pos;
    event->accept();
    double x = best_pos*w;
    update(x-marker_size, 0, x+marker_size, h);
  }
  else
  {
    select_pos = false;
    current_pos = -1;
  }
}

void QTransferFunctionViewer::resetMouseInteraction()
{
  select_pos = false;
  current_pos = -1;
  saved_pos = -1;
}

void QTransferFunctionViewer::mouseReleaseEvent(QMouseEvent *event)
{
  resetMouseInteraction();
  setupGradient();
  emit changedTransferFunction(transfer_fct);
  update();
  QWidget::mouseReleaseEvent(event);
}

void QTransferFunctionViewer::mouseMoveEvent(QMouseEvent *event)
{
  if(event->y() < 0 or event->y() > height())
  {
    if(not select_pos or current_pos == 0 or current_pos == 1)
      return;
    saved_color = transfer_fct.rgba_point(current_pos);
    saved_pos = current_pos;
    transfer_fct.remove_point(current_pos);
    current_pos = -1;
    select_pos = false;
    setupGradient();
    update();
  }
  else if(not select_pos)
  {
    if(saved_pos < 0) return;
    select_pos = true;
    current_pos = saved_pos;
    transfer_fct.add_rgba_point(current_pos, saved_color);
    saved_pos = -1;
    saved_color = Colorf();
    setupGradient();
    update();
  }
  else if(select_pos)
  {
    double new_pos = double(event->x()) / double(width());
    double dp = 1.0 / width();
    if(new_pos < current_pos)
    {
      double ppos = transfer_fct.prev_pos(current_pos);
      if(ppos >= 0 and new_pos <= ppos)
        new_pos = ppos + dp;
      else if(new_pos < 0)
        new_pos = 0;
    }
    else if(new_pos > current_pos)
    {
      double npos = transfer_fct.next_pos(current_pos);
      if(npos >= 0 and new_pos >= npos)
        new_pos = npos - dp;
      else if(new_pos > 1)
        new_pos = 1;
    }
    else
      return;
    if(sticking)
    {
      double dm = marker_size;
      if(fabs(new_pos - current_pos) < dm)
        return;
      sticking = false;
    }
    for(size_t i = 0 ; i < stickers.size() ; ++i)
    {
      double s = stickers[i];
      if((current_pos - s)*(new_pos-s) < 0 or new_pos == s)
      {
        sticking = true;
        new_pos = s;
        break;
      }
    }
    transfer_fct.move_point(current_pos, new_pos);
    current_pos = new_pos;
    setupGradient();
    update();
  }
}

void QTransferFunctionViewer::setMarkerSize(int s)
{
  if(marker_size != s)
  {
    marker_size = s;
    update();
  }
}

void QTransferFunctionViewer::setCheckSize(int s)
{
  if(bg_size != s)
  {
    bg_size = s;
    createBackground();
    update();
  }
}

void QTransferFunctionViewer::setBackgroundType(BackgroundType type)
{
  if(type != bg_type)
  {
    bg_type = type;
    createBackground();
    update();
  }
}

void QTransferFunctionViewer::setInterpolation(TransferFunction::Interpolation i)
{
  if(i != transfer_fct.interpolation())
  {
    transfer_fct.setInterpolation(i);
    setupGradient();
    emit changedTransferFunction(transfer_fct);
    update();
  }
}

void QTransferFunctionViewer::setSelectionColor(QColor col)
{
  if(col.isValid())
  {
    activ_pos_color = col;
    update();
  }
}

void QTransferFunctionViewer::editMarkers()
{
  EditMarkersDlg dlg(transfer_fct, this);
  if(dlg.exec() == QDialog::Accepted)
  {
    transfer_fct.setPointList(dlg.pointList());
    setupGradient();
    update();
    emit changedTransferFunction(transfer_fct);
  }
}

void QTransferFunctionViewer::setBounds(double min, double max)
{
  minValue = min;
  maxValue = max;
}

void QTransferFunctionViewer::autoAdjust()
{
  if(minValue < maxValue)
  {
    transfer_fct.adjust(minValue, maxValue);
    setupGradient();
    update();
    emit changedTransferFunction(transfer_fct);
  }
}

