//=========================================================================
//  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 "pqCMBPoints.h"

#include "pqApplicationCore.h"
#include "pqDataRepresentation.h"
#include "pqDataRepresentation.h"
#include "pqObjectBuilder.h"
#include "pqPipelineSource.h"
#include "pqRenderView.h"
#include "pqSMAdaptor.h"
#include "pqServer.h"

#include "vtkImageData.h"
#include "vtkPVDataInformation.h"
#include "vtkPVLASOutputBlockInformation.h"
#include "vtkPVSceneGenObjectInformation.h"
#include "vtkSMNewWidgetRepresentationProxy.h"
#include "vtkSMProxyManager.h"
#include "vtkSMRepresentationProxy.h"
#include <QFileInfo>
#include <QVariant>
#include <vtkProcessModule.h>
#include <vtkSMDataSourceProxy.h>
#include <vtkSMDoubleVectorProperty.h>
#include <vtkSMIntVectorProperty.h>
#include <vtkSMPropertyHelper.h>
#include <vtkSMProxyProperty.h>
#include <vtkSMRenderViewProxy.h>
#include <vtkSMRepresentationProxy.h>
#include <vtkSMSourceProxy.h>
#include <vtkTransform.h>

#include "vtkNew.h"

pqCMBPoints::pqCMBPoints()
  : pqCMBTexturedObject()
{
  this->DoubleDataPrecision = false;
  this->InitialSurfaceTranslation[0] = InitialSurfaceTranslation[1] = InitialSurfaceTranslation[2] =
    0.0;
  this->setPieceId(-1);
  this->PieceOnRatio = 1;
  this->PieceTotalNumberOfPoints = -1;
  this->UserDefinedType = "Points";
}

pqCMBPoints::pqCMBPoints(
  pqPipelineSource* source, pqRenderView* view, pqServer* server, const char* filename)
  : pqCMBTexturedObject(source, view, server)
{
  this->setPieceId(-1);
  this->PieceOnRatio = 1;
  this->PieceTotalNumberOfPoints = -1;

  this->FileName = filename;
  this->DoubleDataPrecision = false;
  this->InitialSurfaceTranslation[0] = InitialSurfaceTranslation[1] = InitialSurfaceTranslation[2] =
    0.0;

  this->UserDefinedType = "Points";
}

pqCMBPoints::pqCMBPoints(
  pqPipelineSource* source, pqRenderView* view, pqServer* server, bool updateRep)
  : pqCMBTexturedObject(source, view, server)
{
  this->setPieceId(-1);
  this->PieceOnRatio = 1;
  this->PieceTotalNumberOfPoints = -1;

  this->DoubleDataPrecision = false;
  this->InitialSurfaceTranslation[0] = InitialSurfaceTranslation[1] = InitialSurfaceTranslation[2] =
    0.0;

  this->UserDefinedType = "Points";

  if (updateRep)
  {
    this->getRepresentation()->getProxy()->UpdateVTKObjects();
  }
}

pqCMBPoints::pqCMBPoints(
  const char* filename, pqServer* server, pqRenderView* view, int maxNumberOfPoints, bool updateRep)
{
  pqApplicationCore* core = pqApplicationCore::instance();
  pqObjectBuilder* builder = core->getObjectBuilder();
  QStringList files;
  files << filename;

  builder->blockSignals(true);
  pqPipelineSource* source;
  QList<pqCMBPoints*> dataObjects;
  source = builder->createReader("sources", "LIDARReader", files, server);

  vtkSMPropertyHelper(source->getProxy(), "MaxNumberOfPoints").Set(maxNumberOfPoints);
  vtkSMPropertyHelper(source->getProxy(), "LimitToMaxNumberOfPoints").Set(1);
  source->getProxy()->UpdateVTKObjects();
  vtkSMSourceProxy::SafeDownCast(source->getProxy())->UpdatePipeline();
  builder->blockSignals(false);

  this->initialize(source, server, view, updateRep);
  this->setFileName(filename);
  this->setReaderSource(source);
  this->setPieceId(-1);
  this->PieceOnRatio = 1;
  this->PieceTotalNumberOfPoints = -1;
  this->DoubleDataPrecision = false;
  source->getProxy()->UpdatePropertyInformation();
  vtkNew<vtkPVSceneGenObjectInformation> info;
  source->getProxy()->GatherInformation(info.GetPointer());
  int totalNumberOfPoints =
    pqSMAdaptor::getElementProperty(source->getProxy()->GetProperty("TotalNumberOfPoints")).toInt();
  this->setPieceTotalNumberOfPoints(totalNumberOfPoints);
  this->UserDefinedType = "Points";

  if (updateRep)
  {
    this->getRepresentation()->getProxy()->UpdateVTKObjects();
  }
}

