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

  Program:   ParaView
  Module:    $RCSfile$

  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 "vtkSMChartSeriesSelectionDomain.h"

#include "vtkChartRepresentation.h"
#include "vtkColorSeries.h"
#include "vtkCommand.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPVArrayInformation.h"
#include "vtkPVCompositeDataInformation.h"
#include "vtkPVDataInformation.h"
#include "vtkPVDataSetAttributesInformation.h"
#include "vtkPVXMLElement.h"
#include "vtkSMArrayListDomain.h"
#include "vtkSMSourceProxy.h"
#include "vtkSMStringVectorProperty.h"
#include "vtkSMUncheckedPropertyHelper.h"
#include "vtkStdString.h"
#include "vtkStringList.h"

#include <algorithm>
#include <assert.h>
#include <map>
#include <set>
#include <vector>
#include <vtksys/ios/sstream>
#include <vtksys/RegularExpression.hxx>

vtkStandardNewMacro(vtkSMChartSeriesSelectionDomain);
namespace
{
  // match string like: "ACCL (0)" or "VEL (1)"
  static vtksys::RegularExpression PotentailComponentNameRe(".*\\([0-9]+\\)");

  typedef std::pair<vtksys::RegularExpression, bool> SeriesVisibilityPair;
  static std::vector<SeriesVisibilityPair> SeriesVisibilityDefaults;
  static void InitSeriesVisibilityDefaults()
    {
    // initialize SeriesVisibilityDefaults the first time.
    if (SeriesVisibilityDefaults.size() == 0)
      {
      const char* defaults[] = {
        "^arc_length",
        "^bin_extents",
        "^FileId",
        "^GlobalElementId",
        "^GlobalNodeId",
        "^ObjectId",
        "^Pedigree.*",
        "^Points_.*",
        "^Time",
        "^vtkOriginal.*",
        "^vtkValidPointMask",
        "^N .*",
        NULL
      };
      for (int cc=0; defaults[cc] != NULL; cc++)
        {
        SeriesVisibilityDefaults.push_back(SeriesVisibilityPair(
            vtksys::RegularExpression(defaults[cc]), false));
        }
      }
    }
  static void AddSeriesVisibilityDefault(const char* regex, bool value)
    {
    InitSeriesVisibilityDefaults();
    if (regex && regex[0])
      {
      SeriesVisibilityDefaults.push_back(SeriesVisibilityPair(
          vtksys::RegularExpression(regex), value));
      }
    }

  static bool GetSeriesVisibilityDefault(const char* regex, bool &value)
    {
    InitSeriesVisibilityDefaults();
    for (size_t cc=0; cc < SeriesVisibilityDefaults.size(); cc++)
      {
      if (SeriesVisibilityDefaults[cc].first.find(regex))
        {
        value = SeriesVisibilityDefaults[cc].second;
        return true;
        }
      }
    return false;
    }

}

class vtkSMChartSeriesSelectionDomain::vtkInternals
{
  int ColorCounter;
  vtkNew<vtkColorSeries> Colors;
public:
  std::map<std::string, bool> VisibilityOverrides;

  vtkInternals() : ColorCounter(0)
    {
    }

  vtkColor3ub GetNextColor()
    {
    return this->Colors->GetColorRepeating(this->ColorCounter++);
    }
};

//----------------------------------------------------------------------------
vtkSMChartSeriesSelectionDomain::vtkSMChartSeriesSelectionDomain() :
  Internals(new vtkSMChartSeriesSelectionDomain::vtkInternals())
{
  this->DefaultMode = vtkSMChartSeriesSelectionDomain::UNDEFINED;
  this->DefaultValue = 0;
  this->SetDefaultValue("");
  this->FlattenTable = true;

  this->AddObserver(vtkCommand::DomainModifiedEvent,
    this, &vtkSMChartSeriesSelectionDomain::OnDomainModified);
}

//----------------------------------------------------------------------------
vtkSMChartSeriesSelectionDomain::~vtkSMChartSeriesSelectionDomain()
{
  this->SetDefaultValue(NULL);
  delete this->Internals;
  this->Internals = NULL;
}

