/***************************************************************************
**
**  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 <QGpCoreTools.h>
#include <QGpCoreWave.h>

#include "ParamGroundModel.h"
#include "ParamProfile.h"
#include "ParamLayer.h"
#include "GroundParameter.h"

namespace DinverDCCore {

const QString ParamGroundModel::xmlParamGroundModelTag="ParamGroundModel";

ParamGroundModel::ParamGroundModel()
{
  TRACE;
}

ParamGroundModel::ParamGroundModel(const ParamGroundModel& o)
  : XMLClass()
{
  TRACE;
  _customConditions=o._customConditions;
  _position=o._position;
  ProfileList::const_iterator it;
  for(it=o._profiles.begin();it!=o._profiles.end();++it) {
    _profiles.append(new ParamProfile( **it) );
  }
  setDepthLinks();
}

/*!
  Build a parameterized model from \a m. All parameters are fixed.
*/
ParamGroundModel::ParamGroundModel(Seismic1DModel * m)
{
  TRACE;
  ParamProfile * vp=new ParamProfile( "Vp", tr("Compression-wave velocity"), "m/s", 200, 5000,
                                              ParamProfile::Param, SimpleCondition::LessThan);
  _profiles.append(vp);
  ParamProfile * nu=new ParamProfile( "Nu", tr("Poisson's Ratio"), "", 0.2, 0.5,
                                              ParamProfile::Condition, SimpleCondition::GreaterThan);
  _profiles.append(nu);
  ParamProfile * vs=new ParamProfile( "Vs", tr("Shear-wave velocity"), "m/s", 50, 3500,
                                              ParamProfile::Param, SimpleCondition::LessThan);
  _profiles.append(vs);
  ParamProfile * rho=new ParamProfile( "Rho", tr("Density"), "kg/m3", 2000, 2000,
                                               ParamProfile::Param, SimpleCondition::LessThan);
  _profiles.append(rho);

  double d=0.0, v;
  for(int i=0; i<m->layerCount();i++) {
    if(i<m->layerCount()-1) d+=m->h(i);
    v=1.0/m->slowP(i);
    vp->addLayer(new ParamLayer(vp, i, v, v, vs) );
    v=1.0/m->slowS(i);
    vs->addLayer(new ParamLayer(vs, i, v, v, d, d) );
    v=m->rho(i);
    rho->addLayer(new ParamLayer(rho, i, v, v, vs) );
  }
  nu->addLayer(new ParamLayer(nu, 0, 0.2, 0.5, vs) );
}

ParamGroundModel::~ParamGroundModel()
{
  TRACE;
  ProfileList::iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) delete *it;
}

bool ParamGroundModel::isEmpty()
{
  TRACE;
  if(_profiles.size()==0) return true;
  ProfileList::iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    if((*it)->nLayers()==0) return true;
  }
  return false;
}

bool ParamGroundModel::hasGradient() const
{
  TRACE;
  ProfileList::const_iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    if((*it)->hasGradient()) return true;
  }
  return false;
}

/*!
  Inserts \a layer at index \at in \a profile. Layer indexes and depth links are
  properly updated.
*/
void ParamGroundModel::insertLayer(ParamProfile * profile, int at, ParamLayer * layer)
{
  TRACE;
  profile->insertLayer(at, layer);
  // Update names of links
  ProfileList::iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it)
    (*it)->setDepthLinkNames();
}

void ParamGroundModel::removeProfile(ParamProfile * profile)
{
  TRACE;
  int index=_profiles.indexOf(profile);
  if(index>-1) {
    _profiles.removeAt(index);
  }
  delete profile;
}

bool ParamGroundModel::toParameters(RealSpace& ps) const
{
  TRACE;
  LayerMap links;
  ProfileList::const_iterator it;
  // Collect all layers that can be linked (all except bottom half spaces)
  for(it=_profiles.begin();it!=_profiles.end();++it)
    (*it)->collectDepthLinks(links);
  // Add parameters and conditions to parameter space
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    if(!(*it)->toParam(ps,links, this)) return false;
  }
  // Add conditions on depths generated by linked depths
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    (*it)->setLinkedDepth(ps);
  }
  // Add values conditions
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    (*it)->setValueConditions(ps);
  }
  // Add custom conditions
  if( !_customConditions.apply(&ps) ) {
    return false;
  }

  return true;
}

ParamProfile * ParamGroundModel::find(QString shortName)
{
  TRACE;
  ProfileList::iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    if((*it)->shortName()==shortName) return *it;
  }
  return nullptr;
}

void ParamGroundModel::initFinalProfiles()
{
  TRACE;
  ProfileList::iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    (*it)->initFinalProfile();
  }
}

