/***************************************************************************
**
**  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: 2002-05-28
**  Copyright: 2002-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "XYColorLines.h"
#include "XMLSciFigs.h"
#include "XYColorLinesProperties.h"
#include "AxisWindow.h"
#include "GraphContentLayerFactory.h"
#include "LayerPainterRequest.h"
#include "LayerLocker.h"

namespace SciFigs {

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

const QString XYColorLines::xmlXYColorLinesTag="XYColorLines";

REGISTER_GRAPHCONTENTLAYER(XYColorLines, "XYColorLines")

XYColorLines::XYColorLines(AxisWindow * parent, bool autoDelete) :
    GraphContentLayer(parent)
{
  TRACE;
  _autoDelete=autoDelete;
  _curveCount=0;
  _pointCounts=nullptr;
  _points=nullptr;
  _colors=nullptr;
  _lineWeight=0.1;
  _signChangeThreshold=0.0;
  _propertiesTab=nullptr;
}

XYColorLines::~XYColorLines()
{
  TRACE;
  clear();
}

void XYColorLines::clear()
{
  TRACE;
  delete [] _points;
  if(_autoDelete) {
    delete [] _pointCounts;
    delete [] _colors;
    _pointCounts=nullptr;
    _colors=nullptr;
  }
  _points=nullptr;
  _curveCount=0;
}

void XYColorLines::setLineWeight (double lw)
{
  TRACE;
  LayerLocker ll(this);
  _lineWeight=lw;
}

void XYColorLines::setColor(int index, const Color& col)
{
  TRACE;
  LayerLocker ll(this);
  _colors[index]=col;
}

void XYColorLines::setColors(Color * col)
{
  TRACE;
  LayerLocker ll(this);
  delete _colors;
  _colors=col;
}

uint XYColorLines::_tab=PropertyProxy::uniqueId();

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

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

void XYColorLines::properties(PropertyWidget * w) const
{
  TRACE;
  w->setValue(XYColorLinesProperties::LineWeight, lineWeightMM());
  w->setValue(XYColorLinesProperties::SignChangeThreshold, signChangeThreshold());
}

void XYColorLines::setProperty(uint, int pid, QVariant val)
{
  TRACE;
  switch(pid) {
  case XYColorLinesProperties::LineWeight:
    setLineWeight(val.toDouble()*0.1);
    graphContent()->deepUpdate();
    break;
  case XYColorLinesProperties::SignChangeThreshold:
    setSignChangeThreshold(val.toDouble());
    graphContent()->deepUpdate();
    break;
  default:
    break;
  }
}

/*!
  This is the unique function you have to call to populate this layer with lines.
  Never call points() before calling this function.
*/
void XYColorLines::setPointCount(int nPoints, int nCurves, int * pointCounts)
{
  TRACE;
  LayerLocker ll(this);
  clear();
  _curveCount=nCurves;
  _pointCounts=pointCounts;
  if(nPoints>0) {
    _points=new Point2D[nPoints];
  } else {
    _points=nullptr;
  }
}

/*!
  Convenience function to set the internal list of point counts from a dynamic vector of int
*/
void XYColorLines::setPointCount(int nPoints, const QVector<int>& pointCounts)
{
  TRACE;
  LayerLocker ll(this);
  clear();
  _curveCount=pointCounts.count();
  if(_curveCount>0) {
    _pointCounts=new int[_curveCount];
    for(int i=0; i<_curveCount; i++) {
      _pointCounts[i]=pointCounts[i];
    }
  }
  if(nPoints>0) {
    _points=new Point2D[nPoints];
  } else {
    _points=nullptr;
  }
}

/*!
  Convenience function to set the internal list of points from a dynamic vector of points

  \sa setPointCount(int nPoints, const QVector<int>& pointCounts)
*/
void XYColorLines::setPoints(const QVector<Point2D>& points)
{
  TRACE;
  int n=points.count();
  LayerLocker ll(this);
  for(int i=0; i<n; i++) {
    _points[i]=points[i];
  }
}

