/***************************************************************************
**
**  This file is part of DinverDCCore.
**
**  DinverDCCore 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.
**
**  DinverDCCore 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-18
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include <DinverCore.h>
#include <QGpCoreTools.h>
#include "ParamProfile.h"
#include "ParamLayer.h"

namespace DinverDCCore {

const QString ParamProfile::xmlParamProfileTag="ParamProfile";

ParamProfile::ParamProfile()
{
  TRACE;
  _defaultMinimum=100;
  _defaultMaximum=5000;
  _type=Param;
  _defaultCondition=SimpleCondition::LessThan;
}

/*!
  Copy constructor. Used only by copy constructor of ParamGroundModel.
*/
ParamProfile::ParamProfile(const ParamProfile& o)
  : XMLClass()
{
  TRACE;
  _longName=o._longName;
  _shortName=o._shortName;
  _unit=o._unit;
  _defaultMinimum=o._defaultMinimum;
  _defaultMaximum=o._defaultMaximum;
  _defaultCondition=o._defaultCondition;
  _type=o._type;

  LayerList::const_iterator it;
  for(it=o._layers.begin();it!=o._layers.end();++it) {
    ParamLayer * layer=new ParamLayer( **it);
    layer->setProfile(this);
    _layers.append(layer);
  }
}

ParamProfile::ParamProfile(QString shortName, QString longName, QString unit,
                                  double defaultMinimum, double defaultMaximum,
                                  Type type, SimpleCondition::Type defaultCondition)
{
  TRACE;
  _longName=longName;
  _shortName=shortName;
  _unit=unit;
  _defaultMinimum=defaultMinimum;
  _defaultMaximum=defaultMaximum;
  _type=type;
  _defaultCondition=defaultCondition;
}

ParamProfile::~ParamProfile()
{
  TRACE;
  LayerList::iterator it;
  for(it=_layers.begin();it!=_layers.end();++it) delete *it;
}

bool ParamProfile::hasGradient() const
{
  TRACE;
  LayerList::const_iterator it;
  for(it=_layers.begin();it!=_layers.end();++it) {
    if((*it)->shape()!=ParamLayer::Uniform) return true;
  }
  return false;
}

/*!
  Inserts \a layer at index \a at. Never call this function directly,
  use ParamGroundModel::insertLayer() instead.
*/
void ParamProfile::insertLayer(int at, ParamLayer * layer)
{
  TRACE;
  _layers.insert(at, layer);
  int nLayers=_layers.size();
  for(int iLayer=at+1; iLayer<nLayers; iLayer++) {
    ParamLayer * layer=_layers.at(iLayer);
    layer->setIndex(iLayer);
  }
}

/*!
  Append all layers (except half spaces) to layer map \a links.
*/
void ParamProfile::collectDepthLinks(LayerMap& links)
{
  TRACE;
  int lastLayer=_layers.size() - 1; // half space index
  for(int iLayer=0; iLayer<lastLayer; iLayer++) {
    ParamLayer * layer=_layers.at(iLayer);
    links.insert(layer->name(), layer);
  }
}

/*!
  Set the link pointers from the link names and from the layer map \a links.
*/
void ParamProfile::setDepthLinks(const LayerMap& links)
{
  TRACE;
  int lastLayer=_layers.size() - 1; // half space index
  for(int iLayer=0; iLayer<lastLayer; iLayer++) {
    ParamLayer * layer=_layers.at(iLayer);
    if(!layer->linkedTo().isEmpty()) {
      layer->setPtrLink(links[layer->linkedTo()]);
    }
  }
}

/*!
  Set link names in each layer from the link pointers.
*/
void ParamProfile::setDepthLinkNames()
{
  TRACE;
  int lastLayer=_layers.size() - 1; // half space index
  for(int iLayer=0; iLayer<lastLayer; iLayer++) {
    ParamLayer * layer=_layers.at(iLayer);
    layer->setLinkedTo();
  }
}

/*!
  Append all value parameters that describe this profile to \a params.
*/
void ParamProfile::collectValueParameters(QList<Parameter *>& params) const
{
  TRACE;
  int lastLayer=_layers.count() - 1;
  for(int iLayer=0; iLayer<lastLayer; iLayer++ ) {
    ParamLayer * layer=_layers.at(iLayer);
    layer->collectValueParameters(params);
  }
  _layers.at(lastLayer)->collectValueParameters(params);
}

