/***************************************************************************
**
**  This file is part of SciFigs.
**
**  This library is free software; you can redistribute it and/or
**  modify it under the terms of the GNU Lesser General Public
**  License as published by the Free Software Foundation; either
**  version 2.1 of the License, or (at your option) any later version.
**
**  This file 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 Lesser General Public
**  License for more details.
**
**  You should have received a copy of the GNU Lesser General Public
**  License along with this library; if not, write to the Free Software
**  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2003-05-07
**  Copyright: 2003-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "ColorMapWidget.h"
#include "ColorMapProperties.h"
#include "GraphicObjectFactory.h"
#include "ColorMapLayer.h"
#include "XMLSciFigs.h"
#include "AxisWindow.h"

namespace SciFigs {

/*!
  \class ColorMapWidget ColorMapWidget.h
  \brief ColorMapWidget is used to draw colored palettes in a GraphicSheet.

  Usually color palettes are properties of certain types of GraphContentLayer like GridPlot.
  It is convenient to add a visual palette below or together with a colored grid or to any plot
  using colors for plotting values (the pseudo third dimension). It is possible to export the
  palette property to a ".pal" file and to import it to a freshly created ColorMapWidget.

  Here is an example of color palette:

  \image html ColorMapWidget.png "A typical color palette"
  \image latex ColorMapWidget.ps "A typical color palette" width=12cm

  Here is a basic code that adds a new palette to an existing sheet:
 
  \code
  ColorMapWidget * palette=
      static_cast<ColorMapWidget *>(sheet->addObject("ColorMapWidget"));
  // Initialize palette, by default the palette has 20 colors
  palette->setVLinear(0,1);         // Interpole linearly between 0 and 1
  palette->setNumberPrecision(2);           // Two digits are shown for labels
  palette->setTitle("Title");
  palette->setPrintWidth(15.0);     // Set the width in cm
  palette->setPrintHeight(2.0);     // Set the heigh in cm
  palette->setPrintAnchor(Point(5.0,10.0));
  sheet->showObject(palette);       // Show palette and finish docking to sheet
  sheet->autoResizeContent();       // adjust sheet's content
  \endcode  
*/

const QString ColorMapWidget::xmlColorMapWidgetTag="ColorMapWidget";

REGISTER_GRAPHICOBJECT(ColorMapWidget, ColorMapWidget::xmlColorMapWidgetTag,
                       QApplication::translate("ColorMapWidget", "Color map"),
                       QApplication::translate("ColorMapWidget", "&Color map"),
                       QApplication::translate("ColorMapWidget", "Ctrl+M"),
                       QApplication::translate("ColorMapWidget", "A color map"))
SYNONYM_GRAPHICOBJECT(ColorPaletteWidget, "ColorMapWidget")

ColorMapWidget::ColorMapWidget(QWidget *parent, Qt::WindowFlags f) :
    GraphicObject(parent, f)
{
  TRACE;

  AxisWindow * w=new AxisWindow(this);
  w->yAxis()->setEnabled(false);
  w->xAxis()->setTitle(tr("Title"));
  w->xAxis()->setObjectName("colorMapAxis");
  w->graphContent()->setGridLines(false);
  // Grab all mouse events
  w->xAxis()->installEventFilter(this);
  w->yAxis()->installEventFilter(this);
  w->graphContent()->installEventFilter(this);
  w->installEventFilter(this);
  connect(w->xAxis(), SIGNAL(orientationChanged()), this, SLOT(setOrientation()));
  w->xAxis()->setOrientationBlocked(false);
  w->yAxis()->setOrientationBlocked(false);

  _colorMapLayer=new ColorMapLayer(w);
  ColorMap pal;
  pal.generateColorScale(20, ColorPalette::Hsv);
  pal.setVLinear(0, 1);
  _colorMapLayer->setColorMap(pal);
  w->xAxis()->setRange(0,1);
  setOrientation(Axis::North);

  setPrintWidth(15);
  setPrintHeight(2);
}