//----------------------------------------------------------------------------
vtkPVDataInformation* vtkSMChartSeriesSelectionDomain::GetInputInformation()
{
  vtkSMProperty* inputProperty = this->GetRequiredProperty("Input");
  assert(inputProperty);

  vtkSMUncheckedPropertyHelper helper(inputProperty);
  if (helper.GetNumberOfElements() > 0)
    {
    vtkSMSourceProxy* sp = vtkSMSourceProxy::SafeDownCast(helper.GetAsProxy(0));
    if (sp)
      {
      return sp->GetDataInformation(helper.GetOutputPort());
      }
    }
  return NULL;
}

//----------------------------------------------------------------------------
int vtkSMChartSeriesSelectionDomain::ReadXMLAttributes(
  vtkSMProperty* prop, vtkPVXMLElement* element)
{
  if (!this->Superclass::ReadXMLAttributes(prop, element))
    {
    return 0;
    }

  const char* default_mode = element->GetAttribute("default_mode");
  if (default_mode)
    {
    if (strcmp(default_mode, "visibility") == 0)
      {
      this->DefaultMode = vtkSMChartSeriesSelectionDomain::VISIBILITY;
      }
    else if (strcmp(default_mode, "label") == 0)
      {
      this->DefaultMode = vtkSMChartSeriesSelectionDomain::LABEL;
      }
    else if (strcmp(default_mode, "color") == 0)
      {
      this->DefaultMode = vtkSMChartSeriesSelectionDomain::COLOR;
      }
    else if (strcmp(default_mode, "value") == 0)
      {
      this->DefaultMode = vtkSMChartSeriesSelectionDomain::VALUE;
      if (element->GetAttribute("default_value"))
        {
        this->SetDefaultValue(element->GetAttribute("default_value"));
        }
      }
    }

  const char* flatten_table = element->GetAttribute("flatten_table");
  if (flatten_table)
    {
    this->FlattenTable = (strcmp(default_mode, "true") == 0);
    }
  return 1;
}

//----------------------------------------------------------------------------
void vtkSMChartSeriesSelectionDomain::Update(vtkSMProperty*)
{
  vtkSMProperty* input = this->GetRequiredProperty("Input");
  vtkSMProperty* fieldDataSelection =
    this->GetRequiredProperty("FieldDataSelection");
  vtkSMVectorProperty* compositeIndex = vtkSMVectorProperty::SafeDownCast(
    this->GetRequiredProperty("CompositeIndexSelection"));

  if (!input || !fieldDataSelection)
    {
    vtkWarningMacro("Missing required properties. Update failed.");
    return;
    }

  // build strings based on the current domain.
  vtkPVDataInformation* dataInfo = this->GetInputInformation();
  if (!dataInfo)
    {
    return;
    }

  // clear old component names.
  this->Internals->VisibilityOverrides.clear();

  if (compositeIndex == NULL ||
    dataInfo->GetCompositeDataInformation()->GetDataIsComposite() == 0)
    {
    // since there's no way to choose which dataset from a composite one to use,
    // just look at the top-level array information (skipping partial arrays).
    std::vector<vtkStdString> column_names;
    int fieldAssociation = vtkSMUncheckedPropertyHelper(fieldDataSelection).GetAsInt(0);
    this->PopulateAvailableArrays(std::string(),
      column_names, dataInfo, fieldAssociation, this->FlattenTable);
    this->SetStrings(column_names);
    return;
    }

  std::vector<vtkStdString> column_names;
  int fieldAssociation = vtkSMUncheckedPropertyHelper(fieldDataSelection).GetAsInt(0);

  vtkSMUncheckedPropertyHelper compositeIndexHelper(compositeIndex);
  unsigned int numElems = compositeIndexHelper.GetNumberOfElements();
  for (unsigned int cc=0; cc < numElems; cc++)
    {
    vtkPVDataInformation* childInfo =
      dataInfo->GetDataInformationForCompositeIndex(compositeIndexHelper.GetAsInt(cc));
    if (!childInfo)
      {
      continue;
      }
    std::ostringstream blockNameStream;
    if (compositeIndex->GetRepeatCommand())
      {
      // we don't need to add blockName is the proxy doesn't support selecting
      // multiple blocks in the dataset.
      if (childInfo->GetCompositeDataSetName())
        {
        blockNameStream << childInfo->GetCompositeDataSetName();
        }
      else
        {
        blockNameStream << compositeIndexHelper.GetAsInt(cc);
        }
      }
    // if there is only 1 element, use the element to avoid having partial data.
    vtkPVDataInformation* dataInfoWithArrays =
      numElems == 1 ? childInfo : dataInfo;
    this->PopulateAvailableArrays(blockNameStream.str(),
      column_names, dataInfoWithArrays, fieldAssociation,
      this->FlattenTable);
    }
  this->SetStrings(column_names);
}