bool ParamProfile::toParam(RealSpace& ps, LayerMap& links, const ParamGroundModel * groundModel)
{
  TRACE;
  int nLayers=_layers.size();
  int lastLayer=nLayers-1; // half space index
  ParamLayer * layer;
  ParamLayer * prevLayer=nullptr;
  for(int iLayer=0; iLayer<nLayers; iLayer++) {
    layer=_layers.at(iLayer);
    if(layer->linkedTo()!="Not linked" && iLayer!=lastLayer) {
      // Links may be cascaded, look carefully for circular references
      LayerList browsedLinks;
      ParamLayer * link=layer;
      while(link && link->linkedTo()!="Not linked") {
        if(browsedLinks.contains(link)) {
          App::log(tr("Circular referecences for depth links\n") );
          LayerList::iterator it;
          for(it=browsedLinks.begin();it!=browsedLinks.end();++it) {
            App::log(tr("        %1 ---> %2\n").arg((*it)->name()).arg((*it)->linkedTo()) );
          }
          return false;
        } else {
          browsedLinks.append(link);
        }
        LayerMap::iterator itMap=links.find(link->linkedTo());
        if(itMap!=links.end()) {
          link=itMap.value();
        } else {
          link=nullptr;
        }
      }
      if(!link) {
        App::log(tr("Bad depth link %1 for parameter %2\n").arg(layer->linkedTo()).arg(layer->name()));
        return false;
      } else {
        layer->setPtrLink(link);
      }
    } else {
      layer->setPtrLink(nullptr);
    }
    // Convert layer into parameters
    if(_type==Param) {
      if(!layer->valueParameters(ps, groundModel)) {
        App::log(tr("Error initializing parameter %1\n").arg(layer->name()) );
        return false;
      }
    } else {
      layer->setFinalProfileValuesCondition(_minRaw, _maxRaw);
    }
    if(!layer->depthParameters(iLayer==lastLayer, prevLayer, ps, groundModel)) {
      App::log(tr("Error initializing parameter %1\n").arg(layer->name()) );
      return false;
    }
    prevLayer=layer;
  }
  return true;
}

/*!
  Delayed resolution of linked depths
  Needed by profiles for checking and adding condition for depth (across linked depths).
*/
void ParamProfile::setLinkedDepth(RealSpace& ps)
{
  TRACE;
  ParamLayer * prevLayer=nullptr;
  ParamLayer * layer;
  int lastLayer=_layers.size() - 1;
  int iLayer=0;
  for(LayerList::iterator it=_layers.begin();it!=_layers.end();++it, iLayer++ ) {
    layer=*it;
    layer->setLinkedDepth(iLayer==lastLayer, prevLayer, ps);
    prevLayer=layer;
  }
}


/*!
  Conditions between layers and for gradients
  Delayed after resolution of linked depts.
*/
bool ParamProfile::setValueConditions(RealSpace& ps)
{
  TRACE;
  if(_type==Param) {
    int nLayers=_layers.size();
    for(int iLayer=0; iLayer<nLayers-1; iLayer++) {
      ParamLayer * layer=_layers.at(iLayer);
      if(!layer->valueConditions(ps)) {
        App::log(tr("Error initializing value conditions for layer %1\n").arg(layer->name()) );
        return false;
      }
    }
  }
  return true;
}

/*!
  Initialize the final profiles with the correct number of sub layers and set the index of the top sub layer of each paramLayer.
*/
void ParamProfile::initFinalProfile()
{
  TRACE;
  int index=0;
  int n=_layers.count();
  for(int i=0; i<n; i++ ) {
    _layers.at(i)->setTopDepthIndex(index);
  }
  // index is now the total number of layers and sublayers in this profile
  switch (_type) {
  case Condition:
    _minRaw.resize(index);
    _maxRaw.resize(index);
    break;
  case Param:
    _raw.resize(index);
    break;
  }
}

/*!
  Called routinely during inversion, it inits the depths from parameter values.
  Linked depth parameters are not considered.
*/
void ParamProfile::setFinalDepths(int fromLayer)
{
  TRACE;
  double z;
  if(fromLayer==0) {
    z=0.0;
  } else {
    z=_layers.at(fromLayer)->gradientProfile().topDepth();
  }
  int n=_layers.count();
  for(int i=fromLayer; i<n; i++ ) {
    _layers.at(i)->setFinalDepths(z);
  }
}