ColorMapWidget::~ColorMapWidget()
{
  TRACE;
}

ColorMapWidget& ColorMapWidget::operator=(const ColorMapWidget& o)
{
  TRACE;
  _colorMapLayer->setColorMap(o._colorMapLayer->colorMap());
  _colorMapLayer->setAxisType(o._colorMapLayer->axisType());
  AxisWindow * w=_colorMapLayer->graph();
  AxisWindow * ow=o._colorMapLayer->graph();
  *w->xAxis()=*ow->xAxis();
  *w->yAxis()=*ow->yAxis();
  GraphicObject::operator=(o);
  emit changed(colorMap());
  return *this;
}

/*!
  Intercept resize events and forward them to its internal AxisWindow
*/
void ColorMapWidget::resizeEvent(QResizeEvent *)
{
  TRACE;
  _colorMapLayer->graph()->resize(size());
}

void ColorMapWidget::print(QPainter& p, double dotpercm, int x0Sheet, int y0Sheet, bool mask)
{
  AxisWindow * w=_colorMapLayer->graph();
  w->setPrintXAnchor(printXAnchor());
  w->setPrintYAnchor(printYAnchor());
  w->xAxis()->setSizeType(Axis::TotalSize);
  w->xAxis()->setSizeInfo(printWidth());
  w->yAxis()->setSizeType(Axis::TotalSize);
  w->yAxis()->setSizeInfo(printHeight());
  w->setPrintResolution(printResolution());
  w->print(p, dotpercm, x0Sheet, y0Sheet, mask);
}

/*!
  For some high resolution bitmap image, fonts must scaled
  
  Re-implentation to scale fonts in sub-widgets (axis and content)
*/
void ColorMapWidget::scaleFonts(FontScales& original, double scale)
{
  TRACE;
  return _colorMapLayer->graph()->scaleFonts(original,scale);
}

/*!
  Restore a saved scale for fonts
*/
void ColorMapWidget::restoreScaleFonts(const FontScales& original)
{
  TRACE;
  _colorMapLayer->graph()->restoreScaleFonts(original);
}

void ColorMapWidget::setColorMap(const ColorMap& pal)
{
  TRACE;
  static bool locked=false;
  if(!locked) {
    locked=true;
    _colorMapLayer->setColorMap(pal);
    // Adjust range of axis
    AxisWindow * w=_colorMapLayer->graph();
    Rect r=_colorMapLayer->boundingRect();
    if(_colorMapLayer->axisType()==XAxis) {
      w->xAxis()->setRange(r.x1(), r.x2());
    } else {
      w->yAxis()->setRange(r.y1(), r.y2());
    }
    w->deepUpdate();
    emit changed(pal);
    locked=false;
  }
}

const ColorMap& ColorMapWidget::colorMap() const
{
  TRACE;
  return _colorMapLayer->colorMap();
}

void ColorMapWidget::setColorCount(int n)
{
  ColorMap map;
  map.resize(n);
  _colorMapLayer->setColorMap(map);
  emit changed(map);
}

void ColorMapWidget::generateColorScale(int n, ColorPalette::Model m,
                                        bool reversed, int transparency)
{
  ColorMap map;
  map.generateColorScale(n, m, reversed, transparency);
  _colorMapLayer->setColorMap(map);
  emit changed(map);
}

void ColorMapWidget::generateGrayScale(int n, ColorPalette::Model m,
                                       bool reversed, int transparency)
{
  ColorMap map;
  map.generateGrayScale(n, m, reversed, transparency);
  _colorMapLayer->setColorMap(map);
  emit changed(map);
}

void ColorMapWidget::setVLinear(double min, double max)
{
  ColorMap map=_colorMapLayer->colorMap();
  map.setVLinear(min, max);
  _colorMapLayer->setColorMap(map);
  emit changed(map);
}

void ColorMapWidget::setVLog(double min, double max)
{
  ColorMap map=_colorMapLayer->colorMap();
  map.setVLog(min, max);
  _colorMapLayer->setColorMap(map);
  emit changed(map);
}

