/***************************************************************************
**
**  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: 2005-12-23
**  Copyright: 2005-2019
**    Marc Wathelet
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "XYValueLines.h"
#include "XMLSciFigs.h"
#include "XYColorLinesProperties.h"
#include "AxisWindow.h"
#include "GraphContentLayerFactory.h"
#include "ColorMapProperties.h"
#include "LayerPainterRequest.h"
#include "LayerLocker.h"

namespace SciFigs {

/*!
  \class XYValueLines XYValueLines.h
  \brief A XYValueLines is a layer to draw polylines of color (static)
*/

const QString XYValueLines::xmlXYValueLinesTag="XYValueLines";

REGISTER_GRAPHCONTENTLAYER(XYValueLines, "XYValueLines")

XYValueLines::XYValueLines(AxisWindow * parent, bool autoDelete) :
    XYColorLines(parent, autoDelete)
{
  TRACE;
  _values=nullptr;
  _colorMap.generateColorScale(20, ColorPalette::Hsv);
}

XYValueLines::~XYValueLines()
{
  TRACE;
  if(_autoDelete) {
    delete [] _values;
  }
}

/*!
  Set the palette. Safe for painting threads.
*/
void XYValueLines::setColorMap(const ColorMap& map)
{
  TRACE;
  // Avoid recurrent loop
  static bool locked=false;
  if(!locked) {
    locked=true;
    LayerLocker ll(this);
    _colorMap=map;
    // This layer takes generally time to repaint (useless to repaint only this layer) 
    graphContent()->deepUpdate();
    emit colorMapChanged(map);
    locked=false;
  }
}

/*!
  Set a linear scale on palette. Safe for painting threads. For other modification of the palette
  see setColorMap().
*/
void XYValueLines::setLinearColorMap(double minVal, double maxVal)
{
  TRACE;
  LayerLocker ll(this);
  _colorMap.setVLinear(minVal, maxVal);
  // This layer takes generally time to repaint (useless to repaint only this layer) 
  graphContent()->deepUpdate();
  emit colorMapChanged(colorMap());
}

/*!
  Set the internal list of values to \a values. If the object has been created with autodelete, this object takes the
  ownership of \a values.
*/
void XYValueLines::setValues(double * values)
{
  TRACE;
  LayerLocker ll(this);
  if(_autoDelete) {
    delete [] _values;
  }
  _values=values;
}

/*!
  Convenience function to set the internal list of values from a dynamic vector of values.
  Returns the internal allocated list of values. Ownership depends upon the constructor of this object.
*/
double * XYValueLines::setValues(const QVector<double>& values)
{
  TRACE;
  LayerLocker ll(this);
  if(_autoDelete) {
    delete [] _values;
  }
  ASSERT(_curveCount==values.count());
  _values=new double[_curveCount];
  for(int i=0; i<_curveCount; i++) {
    _values[i]=values[i];
  }
  return _values;
}

uint XYValueLines::_tabColorMap=PropertyProxy::uniqueId();

/*!
  Setup property editor
*/
void XYValueLines::addProperties(PropertyProxy * pp)
{
  TRACE;
  XYColorLines::addProperties(pp);
  if(pp->setCurrentTab(_tabColorMap)) {
    pp->addReference(this);
  } else {
    ColorMapProperties * w=new ColorMapProperties;
    pp->addTab(_tabColorMap, tr("Color map"), w, this);
  }
}

/*!
  Clean property editor
*/
void XYValueLines::removeProperties(PropertyProxy * pp)
{
  TRACE;
  XYColorLines::removeProperties(pp);
  pp->removeTab(_tabColorMap, this);
}


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

void XYValueLines::setProperty(uint wid, int pid, QVariant val)
{
  if(wid==_tabColorMap) {
    switch(pid) {
    case ColorMapProperties::Map:
      setColorMap(val.value<ColorMap>());
      break;
    default:
      break;
    }
  } else {
    XYColorLines::setProperty(wid, pid, val);
  }
}