/*!
  Called routinely during inversion, it inits the depths from parameter values.
  Linked depth parameters are considered to build the final depth profile.
*/
void ParamProfile::setFinalProfileDepths(int fromLayer)
{
  TRACE;
  double z;
  if(fromLayer==0) {
    z=0.0;
  } else {
    z=_layers.at(fromLayer)->gradientProfile().topDepth();
  }
  int n=_layers.count();
  switch (_type) {
  case Param:
    for(int i=fromLayer; i<n; i++) {
      _layers.at(i)->setFinalProfileDepthsParam(_raw, z);
    }
    break;
  case Condition:
    for(int i=fromLayer; i<n; i++) {
      _layers.at(i)->setFinalProfileDepthsCondition(_minRaw, _maxRaw, z);
    }
    break;
  }
}

void ParamProfile::setFinalProfileFrom(int layer)
{
  TRACE;
  int n=_layers.count();
  switch (_type) {
  case Param:
    for(int i=layer; i<n; i++ ) {
      _layers.at(i)->setFinalProfileValuesParam(_raw);
    }
    break;
  case Condition:
    break;
  }
}

void ParamProfile::setFinalProfileAt(int layer)
{
  TRACE;
  switch (_type) {
  case Param:
    _layers.at(layer)->setFinalProfileValuesParam(_raw);
    break;
  case Condition:
    break;
  }
}

/*!
  Debug: prints raw profile
*/
void ParamProfile::pRaw() const
{
  TRACE;
  _raw.print();
}

/*!
  Debug: prints resampled profile
*/
void ParamProfile::pResampled() const
{
  TRACE;
  _resampled.print();
}

/*!
  Debug: prints maximum raw profile
*/
void ParamProfile::pMaxRaw() const
{
  TRACE;
  _maxRaw.print();
}

/*!
  Debug: prints maximum resamples profile
*/
void ParamProfile::pMaxResampled() const
{
  TRACE;
  _maxResampled.print();
}

void ParamProfile::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  LayerList::const_iterator it;
  static const QString key("name");
  XMLSaveAttributes att;
  QString& value=att.add(key);
  for(it=_layers.begin();it!=_layers.end();++it) {
    value=( *it) ->name();
    ( *it) ->xml_save(s, context, att);
  }
}

void ParamProfile::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s, "type", _type==Param ? "Param" : "Condition" );
  writeProperty(s, "longName", _longName);
  writeProperty(s, "shortName", _shortName);
  writeProperty(s, "unit", _unit);
  writeProperty(s, "defaultMinimum", _defaultMinimum);
  writeProperty(s, "defaultMaximum", _defaultMaximum);
  writeProperty(s, "defaultCondition", SimpleCondition::toString(_defaultCondition));
}

XMLMember ParamProfile::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="longName" ) return XMLMember(0);
  else if(tag=="shortName" ) return XMLMember(1);
  else if(tag=="unit" ) return XMLMember(2);
  else if(tag=="type" ) return XMLMember(6);
  else if(tag=="defaultMinimum" ) return XMLMember(3);
  else if(tag=="defaultMaximum" ) return XMLMember(4);
  else if(tag=="defaultCondition" ) return XMLMember(5);
  else if(tag=="ParamLayer" ) {
    ParamLayer * l=new ParamLayer(this, nLayers());
    _layers.append(l);
    return XMLMember(l);
  } else if(tag=="minDefault") return XMLMember(3); // Kept for compatibility
  else if(tag=="maxDefault") return XMLMember(4); // Kept for compatibility
  else return XMLMember(XMLMember::Unknown);
}

bool ParamProfile::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(context)
  Q_UNUSED(attributes)
  switch (memberID) {
  case 0: _longName=content.toString(); return true;
  case 1: _shortName=content.toString(); return true;
  case 2: _unit=content.toString(); return true;
  case 3: _defaultMinimum=content.toDouble(); return true;
  case 4: _defaultMaximum=content.toDouble(); return true;
  case 5: {
      bool ok=true;
      _defaultCondition=SimpleCondition::fromString(content.toString(), ok);
      return ok;
    }
  case 6:
    if(content=="Condition" )
      _type=Condition;
    else
      _type=Param;
    return true;
  default: return false;
  }
}

} // namespace DinverDCCore