void ColorMapWidget::setAxis(const Axis& a)
{
  TRACE;
  if(orientation()!=a.orientation()) {
    setOrientation(a.orientation());
  }
  axis()=a;
}

Axis& ColorMapWidget::axis()
{
  TRACE;
  if(_colorMapLayer->axisType()==XAxis) {
    return *_colorMapLayer->graph()->xAxis();
  } else {
    return *_colorMapLayer->graph()->yAxis();
  }
}

const Axis& ColorMapWidget::axis() const
{
  TRACE;
  if(_colorMapLayer->axisType()==XAxis) {
    return *_colorMapLayer->graph()->xAxis();
  } else {
    return *_colorMapLayer->graph()->yAxis();
  }
}

/*!
  Set orientation of palette after a modification of the orientation of the current axis
*/
void ColorMapWidget::setOrientation()
{
  TRACE;
  double tmp;
  AxisWindow * w=_colorMapLayer->graph();
  if(_colorMapLayer->axisType()==XAxis) {
    switch(w->xAxis()->orientation()) {
    case Axis::West:
    case Axis::East:
      _colorMapLayer->setAxisType(YAxis);
      w->swapAxes();
      w->xAxis()->blockSignals(true);
      w->xAxis()->setOrientation(Axis::North);
      w->xAxis()->blockSignals(false);
      w->xAxis()->setZoomEnabled(false);
      tmp=printWidth();
      setPrintWidth(printHeight());
      setPrintHeight(tmp);
      emit sizeChanged();
    case Axis::South:
    case Axis::North:
      break;
    }
  } else {
    switch(w->yAxis()->orientation()) {
    case Axis::North:
    case Axis::South:
      _colorMapLayer->setAxisType(XAxis);
      w->swapAxes();
      w->yAxis()->blockSignals(true);
      w->yAxis()->setOrientation(Axis::East);
      w->yAxis()->blockSignals(false);
      w->yAxis()->setZoomEnabled(false);
      tmp=printWidth();
      setPrintWidth(printHeight());
      setPrintHeight(tmp);
      emit sizeChanged();
    case Axis::West:
    case Axis::East:
      break;
    }
  }
}

/*!
  Return a new mouse event with position converted to this object
*/
QMouseEvent * ColorMapWidget::convertMouseEvent(QObject * obj, QEvent * e, QWidget * dest)
{
  TRACE;
  QWidget * w=static_cast<QWidget *>(obj);
  QMouseEvent * me=static_cast<QMouseEvent *>(e);
  return new QMouseEvent(me->type(),
                         w->mapTo(dest, me->pos()),
                         me->button(),
                         me->buttons(),
                         me->modifiers());
}

bool ColorMapWidget::eventFilter(QObject * obj, QEvent * e)
{
  TRACE;
  QMouseEvent * me=nullptr;
  switch(e->type()) {
  case QEvent::MouseButtonDblClick:
    me=convertMouseEvent(obj, e, this);
    GraphicObject::mouseDoubleClickEvent(me);
    break;
  case QEvent::MouseButtonPress:
    // For left button event, an ignored event is not propagated to
    // parent group (if exists). Hence propagation to group is forced.
    if(static_cast<QMouseEvent *>(e)->button()==Qt::RightButton) {
      me=convertMouseEvent(obj, e, this);
      GraphicObject::mousePressEvent(me);
    } else {
      GraphicObject * g=group();
      me=convertMouseEvent(obj, e, g);
      QApplication::sendEvent(g, me);
    }
    break;
  case QEvent::MouseButtonRelease: {
      GraphicObject * g=group();
      me=convertMouseEvent(obj, e, g);
      QApplication::sendEvent(g, me);
    }
    break;
  case QEvent::MouseMove:
    if(obj==_colorMapLayer->graph()) {
      GraphicObject::mouseMoveEvent(static_cast<QMouseEvent *>(e));
      return true;
    } else {
      return false;
    }
  default:
    return false;
  }
  delete me;
  return true;
}