void XYColorLines::paintData(const LayerPainterRequest& lp, QPainter& p, double dotpercm) const
{
  TRACE;
  if( !_curveCount) return ;
  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);
  QColor col;
  if(_signChangeThreshold==0.0) {
    for(int i=0; i<_curveCount; i++) {
      if(lp.terminated()) return;
      int countPoints=_pointCounts[i];
      if(countPoints>0) {
        if(_colors) {
          guiColor(_colors[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;
      int countPoints=_pointCounts[i];
      if(countPoints>0) {
        if(_colors) {
          guiColor(_colors[i], col);
          currentPen.setColor(col);
        }
        p.setPen(currentPen);
        p1++;
        drawCurveSignThreshold(countPoints, p, gc, height, p1, p2);
      }
    }
  }
}

Rect XYColorLines::boundingRect() const
{
  TRACE;
  if(_curveCount==0) return Rect();
  Point2D *p=_points;
  Rect r(p->x(), p->y(), p->x(), p->y());
  for(int i=0;i < _curveCount;i++ ) {
    int countPoints=_pointCounts[ i ];
    for(int j=0;j < countPoints;j++ ) r.add( *(p++ ));
  }
  return r;
}

/*!
  When the user select curve using the mouse pointer
*/
bool XYColorLines::trackRectangle(int id, double rx1, double ry1, double rx2, double ry2, Qt::KeyboardModifiers)
{
  TRACE;
  // Select the points inside the rectangle and call editData(QVector * indexList)
  // This trackRectangle function can redefined in sub-classes
  Rect r(rx1, ry1, rx2, ry2);
  //printf("Edit rectangle: %lg %lg %lg %lg\n",rx1,ry1,rx2,ry2);
  QVector<int> * indexList=new QVector<int>;
  Point2D *p1=_points-1, *p2;
  for(int i=0; i<_curveCount; i++) {
    int countPoints=_pointCounts[i];
    if(countPoints>0) {
      p1++;
      //printf("Curve %i: \np1 %lg, %lg\n",i,p1->x(),p1->y());
      for(int j=1;j < countPoints;j++ ) {
        p2=p1 + 1;
        //printf("p2 %lg, %lg\n",p2->x(),p2->y());
        if(r.includes( *p1, *p2) ) {
          indexList->push_back(i);
          //printf("%i included!\n",i);
          p1 += countPoints - j;
          break;
        }
        p1=p2;
      }
    }
  }
  if(indexList->count() > 0) emit dataSelected(id, indexList);
  delete indexList;
  return true;
}

void XYColorLines::mulYbyX(double constant)
{
  TRACE;
  Point2D * p=_points;
  LayerLocker ll(this);
  for(int i=0;i < _curveCount;i++ ) {
    int countPoints=_pointCounts[ i ];
    for(int j=0;j < countPoints;j++ ) {
      p->setY(p->y() * constant * p->x());
      p++;
    }
  }
}

void XYColorLines::divYbyX(double constant)
{
  TRACE;
  Point2D * p=_points;
  LayerLocker ll(this);
  for(int i=0;i < _curveCount;i++ ) {
    int countPoints=_pointCounts[ i ];
    for(int j=0;j < countPoints;j++ ) {
      p->setY(p->y()/(constant * p->x()) );
      p++;
    }
  }
}

void XYColorLines::setColor(int curveIndex, int red, int green, int blue)
{
  TRACE;
  if(curveIndex<0 || curveIndex>=_curveCount || !_colors) return ;
  LayerLocker ll(this);
  _colors[curveIndex].setRgba(red, green, blue);
}

void XYColorLines::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  GraphContentLayer::xml_writeProperties(s, context);
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data()) writeBinaryData(s, context);
}

void XYColorLines::xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  s << _curveCount;
  XUniqueYColorData::saveColors(s, _colors, _curveCount);
  // Count number of points
  int n=0;
  s.writeRawData((const char *) _pointCounts, sizeof(int)*_curveCount);
  for(int i=0; i<_curveCount; i++) n+=_pointCounts[i];
  s.writeRawData((const char *) _points, sizeof(Point2D)*n);
}

bool XYColorLines::xml_setBinaryData(XML_SETBINARYDATA_ARGS)
{
  TRACE;
  s >> _curveCount;
  if(_curveCount>0) {
    _autoDelete=true;
    XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
    Version v(scifigsContext->currentFileVersion());
    XUniqueYColorData::restoreColors(s, _colors, _curveCount, &v);
    _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);
    }
  }
  return true;
}

bool XYColorLines::xml_setBinaryData200411(XML_SETBINARYDATA_ARGS)
{
  TRACE;
  s.setByteOrder(QDataStream::LittleEndian);
  bool ret=xml_setBinaryData(s, context);
  s.setByteOrder(QDataStream::BigEndian);
  return ret;
}

} // namespace SciFigs
