//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//=========================================================================

#include "cmbProfileWedgeFunction.h"
#include "pqSMAdaptor.h"
#include "vtkPiecewiseFunction.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMSourceProxy.h"

#include <cmath>

cmbProfileWedgeFunction::cmbProfileWedgeFunction()
  : depth(-8)
  , baseWidth(0)
  , slopeLeft(1)
  , slopeRight(1)
  , WeightingFunction(vtkPiecewiseFunction::New())
  , Relative(true)
  , Symmetry(true)
  , WeightUseSpline(false)
  , clamp(true)
  , dispMode(Dig)
{
  this->WeightingFunction->SetAllowDuplicateScalars(1);
  this->WeightingFunction->AddPoint(0, 1);
  this->WeightingFunction->AddPoint(1, 1);
}

cmbProfileWedgeFunction::cmbProfileWedgeFunction(cmbProfileWedgeFunction const* other)
  : depth(other->depth)
  , baseWidth(other->baseWidth)
  , slopeLeft(other->slopeLeft)
  , slopeRight(other->slopeRight)
  , WeightingFunction(vtkPiecewiseFunction::New())
  , Relative(other->Relative)
  , Symmetry(other->Symmetry)
  , WeightUseSpline(other->WeightUseSpline)
  , clamp(other->clamp)
  , dispMode(other->dispMode)
{
  this->WeightingFunction->DeepCopy(other->WeightingFunction);
}

cmbProfileWedgeFunction::~cmbProfileWedgeFunction()
{
  WeightingFunction->Delete();
}

cmbProfileFunction::FunctionType cmbProfileWedgeFunction::getType() const
{
  return cmbProfileFunction::WEDGE;
}

cmbProfileFunction* cmbProfileWedgeFunction::clone(std::string const& name) const
{
  cmbProfileWedgeFunction* result = new cmbProfileWedgeFunction(this);
  result->setName(name);
  return result;
}

void cmbProfileWedgeFunction::sendDataToProxy(
  int arc_ID, int funId, vtkBoundingBox vtkNotUsed(bbox), vtkSMSourceProxy* source) const
{
  double slopeLeftLocal = getSlopeLeft();
  double slopeRightLocal = getSlopeRight();
  double baseWidthLocal = getBaseWidth();

  QList<QVariant> v;
  double widthLeft = widthLeft = baseWidthLocal * 0.5;
  if (slopeLeftLocal != 0)
  {
    slopeLeftLocal = 1.0 / slopeLeftLocal;
  }
  if (slopeRightLocal != 0)
  {
    slopeRightLocal = 1.0 / slopeRight;
  }
  v << arc_ID << funId << ((this->WeightUseSpline) ? 1 : 0) << Relative << this->getMode()
    << this->clamp << getBaseWidth() << getDepth() << slopeLeftLocal << slopeRightLocal;
  pqSMAdaptor::setMultipleElementProperty(source->GetProperty("CreateWedgeFunction"), v);
  source->UpdateVTKObjects();
  v.clear();
  v << -1 << -1 << 0 << 0 << 0 << 0 << 0 << 0 << 0 << 0;
  pqSMAdaptor::setMultipleElementProperty(source->GetProperty("CreateWedgeFunction"), v);
  source->UpdateVTKObjects();

  for (int i = 0; i < WeightingFunction->GetSize(); ++i)
  {
    double d[4];
    v.clear();
    WeightingFunction->GetNodeValue(i, d);
    v << arc_ID << funId << d[0] << d[1] << d[2] << d[3];
    pqSMAdaptor::setMultipleElementProperty(source->GetProperty("AddWeightPoint"), v);
    source->UpdateVTKObjects();
  }
  v.clear();
  v << -1 << -1 << 0 << 0 << 0 << 0;
  pqSMAdaptor::setMultipleElementProperty(source->GetProperty("AddWeightPoint"), v);
  source->UpdateVTKObjects();
}

double cmbProfileWedgeFunction::getDepth() const
{
  return this->depth;
}

double cmbProfileWedgeFunction::getBaseWidth() const
{
  return this->baseWidth;
}

double cmbProfileWedgeFunction::getSlopeLeft() const
{
  return this->slopeLeft;
}

double cmbProfileWedgeFunction::getSlopeRight() const
{
  return this->slopeRight;
}

void cmbProfileWedgeFunction::setDepth(double d)
{
  this->depth = d;
}