uint ColorMapWidget::_category=PropertyProxy::uniqueId();
uint ColorMapWidget::_tabColorMap=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void ColorMapWidget::addProperties(PropertyProxy * pp)
{
  TRACE;
  GraphicObject::addProperties(pp);
  if( !pp->setCurrentCategory(_category) ) {
    pp->addCategory(_category, tr("Color map"), QIcon( ":ColorMapWidget.png"));
  }
  if(pp->setCurrentTab(_tabColorMap)) {
    pp->addReference(this);
  } else {
    ColorMapProperties * w=new ColorMapProperties;
    pp->addTab(_tabColorMap, tr("Color map"), w, this);
  }
  axis().addProperties(pp);
}

/*!
  Cleanup property editor
*/
void ColorMapWidget::removeProperties(PropertyProxy * pp)
{
  TRACE;
  GraphicObject::removeProperties(pp);
  if(pp->setCurrentCategory(_category) ) {
    pp->removeTab(_tabColorMap, this);
    axis().removeProperties(pp);
  }
}

void ColorMapWidget::properties(PropertyWidget * w) const
{
  if(w->id()==_tabColorMap) {
    w->setValue(ColorMapProperties::Map, QVariant::fromValue(colorMap()));
  } else {
    GraphicObject::properties(w);
  }
}

void ColorMapWidget::setProperty(uint wid, int pid, QVariant val)
{
  if(wid==_tabColorMap) {
    switch(pid) {
    case ColorMapProperties::Map:
      setColorMap(val.value<ColorMap>());
      emit changed(colorMap());
      _colorMapLayer->graphContent()->deepUpdate();
      break;
    default:
      break;
    }
  } else {
    GraphicObject::setProperty(wid, pid, val);
  }
}

/*!
  Forward deep update to internal graph
*/
void ColorMapWidget::deepUpdate()
{
  TRACE;
  _colorMapLayer->graph()->deepUpdate();
  if(transparentMask()) updateMask();
}

/*!
  Forward update to internal graph
*/
void ColorMapWidget::update()
{
  TRACE;
  _colorMapLayer->graph()->update();
  if(transparentMask()) updateMask();
}

void ColorMapWidget::load(QString fileName)
{
  TRACE;
  if(fileName.length()>0) {
    XMLErrorReport xmler(XMLErrorReport::Read);
    xmler.setTitle(tr("Load palette"));
    xmler.setFileName(fileName);
    XMLSciFigs s;
    ColorMap pal;
    if(xmler.exec(s.restoreFile(fileName,&pal,XMLSciFigs::Data))) {
      setColorMap(pal);
    }
  }
}

void ColorMapWidget::save(QString fileName)
{
  TRACE;
  if(fileName.length()>0) {
    XMLSciFigs s;
    s.saveFile(fileName,&colorMap(),XMLSciFigs::Data);
  }
}

void ColorMapWidget::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    colorMap().xml_save(s, context);
  }
  axis().xml_save(s, context); // Axis must be save after. If not the automatic axis
                               // will override the axis settings upon load.
}

XMLMember ColorMapWidget::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    if(tag=="ColorMap") return XMLMember(new ColorMap, true);
  }
  if(tag=="Axis") {
    return XMLMember(&axis());
  }
  return GraphicObject::xml_member(tag, attributes, context);
}

void ColorMapWidget::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  if(child->xml_tagName()=="ColorMap") {
    ColorMap * map=static_cast<ColorMap *>(child);
    setColorMap(*map);
    emit changed(colorMap());
  } else if(child->xml_tagName()=="Axis") {
    double tmp;
    switch(axis().orientation()) {
    case Axis::West:
    case Axis::East:
      tmp=printWidth();
      setPrintWidth(printHeight());
      setPrintHeight(tmp);
      emit sizeChanged();
    case Axis::South:
    case Axis::North:
      break;
    }
    // Only one axis is restored but the hidden Axiswindow must be polish
    // (e.g. visible range)
    _colorMapLayer->graph()->xml_polish(context);
  }
}

} // namespace SciFigs