pqCMBPoints::pqCMBPoints(pqServer* server, pqRenderView* view, pqPipelineSource* source,
  int pieceIndex, int onRatio, bool doublePrecision)
{
  pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();
  vtkSMSourceProxy* sourceProxy = vtkSMSourceProxy::SafeDownCast(source->getProxy());

  QList<QVariant> pieceOnRatioList;
  pieceOnRatioList << pieceIndex << onRatio;

  pqSMAdaptor::setMultipleElementProperty(
    sourceProxy->GetProperty("RequestedPiecesForRead"), pieceOnRatioList);
  pqSMAdaptor::setElementProperty(
    sourceProxy->GetProperty("OutputDataTypeIsDouble"), doublePrecision);
  sourceProxy->UpdateVTKObjects();
  sourceProxy->UpdatePipeline();

  int numberOfPointsInPiece;
  pqPipelineSource* pdSource = builder->createSource("sources", "HydroModelPolySource", server);
  // if from the LAS reader then need to extract the output from the multiblock
  if (!strcmp("vtkLASReader", sourceProxy->GetVTKClassName()))
  {
    pqPipelineSource* extract = builder->createFilter("filters", "ExtractLeafBlock", source);

    // expecting only 1 block
    pqSMAdaptor::setElementProperty(extract->getProxy()->GetProperty("BlockIndex"), 0);
    extract->getProxy()->UpdateVTKObjects();
    vtkSMSourceProxy::SafeDownCast(extract->getProxy())->UpdatePipeline();

    vtkSMDataSourceProxy::SafeDownCast(pdSource->getProxy())
      ->CopyData(vtkSMSourceProxy::SafeDownCast(extract->getProxy()));
    pdSource->updatePipeline();

    vtkNew<vtkPVLASOutputBlockInformation> info;
    pdSource->getProxy()->GatherInformation(info.GetPointer());
    numberOfPointsInPiece = info->GetNumberOfPointsInClassification();
    builder->destroy(extract);
  }
  else
  {
    vtkSMDataSourceProxy::SafeDownCast(pdSource->getProxy())->CopyData(sourceProxy);
    // get total # of points in piece, and save the value
    pqSMAdaptor::setElementProperty(sourceProxy->GetProperty("PieceIndex"), pieceIndex);
    sourceProxy->UpdateVTKObjects();
    sourceProxy->UpdatePropertyInformation();

    numberOfPointsInPiece =
      pqSMAdaptor::getElementProperty(sourceProxy->GetProperty("NumberOfPointsInPiece")).toInt();
  }
  this->initialize(pdSource, server, view, false);
  this->setPieceTotalNumberOfPoints(numberOfPointsInPiece);
  this->setReaderSource(source);
  this->setPieceId(pieceIndex);
  this->setPieceOnRatio(onRatio);
  this->setDoubleDataPrecision(doublePrecision);
  this->UserDefinedType = "Points";
}

pqCMBPoints::~pqCMBPoints()
{
}

void pqCMBPoints::setReaderSource(pqPipelineSource* source)
{
  this->ReaderSource = source;
}

pqPipelineSource* pqCMBPoints::getReaderSource() const
{
  return this->ReaderSource;
}