void XYValueLines::paintData(const LayerPainterRequest& lp, QPainter& p, double dotpercm) const
{
  TRACE;
  if( !_curveCount) return ;
  const ColorMap map=_colorMap; // Copy to avoid disturbance during paint.
  const GraphContentOptions& gc=lp.options();
  int height=lp.size().height();
  Point2D *p1=_points-1, *p2;
  int lineWeight=qRound(_lineWeight*dotpercm);
  if(lineWeight<1) lineWeight=1;
  QPen currentPen(Qt::black, lineWeight, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
  lp.graphContent()->setProgressMaximum(_curveCount);
  QColor col;
  if(_signChangeThreshold==0.0) {
    for(int i=0; i<_curveCount; i++) {
      if(lp.terminated()) return;
      lp.graphContent()->setProgressValue(i);
      int countPoints=_pointCounts[i];
      if(countPoints>0) {
        if(_values) {
          guiColor(map.color(_values[i]), col);
          currentPen.setColor(col);
        }
        p.setPen(currentPen);
        p1++;
        drawCurve(countPoints, p, gc, height, p1, p2);
      }
    }
  } else {
    for(int i=0; i<_curveCount; i++) {
      if(lp.terminated()) return;
      lp.graphContent()->setProgressValue(i);
      int countPoints=_pointCounts[i];
      if(countPoints>0) {
        if(_values) {
          guiColor(map.color(_values[i]), col);
          currentPen.setColor(col);
        }
        p.setPen(currentPen);
        p1++;
        drawCurveSignThreshold(countPoints, p, gc, height, p1, p2);
      }
    }
  }
  lp.graphContent()->setProgressValue(_curveCount);
}

/*!
  Get min and maximum of values (usually to adjust the palette).
  It is the responsability of the caller to initialize min and max to std::numeric_limits<double>::infinity() and -std::numeric_limits<double>::infinity() respectively
*/
void XYValueLines::valueRange(double& min, double& max) const
{
  TRACE;
  if(_curveCount==0) return;
  if(_values[ 0 ] < min) min=_values[ 0 ];
  if(_values[ 0 ] > max) max=_values[ 0 ];
  for(int i=1;i < _curveCount;i++ ) {
    if(_values[ i ] < min) min=_values[ i ];
    else if(_values[ i ] > max) max=_values[ i ];
  }
}

void XYValueLines::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    _colorMap.xml_save(s, context);
  }
}

XMLMember XYValueLines::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) {
    if(tag=="ColorMap" ||
       tag=="ColorPalette") {    // Kept for compatibility
      return XMLMember(new ColorMap, true);
    }
  }
  return XYColorLines::xml_member(tag, attributes, context);
}

void XYValueLines::xml_polishChild(XML_POLISHCHILD_ARGS)
{
  Q_UNUSED(context)
  if(child->xml_tagName()=="ColorMap") {
    ColorMap * map=static_cast<ColorMap *>(child);
    setColorMap(*map);
  }
}

void XYValueLines::xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const
{
  TRACE;
  XYColorLines::xml_writeBinaryData(s, context);
  s.writeRawData(( const char * ) _values, sizeof(double) * _curveCount);
}

bool XYValueLines::xml_setBinaryData(XML_SETBINARYDATA_ARGS)
{
  TRACE;
  s >> _curveCount;
  if(_curveCount > 0) {
    _autoDelete=true;
    bool hasColor;
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    Version v(scifigsContext->currentFileVersion());
    static const Version vRef(1, 0, 99);
    if(v>vRef) {
      s >> hasColor;
    } else {
      hasColor=false;
    }
    _pointCounts=new int[ _curveCount ];
    s.readRawData(( char * ) _pointCounts, sizeof(int) * _curveCount);
    // Count number of points
    int n=0;
    for(int i=0;i < _curveCount;i++ ) {
      if(_pointCounts[ i ]<0) _pointCounts[ i ]=0;
      n += _pointCounts[ i ];
    }
    if(n>0) {
      _points=new Point2D [ n ];
      s.readRawData(( char * ) _points, sizeof(Point2D) * n);
      _values=new double[ _curveCount ];
      s.readRawData(( char * ) _values, sizeof(double) * _curveCount);
    }
  }
  return true;
}

} // namespace SciFigs
