/***************************************************************************
**
**  This file is part of dinver.
**
**  dinver 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.
**
**  dinver 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 Foobar.  If not, see <http://www.gnu.org/licenses/>
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2004-10-26
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <QTabWidget>
#include <QLabel>
#include <QLineEdit>
#include <QComboBox>

#include <DinverCore.h>
#include <QGpGuiTools.h>
#include <SciFigs.h>
#include "NAModelsPlot.h"
#include "PSViewer.h"
#include "NAModelsPlotProperties.h"

/*!
  \class NAModelsPlot NAModelsPlot.h
  \brief A NAModelsPlot is a layer to draw projection of a parameter space

  No data is directly stored in this object. It access directly to models generated by NA engine.

  The layer is exported either as a NAModelsPlot (for psViewer only) or as a XYValuePlot (available in Figue)

*/

const QString NAModelsPlot::xmlNAModelsPlotTag="NAModelsPlot";

REGISTER_GRAPHCONTENTLAYER(NAModelsPlot, "NAModelsPlot" );

NAModelsPlot::NAModelsPlot(AxisWindow * parent) :
    GraphContentLayer(parent)
{
  TRACE;
  _psViewer=0;
  if(parent) setParent(graphContent());

  _size=0.1;
  _paramX=-1;
  _paramY=0;
}

NAModelsPlot::~NAModelsPlot()
{
  TRACE;
}

void NAModelsPlot::setParent(GraphContent * parent)
{
  TRACE;
  if(parent) {
    AxisWindow * w=parent->graph();
    if(w->sheet()) {
      _psViewer=qobject_cast<PSViewer *>(w->sheet()->parentWidget());
    }
  }
  GraphContentLayer::setParent(parent);
}

void NAModelsPlot::paintData(const LayerPainterRequest& lp, QPainter& p, double dotpercm) const
{
  TRACE;
  _psViewer->lockDataModels();
  const GraphContentOptions& gc=lp.options();
  ColorMap map=*_psViewer->colorMap();

  double pointSizeF=_size * dotpercm;
  int pointSize, halfSize;
  if(pointSizeF<2.0) {
    pointSize=2;
    halfSize=1;
  } else {
    pointSize=(int) (pointSizeF);
    halfSize=(int) (0.5 * pointSizeF);
  }
  double x, y, misfit;
  ModelThreadList::const_iterator it;
  ModelThreadList::const_iterator itEnd=_psViewer->modelList().end();
  QColor col;
  for(it=_psViewer->modelList().begin();it!=itEnd;++it) {
    if(lp.terminated()) {
      _psViewer->unlockDataModels();
      return;
    }
    paramValue(x, y, misfit, it);
    if(x > gc.xVisMin() && x < gc.xVisMax()) {
      if(y > gc.yVisMin() && y < gc.yVisMax()) {
        guiColor(map.color(misfit), col);
        p.setPen(col);
        p.setBrush(col);
        int orx=gc.xr2s(x)-halfSize;
        int ory=gc.yr2s(y)-halfSize;
        p.drawEllipse(orx, ory, pointSize, pointSize);
      }
    }
  }
  _psViewer->unlockDataModels();
}

inline void NAModelsPlot::paramValue(double& x, double& y, double& misfit, ModelThreadList::const_iterator it) const
{
  TRACE;
  const ModelThreadInfo & modelInfo=*it;
  const InversionThread& t=*modelInfo.thread();
  misfit=t.misfit(modelInfo.index());
  if(_paramX >= 0)
    x=t.variableParameterValue(modelInfo.index(), _paramX);
  else
    x=misfit;
  if(_paramY >= 0)
    y=t.variableParameterValue(modelInfo.index(), _paramY);
  else
    y=misfit;
}

Rect NAModelsPlot::boundingRect() const
{
  TRACE;
  if(_psViewer->modelList().size() > 0) {
    _psViewer->lockDataModels();
    ModelThreadList::const_iterator it=_psViewer->modelList().begin();
    ModelThreadList::const_iterator itEnd=_psViewer->modelList().end();
    Rect r;
    if(it!=itEnd) {
      double x, y, misfit;
      paramValue(x, y, misfit, it);
      r.setLimits(x, y, x, y);
      for( ++it;it!=itEnd;++it) {
        paramValue(x, y, misfit, it);
        r.add(x, y);
      }
    }
    _psViewer->unlockDataModels();
    return r;
  } else return Rect();
}

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

/*!
  Setup property editor
*/
void NAModelsPlot::addProperties(PropertyProxy * pp)
{
  TRACE;
  if(pp->setCurrentTab(_tab)) {
    pp->addReference(this);
  } else {
    NAModelsPlotProperties * w=new NAModelsPlotProperties;
    w->setParameterList(_psViewer->parameterList());
    pp->addTab(_tab, tr("Parameters"), w, this);
  }
}

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

void NAModelsPlot::properties(PropertyWidget * w) const
{
  TRACE;
  w->setValue(NAModelsPlotProperties::DotDiameter, dotDiameterMM());
  w->setValue(NAModelsPlotProperties::ParamX, paramX()+1);
  w->setValue(NAModelsPlotProperties::ParamY, paramY()+1);
}