//----------------------------------------------------------------------------
// Add arrays from dataInfo to strings. If blockName is non-empty, then it's
// used to "uniquify" the array names.
void vtkSMChartSeriesSelectionDomain::PopulateAvailableArrays(
  const std::string& blockName,
  std::vector<vtkStdString>& strings,
  vtkPVDataInformation* dataInfo, int fieldAssociation, bool flattenTable)
{
  // this method is typically called for leaf nodes (or multi-piece).
  // assert((dataInfo->GetCompositeDataInformation()->GetDataIsComposite() == 0) ||
  //   (dataInfo->GetCompositeDataInformation()->GetDataIsMultiPiece() == 0));
  vtkChartRepresentation* chartRepr = vtkChartRepresentation::SafeDownCast(
    this->GetProperty()->GetParent()->GetClientSideObject());
  if (!chartRepr)
    {
    return;
    }

  // helps use avoid duplicates. duplicates may arise for plot types that treat
  // multiple columns as a single series/plot e.g. quartile plots.
  std::set<vtkStdString> uniquestrings;

  vtkPVDataSetAttributesInformation* dsa =
    dataInfo->GetAttributeInformation(fieldAssociation);
  for (int cc=0; dsa != NULL && cc < dsa->GetNumberOfArrays(); cc++)
    {
    vtkPVArrayInformation* arrayInfo = dsa->GetArrayInformation(cc);
    if (arrayInfo == NULL || arrayInfo->GetIsPartial() == 1)
      {
      continue;
      }

    if (arrayInfo->GetName() &&
      strcmp(arrayInfo->GetName(), "vtkValidPointMask") == 0)
      {
      // Skip vtkValidPointMask since that's an internal array used for masking,
      // not really meant to be plotted.
      continue;
      }

    if (arrayInfo->GetNumberOfComponents() > 1 && flattenTable)
      {
      for (int kk=0; kk <= arrayInfo->GetNumberOfComponents(); kk++)
        {
        std::string component_name = vtkSMArrayListDomain::CreateMangledName(arrayInfo, kk);
        component_name = chartRepr->GetDefaultSeriesLabel(blockName, component_name);
        if (uniquestrings.find(component_name) == uniquestrings.end())
          {
          strings.push_back(component_name);
          uniquestrings.insert(component_name);
          }
        if (kk != arrayInfo->GetNumberOfComponents())
          {
          // save component names so we can detect them when setting defaults
          // later.
          this->SetDefaultVisibilityOverride(component_name, false);
          }
        }
      }
    else
      {
      std::string seriesName = chartRepr->GetDefaultSeriesLabel(blockName, arrayInfo->GetName());
      if (uniquestrings.find(seriesName) == uniquestrings.end())
        {
        strings.push_back(seriesName);
        uniquestrings.insert(seriesName);

        // Special case for Quartile plots. PlotSelectionOverTime filter, when
        // produces stats, likes to pre-split components in an array into multiple
        // single component arrays. We still want those to be treated as
        // components and not be shown by default. Hence, we use this hack.
        // (See BUG #15512).
        std::string seriesNameWithoutTableName = chartRepr->GetDefaultSeriesLabel(
          std::string(), arrayInfo->GetName());
        if (PotentailComponentNameRe.find(seriesNameWithoutTableName))
          {
          this->SetDefaultVisibilityOverride(seriesName, false);
          }
        }
      }
    }
}

