/*=========================================================================

  Program:   ParaView
  Module:    vtkPVComparativeAnimationCue.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkPVComparativeAnimationCue.h"

#include "vtkCommand.h"
#include "vtkObjectFactory.h"
#include "vtkPVXMLElement.h"
#include "vtkSMDomain.h"
#include "vtkSMDomainIterator.h"
#include "vtkSMVectorProperty.h"
#include "vtkSMProxy.h"

#include <vtksys/SystemTools.hxx>
#include <sstream>
#include <vector>
#include <string>
#include <string.h>

class vtkPVComparativeAnimationCue::vtkInternals
{
public:
  enum
    {
    SINGLE,
    XRANGE,
    YRANGE,
    TRANGE,
    TRANGE_VERTICAL_FIRST
    };

  // vtkCueCommand is a unit of computation used to compute the value at a given
  // position in the comparative grid.
  class vtkCueCommand
    {
  private:
    std::string ValuesToString(double* values)
      {
      std::ostringstream str;
      for (unsigned int cc=0; cc < this->NumberOfValues; cc++)
        {
        str << setprecision(16)<< values[cc];
        if (cc > 0)
          {
          str << ",";
          }
        }
      return str.str();
      }

    // don't call this if this->NumberOfValues == 1.
    double *ValuesFromString(const char* str)
      {
      double * values = NULL;
      if (str != NULL && *str != 0)
        {
        std::vector<vtksys::String> parts =
          vtksys::SystemTools::SplitString(str, ',');
        if (static_cast<unsigned int>(parts.size()) == this->NumberOfValues)
          {
          values = new double[this->NumberOfValues];
          for (unsigned int cc=0; cc < this->NumberOfValues; cc++)
            {
            values[cc] = atof(parts[cc].c_str());
            }
          }
        }
      return values;
      }

    void Duplicate(const vtkCueCommand& other)
      {
      this->Type = other.Type;
      this->AnchorX = other.AnchorX;
      this->AnchorY = other.AnchorY;
      this->NumberOfValues = other.NumberOfValues;
      this->MinValues = this->MaxValues = NULL;
      if (this->NumberOfValues > 0)
        {
        this->MinValues = new double [this->NumberOfValues];
        memcpy(this->MinValues, other.MinValues,
          sizeof(double) * this->NumberOfValues);

        this->MaxValues = new double [this->NumberOfValues];
        memcpy(this->MaxValues, other.MaxValues,
          sizeof(double) * this->NumberOfValues);
        }
      }
  public:
    int Type;
    double* MinValues;
    double* MaxValues;
    unsigned int NumberOfValues;
    int AnchorX, AnchorY;
    vtkCueCommand()
      {
      this->Type = -1;
      this->AnchorX = this->AnchorY = -1;
      this->NumberOfValues = 0;
      this->MaxValues = 0;
      this->MinValues = 0;
      }

    vtkCueCommand(const vtkCueCommand& other)
      {
      this->Duplicate(other);
      }

    vtkCueCommand& operator=(const vtkCueCommand& other)
      {
      delete this->MinValues;
      delete this->MaxValues;
      this->Duplicate(other);
      return *this;
      }

    ~vtkCueCommand()
      {
      delete [] this->MinValues;
      this->MinValues = 0;

      delete [] this->MaxValues;
      this->MaxValues = 0;
      }

    void SetValues(double* minValues, double* maxValues, unsigned int num)
      {
      delete [] this->MaxValues;
      delete [] this->MinValues;
      this->MaxValues = 0;
      this->MinValues = 0;
      this->NumberOfValues = num;
      if (num > 0)
        {
        this->MinValues = new double[num];
        this->MaxValues = new double[num];
        memcpy(this->MinValues, minValues, num*sizeof(double));
        memcpy(this->MaxValues, maxValues, num*sizeof(double));
        }
      }

    bool operator==(const vtkCueCommand& other)
      {
      return this->Type == other.Type &&
        this->NumberOfValues == other.NumberOfValues &&
        this->AnchorX == other.AnchorX &&
        this->AnchorY == other.AnchorY &&
        (memcmp(this->MinValues, other.MinValues,
                sizeof(double) * this->NumberOfValues) == 0) &&
        (memcmp(this->MaxValues, other.MaxValues,
                sizeof(double) * this->NumberOfValues) == 0);
      }

    vtkPVXMLElement* ToXML()
      {
      vtkPVXMLElement* elem = vtkPVXMLElement::New();
      elem->SetName("CueCommand");
      elem->AddAttribute("type", this->Type);
      elem->AddAttribute("anchorX", this->AnchorX);
      elem->AddAttribute("anchorY", this->AnchorY);
      elem->AddAttribute("num_values", this->NumberOfValues);
      elem->AddAttribute("min_values",
        this->ValuesToString(this->MinValues).c_str());
      elem->AddAttribute("max_values",
        this->ValuesToString(this->MaxValues).c_str());
      return elem;
      }

    bool FromXML(vtkPVXMLElement* elem)
      {
      if (!elem->GetName() || strcmp(elem->GetName(), "CueCommand") != 0)
        {
        return false;
        }
      int numvalues=0;
      if (elem->GetScalarAttribute("type", &this->Type) &&
        elem->GetScalarAttribute("anchorX", &this->AnchorX) &&
        elem->GetScalarAttribute("anchorY", &this->AnchorY) &&
        elem->GetScalarAttribute("num_values", &numvalues))
        {
        this->NumberOfValues = static_cast<unsigned int>(numvalues);
        if (this->NumberOfValues > 1)
          {
          delete [] this->MinValues;
          delete [] this->MaxValues;

          this->MinValues = this->ValuesFromString(elem->GetAttribute("min_values"));
          this->MaxValues = this->ValuesFromString(elem->GetAttribute("max_values"));
          return this->MaxValues != NULL && this->MinValues != NULL;
          }
        else
          {
          delete [] this->MinValues;
          this->MinValues = new double[1];
          this->MinValues[0] = 0;

          delete [] this->MaxValues;
          this->MaxValues = new double[1];
          this->MaxValues[0] = 0;

          return elem->GetScalarAttribute("min_values", this->MinValues) &&
            elem->GetScalarAttribute("max_values", this->MaxValues);
          }
        }
      return false;
      }
    };

  void RemoveCommand(const vtkCueCommand& cmd)
    {
    std::vector<vtkCueCommand>::iterator iter;
    for (iter = this->CommandQueue.begin();
      iter != this->CommandQueue.end(); ++iter)
      {
      if (*iter == cmd)
        {
        this->CommandQueue.erase(iter);
        break;
        }
      }
    }

  void InsertCommand(const vtkCueCommand& cmd, int pos)
    {
    std::vector<vtkCueCommand>::iterator iter;
    int cc=0;
    for (iter = this->CommandQueue.begin();
      iter != this->CommandQueue.end() && cc < pos;
      ++iter, ++cc)
      {
      }
    this->CommandQueue.insert(iter, cmd);
    }

  std::vector<vtkCueCommand> CommandQueue;
};

vtkStandardNewMacro(vtkPVComparativeAnimationCue);
vtkCxxSetObjectMacro(vtkPVComparativeAnimationCue, AnimatedProxy, vtkSMProxy);
//----------------------------------------------------------------------------
vtkPVComparativeAnimationCue::vtkPVComparativeAnimationCue()
{
  this->Internals = new vtkInternals();
  this->Values = new double[128]; // some large limit.
  this->AnimatedProxy = 0;
  this->AnimatedPropertyName = 0;
  this->AnimatedDomainName = 0;
  this->AnimatedElement = 0;
  this->Enabled = true;
}

//----------------------------------------------------------------------------
vtkPVComparativeAnimationCue::~vtkPVComparativeAnimationCue()
{
  delete this->Internals;
  this->Internals = 0;
  delete [] this->Values;
  this->Values = 0;
   this->SetAnimatedProxy(0);
  this->SetAnimatedPropertyName(0);
  this->SetAnimatedDomainName(0);
}

//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::RemoveAnimatedProxy()
{
  this->SetAnimatedProxy(0);
}
//----------------------------------------------------------------------------
vtkSMProperty* vtkPVComparativeAnimationCue::GetAnimatedProperty()
{
  if (!this->AnimatedPropertyName || !this->AnimatedProxy)
    {
    return NULL;
    }

  return this->AnimatedProxy->GetProperty(this->AnimatedPropertyName);
}

//----------------------------------------------------------------------------
vtkSMDomain* vtkPVComparativeAnimationCue::GetAnimatedDomain()
{
  vtkSMProperty* property = this->GetAnimatedProperty();
  if (!property)
    {
    return NULL;
    }
  vtkSMDomain* domain = NULL;
  vtkSMDomainIterator* iter = property->NewDomainIterator();
  iter->Begin();
  if (!iter->IsAtEnd())
    {
    domain = iter->GetDomain();
    }
  iter->Delete();
  return domain;
}


//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::UpdateXRange(
  int y, double* minx, double* maxx, unsigned int numValues)
{
  vtkInternals::vtkCueCommand cmd;
  cmd.Type = vtkInternals::XRANGE;
  cmd.AnchorX = -1;
  cmd.AnchorY = y;
  cmd.SetValues(minx, maxx, numValues);

  vtkPVXMLElement* changeXML = vtkPVXMLElement::New();
  changeXML->SetName("StateChange");

  int position = 0;
  // remove obsolete values.
  std::vector<vtkInternals::vtkCueCommand>::iterator iter;
  for (iter = this->Internals->CommandQueue.begin();
    iter != this->Internals->CommandQueue.end(); position++)
    {
    bool remove = false;
    if (iter->Type == vtkInternals::SINGLE && iter->AnchorY == y)
      {
      remove = true;
      }
    if (iter->Type == vtkInternals::XRANGE && iter->AnchorY == y)
      {
      remove = true;
      }
    if (remove)
      {
      vtkPVXMLElement* removeXML = iter->ToXML();
      removeXML->AddAttribute("position", position);
      removeXML->AddAttribute("remove", 1);
      changeXML->AddNestedElement(removeXML);
      removeXML->FastDelete();

      iter = this->Internals->CommandQueue.erase(iter);
      }
    else
      {
      iter++;
      }
    }

  this->Internals->CommandQueue.push_back(cmd);

  vtkPVXMLElement* addXML = cmd.ToXML();
  changeXML->AddNestedElement(addXML);
  addXML->FastDelete();
  this->InvokeEvent(vtkCommand::StateChangedEvent, changeXML);
  changeXML->Delete();

  this->Modified();
}

//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::UpdateYRange(
  int x, double* miny, double* maxy, unsigned int numValues)
{
  vtkInternals::vtkCueCommand cmd;
  cmd.Type = vtkInternals::YRANGE;
  cmd.AnchorX = x;
  cmd.AnchorY = -1;
  cmd.SetValues(miny, maxy, numValues);

  vtkPVXMLElement* changeXML = vtkPVXMLElement::New();
  changeXML->SetName("StateChange");

  int position = 0;
  // remove obsolete values.
  std::vector<vtkInternals::vtkCueCommand>::iterator iter;
  for (iter = this->Internals->CommandQueue.begin();
    iter != this->Internals->CommandQueue.end(); position++)
    {
    bool remove = false;
    if (iter->Type == vtkInternals::SINGLE && iter->AnchorX == x)
      {
      remove = true;
      }
    if (iter->Type == vtkInternals::YRANGE && iter->AnchorX == x)
      {
      remove = true;
      }
    if (remove)
      {
      vtkPVXMLElement* removeXML = iter->ToXML();
      removeXML->AddAttribute("position", position);
      removeXML->AddAttribute("remove", 1);
      changeXML->AddNestedElement(removeXML);
      removeXML->FastDelete();

      iter = this->Internals->CommandQueue.erase(iter);
      }
    else
      {
      iter++;
      }
    }

  this->Internals->CommandQueue.push_back(cmd);

  vtkPVXMLElement* addXML = cmd.ToXML();
  changeXML->AddNestedElement(addXML);
  addXML->FastDelete();
  this->InvokeEvent(vtkCommand::StateChangedEvent, changeXML);
  changeXML->Delete();

  this->Modified();
}

//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::UpdateWholeRange(
  double *mint, double *maxt, unsigned int numValues,
  bool vertical_first)
{
  vtkPVXMLElement* changeXML = vtkPVXMLElement::New();
  changeXML->SetName("StateChange");

  // remove all  values (we are just recording the state change here).
  int position = 0;
  std::vector<vtkInternals::vtkCueCommand>::iterator iter;
  for (iter = this->Internals->CommandQueue.begin();
    iter != this->Internals->CommandQueue.end(); ++position, ++iter)
    {
    vtkPVXMLElement* removeXML = iter->ToXML();
    removeXML->AddAttribute("position", position);
    removeXML->AddAttribute("remove", 1);
    changeXML->AddNestedElement(removeXML);
    removeXML->FastDelete();
    }

  this->Internals->CommandQueue.clear();
  vtkInternals::vtkCueCommand cmd;
  cmd.Type = vertical_first? vtkInternals::TRANGE_VERTICAL_FIRST :
    vtkInternals::TRANGE;
  cmd.SetValues(mint, maxt, numValues);

  this->Internals->CommandQueue.push_back(cmd);

  vtkPVXMLElement* addXML = cmd.ToXML();
  changeXML->AddNestedElement(addXML);
  addXML->FastDelete();
  this->InvokeEvent(vtkCommand::StateChangedEvent, changeXML);
  changeXML->Delete();

  this->Modified();
}

//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::UpdateValue(
  int x, int y, double *value, unsigned int numValues)
{
  vtkInternals::vtkCueCommand cmd;
  cmd.Type = vtkInternals::SINGLE;
  cmd.AnchorX = x; cmd.AnchorY = y;
  cmd.SetValues(value, value, numValues);

  vtkPVXMLElement* changeXML = vtkPVXMLElement::New();
  changeXML->SetName("StateChange");

  int position = 0;
  std::vector<vtkInternals::vtkCueCommand>::iterator iter;
  for (iter = this->Internals->CommandQueue.begin();
    iter != this->Internals->CommandQueue.end(); ++position)
    {
    bool remove = false;
    if (iter->Type == vtkInternals::SINGLE && iter->AnchorX == x &&
      iter->AnchorY == y)
      {
      remove = true;
      }
    if (remove)
      {
      vtkPVXMLElement* removeXML = iter->ToXML();
      removeXML->AddAttribute("position", position);
      removeXML->AddAttribute("remove", 1);
      changeXML->AddNestedElement(removeXML);
      removeXML->FastDelete();

      iter = this->Internals->CommandQueue.erase(iter);
      }
    else
      {
      iter++;
      }
    }
  this->Internals->CommandQueue.push_back(cmd);

  vtkPVXMLElement* addXML = cmd.ToXML();
  changeXML->AddNestedElement(addXML);
  addXML->FastDelete();
  this->InvokeEvent(vtkCommand::StateChangedEvent, changeXML);
  changeXML->Delete();
  this->Modified();
}

//----------------------------------------------------------------------------
double* vtkPVComparativeAnimationCue::GetValues(
  int x, int y, int dx, int dy, unsigned int &numValues)
{
  numValues = 0;
  std::vector<vtkInternals::vtkCueCommand>::iterator iter;
  for (iter = this->Internals->CommandQueue.begin();
    iter != this->Internals->CommandQueue.end(); ++iter)
    {
    unsigned int count = iter->NumberOfValues > 128? 128 : iter->NumberOfValues;
    switch (iter->Type)
      {
    case vtkInternals::SINGLE:
      if (x == iter->AnchorX && y == iter->AnchorY)
        {
        memcpy(this->Values, iter->MinValues, sizeof(double)*count);
        numValues = count;
        }
      break;

    case vtkInternals::XRANGE:
      if (y == iter->AnchorY || iter->AnchorY == -1)
        {
        for (unsigned int cc=0; cc < count; cc++)
          {
          this->Values[cc] =  dx > 1 ?
            iter->MinValues[cc] + (x * (iter->MaxValues[cc] - iter->MinValues[cc])) / (dx - 1) :
            iter->MinValues[cc];
          }
        numValues = count;
        }
      break;

    case vtkInternals::YRANGE:
      if (x == iter->AnchorX || iter->AnchorX == -1)
        {
        for (unsigned int cc=0; cc < count; cc++)
          {
          this->Values[cc] =  dy > 1 ?
            iter->MinValues[cc] + (y * (iter->MaxValues[cc] - iter->MinValues[cc])) / (dy - 1) :
            iter->MinValues[cc];
          }
        numValues = count;
        }
      break;

    case vtkInternals::TRANGE:
        {
        for (unsigned int cc=0; cc < count; cc++)
          {
          this->Values[cc] = (dx*dy> 1)?
            iter->MinValues[cc] + (y*dx + x) * (iter->MaxValues[cc] -
              iter->MinValues[cc]) / (dx*dy - 1) :
            iter->MinValues[cc];
          }
        numValues = count;
        }
      break;

    case vtkInternals::TRANGE_VERTICAL_FIRST:
        {
        for (unsigned int cc=0; cc < count; cc++)
          {
          this->Values[cc] = (dx*dy> 1)?
            iter->MinValues[cc] + (x*dy + y) * (iter->MaxValues[cc] -
              iter->MinValues[cc]) / (dx*dy - 1) :
            iter->MinValues[cc];
          }
        numValues = count;
        }
      break;
      }
    }

  return numValues>0 ? this->Values : NULL;
}

//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::UpdateAnimatedValue(
  int x, int y, int dx, int dy)
{
  if (!this->GetEnabled())
    {
    return;
    }

  vtkSMDomain *domain = this->GetAnimatedDomain();
  vtkSMProperty *property = this->GetAnimatedProperty();
  vtkSMProxy *proxy = this->GetAnimatedProxy();
  int animated_element = this->GetAnimatedElement();
  if (!proxy || !domain || !property)
    {
    vtkErrorMacro("Cue does not have domain or property set!");
    return;
    }

  unsigned int numValues = 0;
  double *values = this->GetValues(x, y, dx, dy, numValues);

  if (numValues == 0)
    {
    vtkErrorMacro("Failed to determine any value: "<< x <<", " << y);
    }
  else if (numValues == 1 && animated_element >= 0)
    {
    domain->SetAnimationValue(property, animated_element, values[0]);
    }
  else if (numValues >= 1 && animated_element == -1)
    {
    vtkSMVectorProperty* vp = vtkSMVectorProperty::SafeDownCast(property);
    if (vp)
      {
      vp->SetNumberOfElements(numValues);
      }
    for (unsigned int cc=0; cc < numValues; cc++)
      {
      domain->SetAnimationValue(property, cc, values[cc]);
      }
    }
  else
    {
    vtkErrorMacro("Failed to change parameter.");
    }
  proxy->UpdateVTKObjects();
}

//----------------------------------------------------------------------------
vtkPVXMLElement* vtkPVComparativeAnimationCue::AppendCommandInfo(vtkPVXMLElement* proxyElem)
{
  if (!proxyElem)
    {
    return NULL;
    }

  std::vector<vtkInternals::vtkCueCommand>::iterator iter;
  for (iter = this->Internals->CommandQueue.begin();
    iter != this->Internals->CommandQueue.end(); ++iter)
    {
    vtkPVXMLElement* commandElem = iter->ToXML();
    proxyElem->AddNestedElement(commandElem);
    commandElem->Delete();
    }
  return proxyElem;
}

//----------------------------------------------------------------------------
int vtkPVComparativeAnimationCue::LoadCommandInfo(vtkPVXMLElement* proxyElement)
{
  bool state_change_xml = (strcmp(proxyElement->GetName(), "StateChange") != 0);
  if (state_change_xml)
    {
    // unless the state being loaded is a StateChange, we start from scratch.
    this->Internals->CommandQueue.clear();
    }

  // NOTE: In case of state_change_xml,
  // this assumes that all "removes" happen before any inserts which are
  // always appends.
  unsigned int numElems = proxyElement->GetNumberOfNestedElements();
  for (unsigned int i=0; i<numElems; i++)
    {
    vtkPVXMLElement* currentElement = proxyElement->GetNestedElement(i);
    const char* name =  currentElement->GetName();
    if (name && strcmp(name, "CueCommand") == 0)
      {
      vtkInternals::vtkCueCommand cmd;
      if (cmd.FromXML(currentElement) == false)
        {
        vtkErrorMacro("Error when loading CueCommand.");
        return 0;
        }
      int remove = 0;
      if (state_change_xml &&
        currentElement->GetScalarAttribute("remove", &remove) &&
        remove != 0)
        {
        this->Internals->RemoveCommand(cmd);
        }
      else
        {
        this->Internals->CommandQueue.push_back(cmd);
        }
      }
    }
  this->Modified();
  return 1;
}

//----------------------------------------------------------------------------
void vtkPVComparativeAnimationCue::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}