void ParamGroundModel::setDepthLinks()
{
  TRACE;
  LayerMap links;
  ProfileList::iterator it;
  // Collect all layers that can be linked (all except bottom half spaces)
  for(it=_profiles.begin();it!=_profiles.end();++it)
    (*it)->collectDepthLinks(links);
  for(it=_profiles.begin();it!=_profiles.end();++it)
    (*it)->setDepthLinks(links);
}

/*!
  Called routinely during inversion, it creates a curve for all profiles with the same sampling depths
*/
void ParamGroundModel::updateFinalProfiles(const GroundParameter * from)
{
  TRACE;
  if(from) {
    switch (from->type()) {
    case GroundParameter::Depth:
    case GroundParameter::Thickness:
      for(int i=from->count()-1; i>=0; i-- ) {
        const ParamLayer * l=from->layer(i);
        l->profile()->setFinalDepths(l->index());
      }
      for(int i=from->count()-1; i>=0; i-- ) {
        const ParamLayer * l=from->layer(i);
        l->profile()->setFinalProfileDepths(l->index());
      }
      for(int i=from->count()-1; i>=0; i-- ) {
        const ParamLayer * l=from->layer(i);
        l->profile()->setFinalProfileFrom(l->index());
      }
      break;
    case GroundParameter::ValueGradient:
    case GroundParameter::ValueTop:
    case GroundParameter::ValueBottom:
      for(int i=from->count()-1; i>=0; i-- ) {
        const ParamLayer * l=from->layer(i);
        l->profile()->setFinalProfileAt(l->index());
      }
      break;
    }
  } else {
    ProfileList::iterator it;
    for(it=_profiles.begin();it!=_profiles.end();++it) {
      (*it)->setFinalDepths();
    }
    for(it=_profiles.begin();it!=_profiles.end();++it) {
      (*it)->setFinalProfileDepths();
    }
    for(it=_profiles.begin();it!=_profiles.end();++it) {
      (*it)->setFinalProfileFrom();
    }
  }
  resampleProfiles();
  /*for(ProfileList::const_iterator it=_profiles.begin();it!=_profiles.end();++it) {
    ParamProfile * p=*it;
    if(p->type()==ParamProfile::Param && p->shortName()!="Rho") {
      printf("Resampled %s profile\n", p->shortName().toLatin1().data());
      p->resampledProfile().print();
    }
  }*/
}

void ParamGroundModel::resampleProfiles()
{
  TRACE;
  // At this stage all profiles are ok and sorted
  // resample profiles to get a unique discretization
  QVector<double> depths;
  ProfileList::iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    (*it)->collectDepths(depths);
  }
  /*QList<double>::iterator itD;
  for(itD=depths.begin();itD!=depths.end();itD++) {
    printf("Before sort %lg\n",*itD);
  }*/
  std::sort(depths.begin(), depths.end());
  unique(depths);
  /*for(itD=depths.begin();itD!=depths.end();itD++) {
    printf("After unique %lg\n",*itD);
  }*/
  for(it=_profiles.begin();it!=_profiles.end();++it) {
    (*it)->resampleProfile(depths);
  }
}

void ParamGroundModel::xml_writeProperties(XML_WRITEPROPERTIES_ARGS) const
{
  TRACE;
  Q_UNUSED(context)
  writeProperty(s, "position", _position.toString(20));
}

void ParamGroundModel::xml_writeChildren(XML_WRITECHILDREN_ARGS) const
{
  TRACE;
  ProfileList::const_iterator it;
  for(it=_profiles.begin();it!=_profiles.end();++it) (*it)->xml_save(s, context);
  _customConditions.xml_save(s, context);
}

XMLMember ParamGroundModel::xml_member(XML_MEMBER_ARGS)
{
  TRACE;
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  if(tag=="ParamProfile") {
    ParamProfile * p=new ParamProfile;
    _profiles.append(p);
    return XMLMember(p);
  } else if(tag=="ParamSpaceScript") {
    return XMLMember(&_customConditions);
  } else if(tag=="ProfileCondition") {
    return XMLMember(new XMLGenericItem("ProfileCondition"), true);     // Kept for compatibility
  } else if(tag=="position") {
    return XMLMember(0);
  } else return XMLMember(XMLMember::Unknown);
}

bool ParamGroundModel::xml_setProperty(XML_SETPROPERTY_ARGS)
{
  TRACE;
  Q_UNUSED(tag)
  Q_UNUSED(attributes)
  Q_UNUSED(context)
  switch (memberID) {
  case 0: _position.fromString(content.toString()); return true;
  default: return false;
  }
}

bool ParamGroundModel::xml_polish(XML_POLISH_ARGS)
{
  Q_UNUSED(context)
  setDepthLinks();
  return true;
}

} // namespace DinverDCCore