void NAModelsPlot::setProperty(uint, int pid, QVariant val)
{
  TRACE;
  bool axisUpdate=false;
  switch(pid) {
  case NAModelsPlotProperties::DotDiameter:
    setDotDiameterMM(val.toDouble());
    graphContent() ->deepUpdate();
    break;
  case NAModelsPlotProperties::ParamX:
    setParamX(val.toInt()-1);
    axisUpdate=true;
    break;
  case NAModelsPlotProperties::ParamY:
    setParamY(val.toInt()-1);
    axisUpdate=true;
    break;
  }
  if(axisUpdate) {
    // force update here because it affects the axis as well (and set limits again)
    Rect r=boundingRect();
    graph()->xAxis()->setRange(r.x1(), r.x2());
    graph()->yAxis()->setRange(r.y1(), r.y2());
    graph() ->deepUpdate();
  }
}

/*!
  px may range from -1 to the number of available parameters

  -1 means misfit
*/
void NAModelsPlot::setParamX(int px)
{
  TRACE;
  LayerLocker ll(this);
  _paramX=px;
  const InversionThread * paramSet=_psViewer->parameterList();
  Axis& axis=*graphContent()->graph() ->xAxis();
  QString str;
  if(px>=0) {
    const Parameter & param=*paramSet->variableParameter(px);
    // TODO BRANCH 3.4: add a function and call it from setParamX and Y
    if(param.unit().isEmpty()) {
      str=param.name();
    } else {
      str=QString("%1 (%2)").arg(param.name()).arg(param.unit());
    }
    axis.setRange(param.minimum(), param.maximum());
  } else {
    str="Misfit";
    axis.setScaleType(Scale::Log);
    axis.setAutoTicks(false);
    axis.setMajorTicks(10.0);
    axis.setMinorTicks(2.0);
  }
  axis.setTitle(str);
}

/*!
  py may range from -1 to the number of available parameters

  -1 means misfit
*/
void NAModelsPlot::setParamY(int py)
{
  TRACE;
  LayerLocker ll(this);
  _paramY=py;
  const InversionThread * paramSet=_psViewer->parameterList();
  Axis& axis=*graphContent()->graph()->yAxis();
  QString str;
  if(py >= 0) {
    const Parameter & param=*paramSet->variableParameter(py);
    // TODO BRANCH 3.4: add a function and call it from setParamX and Y
    if(param.unit().isEmpty()) {
      str=param.name();
    } else {
      str=QString("%1 (%2)").arg(param.name()).arg(param.unit());
    }
    axis.setRange(param.minimum(), param.maximum());
  } else {
    str="Misfit";
    axis.setScaleType(Scale::Log);
    axis.setAutoTicks(false);
    axis.setMajorTicks(10.0);
    axis.setMinorTicks(2.0);
  }
  axis.setTitle(str);
}

bool NAModelsPlot::saveParameters() const
{
  TRACE;
  return _psViewer->saveParameters();
}

const QString& NAModelsPlot::xml_tagName() const
{
  TRACE;
  if(saveParameters())
    return xmlNAModelsPlotTag;
  else
    return XYValuePlot::xmlXYValuePlotTag;
}

void NAModelsPlot::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  if(saveParameters()) {
    writeProperty(s, "paramX", paramX());
    writeProperty(s, "paramY", paramY());
  }
  GraphContentLayer::xml_writeProperties(s, context);
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data() && !saveParameters()) writeBinaryData(s, context);
}

void NAModelsPlot::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  XMLSciFigs * scifigsContext=static_cast<XMLSciFigs *>(context);
  if(scifigsContext->data() && !saveParameters()) {
    _psViewer->colorMap()->xml_save(s, context);
  }
}

void NAModelsPlot::xml_writeBinaryData(XML_WRITEBINARYDATA_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  _psViewer->lockDataModels();
  ModelThreadList::const_iterator it;
  ModelThreadList::const_iterator itEnd=_psViewer->modelList().end();
  int n=_psViewer->modelList().count();
  s << n;

  if(_paramX>=0) {
    int p=_paramX;
    for(it=_psViewer->modelList().begin();it!=itEnd;++it) {
      const ModelThreadInfo& modelInfo=*it;
      s << modelInfo.thread()->variableParameterValue(modelInfo.index(),p);
    }
  } else {
    for(it=_psViewer->modelList().begin();it!=itEnd;++it) {
      const ModelThreadInfo& modelInfo=*it;
      s << modelInfo.thread()->misfit(modelInfo.index());
    }
  }

  if(_paramY>=0) {
    int p=_paramY;
    for(it=_psViewer->modelList().begin();it!=itEnd;++it) {
      const ModelThreadInfo& modelInfo=*it;
      s << modelInfo.thread()->variableParameterValue(modelInfo.index(),p);
    }
  } else {
    for(it=_psViewer->modelList().begin();it!=itEnd;++it) {
      const ModelThreadInfo& modelInfo=*it;
      s << (*it).thread()->misfit(modelInfo.index());
    }
  }

  for(it=_psViewer->modelList().begin();it!=itEnd;++it) {
    const ModelThreadInfo& modelInfo=*it;
    s << (*it).thread()->misfit(modelInfo.index());
  }

  _psViewer->unlockDataModels();
}

XMLMember NAModelsPlot::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(saveParameters()) {
    if(tag=="paramX") return XMLMember(0);
    else if(tag=="paramY") return XMLMember(1);
  }
  return XMLMember(XMLMember::Unknown);
}


bool NAModelsPlot::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 0: setParamX(content.toInt()); return true;
  case 1: setParamY(content.toInt()); return true;
  }
  return false;
}
