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

  Program:   Visualization Toolkit
  Module:    vtkGeoAssignCoordinates.cxx

  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/Copyright.htm 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.

=========================================================================*/
/*-------------------------------------------------------------------------
  Copyright 2008 Sandia Corporation.
  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  the U.S. Government retains certain rights in this software.
-------------------------------------------------------------------------*/

#include "vtkGeoAssignCoordinates.h"

#include "vtkAbstractTransform.h"
#include "vtkGeoMath.h"
#include "vtkGlobeSource.h"
#include "vtkGraph.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointData.h"
#include "vtkPointSet.h"
#include "vtkPoints.h"

vtkCxxSetObjectMacro(vtkGeoAssignCoordinates, Transform, vtkAbstractTransform);
vtkStandardNewMacro(vtkGeoAssignCoordinates);

vtkGeoAssignCoordinates::vtkGeoAssignCoordinates()
{
  this->LongitudeArrayName = nullptr;
  this->LatitudeArrayName = nullptr;
  this->CoordinatesInArrays = true;
  this->Transform = nullptr;

  this->GlobeRadius = vtkGeoMath::EarthRadiusMeters();
}

vtkGeoAssignCoordinates::~vtkGeoAssignCoordinates()
{
  if (this->Transform)
  {
    this->Transform->Delete();
  }
  delete[] this->LongitudeArrayName;
  delete[] this->LatitudeArrayName;
}


int vtkGeoAssignCoordinates::RequestData(vtkInformation *vtkNotUsed(request),
                            vtkInformationVector **inputVector,
                            vtkInformationVector *outputVector)
{

  // get the info objects
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the input and output
  vtkDataObject *input = inInfo->Get(vtkDataObject::DATA_OBJECT());
  vtkDataObject *output = outInfo->Get(vtkDataObject::DATA_OBJECT());
  vtkGraph *graphInput = vtkGraph::SafeDownCast(input);
  vtkGraph *graphOutput = vtkGraph::SafeDownCast(output);
  vtkPointSet *psInput = vtkPointSet::SafeDownCast(input);
  vtkPointSet *psOutput = vtkPointSet::SafeDownCast(output);

  // Do a shallow copy of the input to the output
  // and then create new points on the output
  output->ShallowCopy(input);
  vtkPoints *newPoints = vtkPoints::New();
  vtkPoints *oldPoints = nullptr;
  vtkDataSetAttributes *pd = nullptr;
  vtkIdType numPoints = 0;
  if (graphInput)
  {
    oldPoints = graphInput->GetPoints();
    newPoints->DeepCopy(oldPoints);
    graphOutput->SetPoints(newPoints);
    pd = graphOutput->GetVertexData();
    numPoints = graphInput->GetNumberOfVertices();
  }
  else
  {
    oldPoints = psInput->GetPoints();
    newPoints->DeepCopy(oldPoints);
    psOutput->SetPoints(newPoints);
    pd = psOutput->GetPointData();
    numPoints = psInput->GetNumberOfPoints();
  }
  newPoints->Delete();

  // If there are no points in the input, we're done!
  if (numPoints == 0)
  {
    return 1;
  }

  vtkDataArray* latitudeArray = nullptr;
  vtkDataArray* longitudeArray = nullptr;
  if (this->CoordinatesInArrays)
  {
    // I need a latitude array
    if (!this->LatitudeArrayName || strlen(this->LatitudeArrayName) == 0)
    {
      vtkErrorMacro("No latitude array defined.");
      return 0;
    }

    // I need a longitude array
    if (!this->LongitudeArrayName || strlen(this->LongitudeArrayName) == 0)
    {  // If on, uses LatitudeArrayName and LongitudeArrayName to
      // move values in data arrays into the points of the data set.
      // Turn off if the latitude and longitude are already in
      // the points.

      vtkErrorMacro("No longitude array defined.");
      return 0;
    }

    // Okay now check for arrays
    latitudeArray = pd->GetArray(this->LatitudeArrayName);

    // Does the latitude array exist at all?
    if (this->CoordinatesInArrays && latitudeArray == nullptr)
    {
      vtkErrorMacro("Could not find array named " << this->LatitudeArrayName);
      return 0;
    }

    // Longitude coordinate array
    longitudeArray = pd->GetArray(this->LongitudeArrayName);

    // Does the array exist at all?
    if (this->CoordinatesInArrays && longitudeArray == nullptr)
    {
      vtkErrorMacro("Could not find array named " << this->LongitudeArrayName);
      return 0;
    }
  }

  // Convert the points to global coordinates
  for (int i = 0; i < numPoints; i++)
  {
    double theta, phi;
    if (this->CoordinatesInArrays)
    {
      theta = longitudeArray->GetTuple1(i);
      phi = latitudeArray->GetTuple1(i);
    }
    else
    {
      double a[3];
      oldPoints->GetPoint(i, a);
      theta = a[0];
      phi = a[1];
    }

    // Clamp to lat/long bounds
    theta = (theta >  180.0) ?  180.0 : theta;
    theta = (theta < -180.0) ? -180.0 : theta;
    phi = (phi >  90.0) ?  90.0 : phi;
    phi = (phi < -90.0) ? -90.0 : phi;

    double x[3];
    if (this->Transform)
    {
      double in[] = {theta, phi, 0.0};
      this->Transform->TransformPoint(in, x);
    }
    else
    {
      vtkGlobeSource::ComputeGlobePoint(theta, phi, this->GlobeRadius, x);
    }
    newPoints->SetPoint(i, x[0], x[1], x[2]);
  }

  return 1;
}

int vtkGeoAssignCoordinates::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
{
  // This algorithm may accept a vtkPointSet or vtkGraph.
  info->Remove(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE());
  info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkPointSet");
  info->Append(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(), "vtkGraph");
  return 1;
}

void vtkGeoAssignCoordinates::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os,indent);
  os << indent << "LatitudeArrayName: "
     << (this->LatitudeArrayName ? this->LatitudeArrayName : "(none)") << endl;
  os << indent << "LongitudeArrayName: "
     << (this->LongitudeArrayName ? this->LongitudeArrayName : "(none)") << endl;
  os << indent << "GlobeRadius: " << this->GlobeRadius << endl;
  os << indent << "CoordinatesInArrays: " << (this->CoordinatesInArrays ? "on" : "off") << endl;
  os << indent << "Transform: " << (this->Transform ? "" : "(none)") << endl;
  if (this->Transform)
  {
    this->Transform->PrintSelf(os, indent.GetNextIndent());
  }
}