pqPipelineSource* pqCMBPoints::getTransformedSource(pqServer* server) const
{
  vtkSmartPointer<vtkTransform> transform = vtkSmartPointer<vtkTransform>::New();
  this->getTransform(transform);
  vtkMatrix4x4* matrix = transform->GetMatrix();
  // if non-identity transform... need to transform the data
  if (matrix->Element[0][0] != 1 || matrix->Element[0][1] != 0 || matrix->Element[0][2] != 0 ||
    matrix->Element[0][3] != 0 || matrix->Element[1][0] != 0 || matrix->Element[1][1] != 1 ||
    matrix->Element[1][2] != 0 || matrix->Element[1][3] != 0 || matrix->Element[2][0] != 0 ||
    matrix->Element[2][1] != 0 || matrix->Element[2][2] != 1 || matrix->Element[2][3] != 0 ||
    matrix->Element[3][0] != 0 || matrix->Element[3][1] != 0 || matrix->Element[3][2] != 0 ||
    matrix->Element[3][3] != 1)
  {
    QList<QVariant> values;
    for (int i = 0; i < 4; i++)
    {
      for (int j = 0; j < 4; j++)
      {
        values << matrix->Element[i][j];
      }
    }
    pqObjectBuilder* builder = pqApplicationCore::instance()->getObjectBuilder();

    vtkSMProxy* transformProxy =
      builder->createProxy("transforms", "Transform", server, "transforms");
    pqSMAdaptor::setMultipleElementProperty(transformProxy->GetProperty("Matrix"), values);
    transformProxy->UpdateVTKObjects();

    pqPipelineSource* transformFilter =
      builder->createFilter("filters", "TransformFilter", this->Source);
    pqSMAdaptor::setProxyProperty(
      transformFilter->getProxy()->GetProperty("Transform"), transformProxy);
    transformFilter->getProxy()->UpdateVTKObjects();
    vtkSMSourceProxy::SafeDownCast(transformFilter->getProxy())->UpdatePipeline();

    pqPipelineSource* pdSource = builder->createSource("sources", "HydroModelPolySource", server);
    vtkSMDataSourceProxy::SafeDownCast(pdSource->getProxy())
      ->CopyData(vtkSMSourceProxy::SafeDownCast(transformFilter->getProxy()));
    builder->destroy(transformFilter);

    return pdSource;
  }
  return this->Source;
}

pqCMBSceneObjectBase* pqCMBPoints::duplicate(pqServer* server, pqRenderView* view, bool updateRep)
{
  pqApplicationCore* core = pqApplicationCore::instance();
  pqObjectBuilder* builder = core->getObjectBuilder();

  pqPipelineSource* pdSource = builder->createSource("sources", "HydroModelPolySource", server);

  vtkSMDataSourceProxy::SafeDownCast(pdSource->getProxy())
    ->CopyData(vtkSMSourceProxy::SafeDownCast(this->Source->getProxy()));

  pqCMBPoints* nobj = new pqCMBPoints(pdSource, view, server, this->FileName.c_str());
  this->duplicateInternals(nobj);

  if (updateRep)
  {
    nobj->getRepresentation()->getProxy()->UpdateVTKObjects();
  }

  nobj->DoubleDataPrecision = this->DoubleDataPrecision;
  nobj->InitialSurfaceTranslation[0] = this->InitialSurfaceTranslation[0];
  nobj->InitialSurfaceTranslation[1] = this->InitialSurfaceTranslation[1];
  nobj->InitialSurfaceTranslation[2] = this->InitialSurfaceTranslation[2];
  nobj->ReaderSource = this->ReaderSource;
  nobj->FileName = this->FileName;
  nobj->PieceTotalNumberOfPoints = this->PieceTotalNumberOfPoints;
  nobj->PieceId = this->PieceId;
  nobj->PieceOnRatio = this->PieceOnRatio;
  return nobj;
}

void pqCMBPoints::initialize(
  pqPipelineSource* source, pqServer* server, pqRenderView* view, bool updateRep)
{
  this->InitialSurfaceTranslation[0] = InitialSurfaceTranslation[1] = InitialSurfaceTranslation[2] =
    0.0;
  this->Source = source;
  this->prepTexturedObject(server, view);
  if (updateRep)
  {
    this->getRepresentation()->getProxy()->UpdateVTKObjects();
  }
}

bool pqCMBPoints::isPointsFile(const char* filename)
{
  QFileInfo finfo(filename);
  if (finfo.completeSuffix().toLower() == "pts" || finfo.completeSuffix().toLower() == "bin" ||
    finfo.completeSuffix().toLower() == "bin.pts" || finfo.completeSuffix().toLower() == "las")
  {
    return true;
  }
  return false;
}

void pqCMBPoints::setInitialSurfaceTranslation(double translation[3])
{
  this->InitialSurfaceTranslation[0] = translation[0];
  this->InitialSurfaceTranslation[1] = translation[1];
  this->InitialSurfaceTranslation[2] = translation[2];
}

void pqCMBPoints::getInitialSurfaceTranslation(double translation[3]) const
{
  translation[0] = this->InitialSurfaceTranslation[0];
  translation[1] = this->InitialSurfaceTranslation[1];
  translation[2] = this->InitialSurfaceTranslation[2];
}

pqCMBSceneObjectBase::enumObjectType pqCMBPoints::getType() const
{
  return pqCMBSceneObjectBase::Points;
}