//----------------------------------------------------------------------------
std::vector<vtkStdString> vtkSMChartSeriesSelectionDomain::GetDefaultValue(const char* series)
{
  std::vector<vtkStdString> values;
  if (this->DefaultMode == VISIBILITY)
    {
    values.push_back(
      this->GetDefaultSeriesVisibility(series)?
      "1" : "0");
    }
  else if (this->DefaultMode == LABEL)
    {
    // by default, label is same as the name of the series.
    values.push_back(series);
    }
  else if (this->DefaultMode == COLOR)
    {
    vtkColor3ub color = this->Internals->GetNextColor();
    for (int kk=0; kk < 3; kk++)
      {
      std::ostringstream stream;
      stream << std::setprecision(2) << color.GetData()[kk]/255.0;
      values.push_back(stream.str());
      }
    }
  else if (this->DefaultMode == VALUE)
    {
    values.push_back(this->DefaultValue);
    }
  return values;
}

//----------------------------------------------------------------------------
int vtkSMChartSeriesSelectionDomain::SetDefaultValues(
  vtkSMProperty* property, bool use_unchecked_values)
{
  vtkSMStringVectorProperty* vp = vtkSMStringVectorProperty::SafeDownCast(property);
  if (!vp)
    {
    vtkErrorMacro("Property must be a vtkSMVectorProperty subclass.");
    return 0;
    }
  if (use_unchecked_values)
    {
    vtkWarningMacro("Developer warning: use_unchecked_values not implemented yet.");
    }

  this->UpdateDefaultValues(property, false);
  return 1;
}

//----------------------------------------------------------------------------
void vtkSMChartSeriesSelectionDomain::UpdateDefaultValues(
  vtkSMProperty* property, bool preserve_previous_values)
{
  vtkSMStringVectorProperty* vp = vtkSMStringVectorProperty::SafeDownCast(property);
  assert(vp != NULL);

  vtkNew<vtkStringList> values;
  std::set<std::string> seriesNames;
  if (preserve_previous_values)
    {
    // capture old values.
    unsigned int numElems = vp->GetNumberOfElements();
    int stepSize = vp->GetNumberOfElementsPerCommand() > 0?
      vp->GetNumberOfElementsPerCommand() : 1;

    for (unsigned int cc=0; (cc+stepSize) <= numElems; cc+=stepSize)
      {
      seriesNames.insert(vp->GetElement(cc)? vp->GetElement(cc) : "");
      for (int kk=0; kk < stepSize; kk++)
        {
        values->AddString(vp->GetElement(cc+kk));
        }
      }
    }

  const std::vector<vtkStdString>& domain_strings = this->GetStrings();
  for (size_t cc=0; cc < domain_strings.size(); cc++)
    {
    if (preserve_previous_values &&
      seriesNames.find(domain_strings[cc]) != seriesNames.end())
      {
      // skip this. This series had a value set already which we are requested
      // to preserve.
      continue;
      }
    std::vector<vtkStdString> cur_values =
      this->GetDefaultValue(domain_strings[cc].c_str());
    if (cur_values.size() > 0)
      {
      values->AddString(domain_strings[cc].c_str());
      for (size_t kk=0; kk < cur_values.size(); kk++)
        {
        values->AddString(cur_values[kk].c_str());
        }
      }
    }

  vp->SetElements(values.GetPointer());
}

//----------------------------------------------------------------------------
void vtkSMChartSeriesSelectionDomain::AddSeriesVisibilityDefault(
  const char* name, bool value)
{
  ::AddSeriesVisibilityDefault(name, value);
}

//----------------------------------------------------------------------------
bool vtkSMChartSeriesSelectionDomain::GetDefaultSeriesVisibility(const char* name)
{
  bool result;
  if (::GetSeriesVisibilityDefault(name, result))
    {
    return result;
    }

  if (this->Internals->VisibilityOverrides.find(name) !=
    this->Internals->VisibilityOverrides.end())
    {
    //  hide components by default, we'll show the magnitudes for them.
    return this->Internals->VisibilityOverrides[name];
    }

  return true;
}

//----------------------------------------------------------------------------
void vtkSMChartSeriesSelectionDomain::OnDomainModified()
{
  vtkSMProperty* prop = this->GetProperty();
  this->UpdateDefaultValues(prop, true);
  if (prop->GetParent())
    {
    // FIXME:
    // prop->GetParent()->UpdateProperty(prop);
    prop->GetParent()->UpdateVTKObjects();
    }
}

//----------------------------------------------------------------------------
void vtkSMChartSeriesSelectionDomain::SetDefaultVisibilityOverride(
  const vtkStdString& arrayname, bool visibility)
{
  this->Internals->VisibilityOverrides[arrayname] = visibility;
}

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