void cmbProfileWedgeFunction::setBaseWidth(double d)
{
  this->baseWidth = d;
}

void cmbProfileWedgeFunction::setSlopeLeft(double d)
{
  this->slopeLeft = d;
}

void cmbProfileWedgeFunction::setSlopeRight(double d)
{
  this->slopeRight = d;
}

bool cmbProfileWedgeFunction::isRelative() const
{
  return this->Relative;
}

void cmbProfileWedgeFunction::setRelative(bool ir)
{
  this->Relative = ir;
}

bool cmbProfileWedgeFunction::isSymmetric() const
{
  return this->Symmetry;
}

void cmbProfileWedgeFunction::setSymmetry(bool d)
{
  if (d == Symmetry)
    return;
  Symmetry = d;
  double v[4];
  vtkPiecewiseFunction* tw = vtkPiecewiseFunction::New();
  tw->SetAllowDuplicateScalars(1);
  tw->DeepCopy(WeightingFunction);
  WeightingFunction->Initialize();
  if (!d)
  {
    this->setSlopeRight(this->getSlopeLeft());
    for (int i = 0; i < tw->GetSize(); ++i)
    {
      tw->GetNodeValue(i, v);
      WeightingFunction->AddPoint(v[0], v[1], v[2], v[3]);
    }
    for (int i = tw->GetSize() - 1; i >= 0; --i)
    {
      tw->GetNodeValue(i, v);
      if (v[0] != 0)
      {
        WeightingFunction->AddPoint(-v[0], v[1], v[2], v[3]);
      }
    }
  }
  else
  {
    for (int i = 0; i < tw->GetSize(); ++i)
    {
      tw->GetNodeValue(i, v);
      if (v[0] > 0)
      {
        WeightingFunction->AddPoint(v[0], v[1], v[2], v[3]);
      }
    }
    WeightingFunction->AddPoint(0, tw->GetValue(0));
  }
}

bool cmbProfileWedgeFunction::readData(std::ifstream& in, int vtkNotUsed(version))
{
  int subv, tmpMode;
  in >> subv >> this->depth >> this->baseWidth >> this->slopeLeft >> this->slopeRight >>
    this->Relative >> this->Symmetry >> this->WeightUseSpline >> this->clamp >> tmpMode;
  this->dispMode = static_cast<DisplacmentMode>(tmpMode);

  int n;
  in >> n;
  this->WeightingFunction->Initialize();
  for (int i = 0; i < n; ++i)
  {
    double d[4];
    in >> d[0] >> d[1] >> d[2] >> d[3];
    this->WeightingFunction->AddPoint(d[0], d[1], d[2], d[3]);
  }
  return true;
}

bool cmbProfileWedgeFunction::writeData(std::ofstream& out) const
{
  out << '\n'
      << 1 << '\n' //version
      << this->depth << ' ' << this->baseWidth << ' ' << this->slopeLeft << ' ' << this->slopeRight
      << '\n'
      << this->Relative << ' ' << this->Symmetry << ' ' << this->WeightUseSpline << ' '
      << this->clamp << ' ' << this->dispMode << '\n';

  out << this->WeightingFunction->GetSize() << "\n";
  for (int i = 0; i < WeightingFunction->GetSize(); ++i)
  {
    double d[4];
    this->WeightingFunction->GetNodeValue(i, d);
    out << d[0] << ' ' << d[1] << ' ' << d[2] << ' ' << d[3] << '\n';
  }

  return true;
}

bool cmbProfileWedgeFunction::isWeightSpline() const
{
  return WeightUseSpline;
}

void cmbProfileWedgeFunction::setWeightSpline(bool w)
{
  WeightUseSpline = w;
}

bool cmbProfileWedgeFunction::isClamped() const
{
  return clamp;
}
void cmbProfileWedgeFunction::setClamped(bool w)
{
  clamp = w;
}

void cmbProfileWedgeFunction::setMode(DisplacmentMode d)
{
  if (Relative)
  {
    if (d == Level)
      return;
    if (depth < 0)
      this->dispMode = Dig;
    else
      this->dispMode = Raise;
  }
  else
  {
    this->dispMode = d;
  }
}

cmbProfileWedgeFunction::DisplacmentMode cmbProfileWedgeFunction::getMode() const
{
  if (Relative)
  {
    if (depth < 0)
      return Dig;
    else
      return Raise;
  }
  return this->dispMode;
}
