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

  Program:   Visualization Toolkit
  Module:    vtkGeoTerrain.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 "vtkGeoTerrain.h"

#include "vtkActor.h"
#include "vtkAssembly.h"
#include "vtkBox.h"
#include "vtkCamera.h"
#include "vtkCellArray.h"
#include "vtkClipPolyData.h"
#include "vtkCollection.h"
#include "vtkDoubleArray.h"
#include "vtkExtractSelectedFrustum.h"
#include "vtkGeoAlignedImageRepresentation.h"
#include "vtkGeoCamera.h"
#include "vtkGeoImageNode.h"
#include "vtkGeoInteractorStyle.h"
#include "vtkGeoTreeNodeCache.h"
#include "vtkGeoSource.h"
#include "vtkGeoTerrainNode.h"
#include "vtkImageData.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPlane.h"
#include "vtkPlanes.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkPolyDataMapper.h"
#include "vtkProp3DCollection.h"
#include "vtkProperty.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkSmartPointer.h"
#include "vtkTimerLog.h"
#include "vtkTransformFilter.h"
#include "vtkXMLPolyDataWriter.h"

#include <stack>
#include <utility>

vtkStandardNewMacro(vtkGeoTerrain);
vtkCxxSetObjectMacro(vtkGeoTerrain, GeoSource, vtkGeoSource);
vtkCxxSetObjectMacro(vtkGeoTerrain, GeoCamera, vtkGeoCamera);
//----------------------------------------------------------------------------
vtkGeoTerrain::vtkGeoTerrain()
{
  this->GeoSource = 0;
  this->Root = vtkGeoTerrainNode::New();
  this->Origin[0] = 0.0;
  this->Origin[1] = 0.0;
  this->Origin[2] = 0.0;
  this->Extractor = vtkExtractSelectedFrustum::New();
  this->GeoCamera = 0;
  this->MaxLevel = 20;
  this->Cache = vtkGeoTreeNodeCache::New();
}

//----------------------------------------------------------------------------
vtkGeoTerrain::~vtkGeoTerrain()
{
  this->SetGeoSource(0);
  this->SetGeoCamera(0);
  if (this->Root)
  {
    this->Root->Delete();
  }
  if (this->Extractor)
  {
    this->Extractor->Delete();
  }
  if (this->Cache)
  {
    this->Cache->Delete();
  }
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::SetSource(vtkGeoSource* source)
{
  if (this->GeoSource != source)
  {
    this->SetGeoSource(source);
    if (this->GeoSource)
    {
      this->Initialize();
    }
  }
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::Initialize()
{
  if (!this->GeoSource)
  {
    vtkErrorMacro(<< "Must set source before initializing.");
    return;
  }

  // Start by fetching the root.
  this->GeoSource->FetchRoot(this->Root);
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::InitializeNodeAnalysis(vtkRenderer* ren)
{
  vtkGeoInteractorStyle* style = vtkGeoInteractorStyle::SafeDownCast(
    ren->GetRenderWindow()->GetInteractor()->GetInteractorStyle());
  if (!style)
  {
    vtkErrorMacro("vtkGeoTerrain requires vtkGeoInteractorStyle in order to get geo camera.");
    return;
  }
  vtkGeoCamera* camera = style->GetGeoCamera();
  int* rendererSize = ren->GetSize();
  camera->InitializeNodeAnalysis(rendererSize);
  this->SetGeoCamera(camera);

  // Setup the frustum extractor for finding
  // node intersections with the view frustum.
  double frustumPlanes[24];
  double aspect = ren->GetTiledAspectRatio();
  camera->GetVTKCamera()->GetFrustumPlanes( aspect, frustumPlanes );
  vtkSmartPointer<vtkPlanes> frustum = vtkSmartPointer<vtkPlanes>::New();
  frustum->SetFrustumPlanes(frustumPlanes);
  this->Extractor->SetFrustum(frustum);
}

//----------------------------------------------------------------------------
bool vtkGeoTerrain::NodeInViewport(vtkGeoTerrainNode* cur)
{
  // Determine if node is within viewport
  double bbox[6];
  cur->GetModel()->GetBounds(bbox);
  for (int i = 0; i < 6; ++i)
  {
    bbox[i] = bbox[i] - this->GeoCamera->GetOrigin()[i/2];
  }
  int boundsTest = this->Extractor->OverallBoundsTest(bbox);
  return (boundsTest != 0);
}

//-----------------------------------------------------------------------------
// Returns 0 if there should be no change, -1 if the node resolution is too
// high, and +1 if the nodes resolution is too low.
int vtkGeoTerrain::EvaluateNode(vtkGeoTerrainNode* node)
{
  double sphereViewSize;

  if (!this->GeoCamera)
  {
    return 0;
  }

  // Size of the sphere in view area units (0 -> 1)
  sphereViewSize = this->GeoCamera->GetNodeCoverage(node);

  // Arbitrary thresholds
  if (sphereViewSize > 0.2)
  {
    return 1;
  }
  if (sphereViewSize < 0.05)
  {
    return -1;
  }
  // Do not change the node.
  return 0;
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::AddActors(
  vtkRenderer* ren,
  vtkAssembly* assembly,
  vtkCollection* imageReps)
{
  // This method requires that the render window graphics context
  // has been created.
  ren->GetRenderWindow()->MakeCurrent();
  if (!ren->GetRenderWindow()->IsCurrent())
  {
    return;
  }

  this->InitializeNodeAnalysis(ren);

  // Disable multitexturing, using deprecated API.
  bool multiTexturing = false;
  int textureUnits = 0;

  // Extract the image representations from the collection.
  vtkGeoAlignedImageRepresentation* textureTree1 = 0;
  if (imageReps->GetNumberOfItems() >= 1)
  {
    textureTree1 = vtkGeoAlignedImageRepresentation::SafeDownCast(
      imageReps->GetItemAsObject(0));
  }
  vtkGeoAlignedImageRepresentation* textureTree2 = 0;
  if (imageReps->GetNumberOfItems() >= 2)
  {
    textureTree2 = vtkGeoAlignedImageRepresentation::SafeDownCast(
      imageReps->GetItemAsObject(1));
  }

  int visibleActors = 0;

  vtkProp3DCollection* props = assembly->GetParts();
  vtkDebugMacro("Number Of Props: " << props->GetNumberOfItems());
  vtkSmartPointer<vtkTimerLog> timer = vtkSmartPointer<vtkTimerLog>::New();
  timer->StartTimer();

  // Remove actors at the beginning of the actor list until there are at most
  // 100 actors.
  while (props->GetNumberOfItems() > 100)
  {
    assembly->RemovePart(vtkActor::SafeDownCast(props->GetItemAsObject(0)));
  }

  // First turn off visibility of all actors
  for (int p = 0; p < props->GetNumberOfItems(); ++p)
  {
    vtkActor* actor = vtkActor::SafeDownCast(props->GetItemAsObject(p));
    actor->VisibilityOff();
  }

  // Use stack rather than recursion
  std::stack<vtkGeoTerrainNode*> s;
  s.push(this->Root);

  vtkGeoTerrainNode* child = NULL;
  vtkCollection* coll = NULL;

  double llbounds[4];
  while (!s.empty())
  {
    vtkGeoTerrainNode* cur = s.top();
    s.pop();
    if (!cur->HasData() || cur->GetModel()->GetNumberOfCells() == 0)
    {
      continue;
    }

    if (!this->NodeInViewport(cur))
    {
      // Totally outside, so prune node and subtree
      continue;
    }

    // Mark this node as "visited" so it will be less likely to
    // be deleted.
    this->Cache->SendToFront(cur);

    // Determine whether to traverse this node's children
    int refine = this->EvaluateNode(cur);

    child = cur->GetChild(0);
    if (((!child || !child->HasData()) &&
         cur->GetLevel() < this->MaxLevel &&
         refine == 1) || cur->GetStatus() == vtkGeoTreeNode::PROCESSING)
    {
      coll = this->GeoSource->GetRequestedNodes(cur);
      // Load children
      if (coll != NULL && coll->GetNumberOfItems() == 4)
      {
        for (int c = 0; c < 4; ++c)
        {
          child = vtkGeoTerrainNode::SafeDownCast(coll->GetItemAsObject(c));
          vtkGeoTerrainNode* oldChild = cur->GetChild(c);
          if (oldChild)
          {
            this->Cache->RemoveNode(oldChild);
          }
          this->Cache->SendToFront(child);
          cur->SetChild(child, c);
          child->SetParent(cur);
        }
        cur->SetStatus(vtkGeoTreeNode::NONE);
      }
      else if(cur->GetStatus() == vtkGeoTreeNode::NONE)
      {
        cur->SetStatus(vtkGeoTreeNode::PROCESSING);
        vtkGeoTerrainNode * temp = vtkGeoTerrainNode::New();
        temp->DeepCopy(cur);
        this->GeoSource->RequestChildren(temp);
      }
      if (coll)
      {
        coll->Delete();
      }
    }

    if (!cur->GetChild(0) || !cur->GetChild(0)->HasData() || refine != 1)
    {

      // Find the best texture for this geometry
      llbounds[0] = cur->GetLongitudeRange()[0];
      llbounds[1] = cur->GetLongitudeRange()[1];
      llbounds[2] = cur->GetLatitudeRange()[0];
      llbounds[3] = cur->GetLatitudeRange()[1];
      vtkGeoImageNode* textureNode1 = textureTree1->GetBestImageForBounds(llbounds);
      if (!textureNode1)
      {
        vtkWarningMacro(<< "could not find node for bounds: "
                        << llbounds[0] << ","
                        << llbounds[1] << ","
                        << llbounds[2] << ","
                        << llbounds[3]);
      }
      vtkGeoImageNode* textureNode2 = 0;
      if (textureTree2)
      {
        textureNode2 = textureTree2->GetBestImageForBounds(llbounds);
      }

      // See if we already have an actor for this geometry
      vtkActor* existingActor = 0;
      for (int p = 0; p < props->GetNumberOfItems(); ++p)
      {
        vtkActor* actor = vtkActor::SafeDownCast(props->GetItemAsObject(p));
        bool sameTexture = false;
        if (multiTexturing)
        {
          sameTexture = (
              !textureNode1 ||
              actor->GetProperty()->GetNumberOfTextures() < 1 ||
              actor->GetProperty()->GetTexture(vtkProperty::VTK_TEXTURE_UNIT_0) == textureNode1->GetTexture()
            ) && (
              !textureNode2 ||
              actor->GetProperty()->GetNumberOfTextures() < 2 ||
              actor->GetProperty()->GetTexture(vtkProperty::VTK_TEXTURE_UNIT_1) == textureNode2->GetTexture()
            );
        }
        else
        {
          sameTexture = !textureNode1 || actor->GetTexture() == textureNode1->GetTexture();
        }
        if (actor && actor->GetMapper()->GetInputDataObject(0, 0) == cur->GetModel() && sameTexture)
        {
          existingActor = actor;
          existingActor->VisibilityOn();
          visibleActors++;

          // Move the actor to the end of the list so it is less likely removed.
          actor->Register(this);
          assembly->RemovePart(actor);
          assembly->AddPart(actor);
          actor->Delete();
          break;
        }
      }
      if (existingActor)
      {
        continue;
      }

      // Add the data to the view
      vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
      vtkSmartPointer<vtkActor> actor = vtkSmartPointer<vtkActor>::New();
      mapper->SetInputData(cur->GetModel());
      mapper->ScalarVisibilityOff();
      actor->SetMapper(mapper);
      actor->SetPosition(-this->Origin[0], -this->Origin[1], -this->Origin[2] - 0.1);
      visibleActors++;

      if (textureNode1)
      {
        // Some implementations will report they support multi-texturing but have only 1
        // texture unit. Seriously!!
        if (multiTexturing && textureUnits > 1)
        {
          // Multi texturing
          mapper->MapDataArrayToMultiTextureAttribute(vtkProperty::VTK_TEXTURE_UNIT_0,
              "LatLong", vtkDataObject::FIELD_ASSOCIATION_POINTS);
          textureNode1->GetTexture()->SetBlendingMode(vtkTexture::VTK_TEXTURE_BLENDING_MODE_REPLACE);
          actor->GetProperty()->SetTexture(vtkProperty::VTK_TEXTURE_UNIT_0, textureNode1->GetTexture());

          if (textureNode2)
          {
            mapper->MapDataArrayToMultiTextureAttribute(vtkProperty::VTK_TEXTURE_UNIT_1,
                "LatLong", vtkDataObject::FIELD_ASSOCIATION_POINTS);
            textureNode2->GetTexture()->SetBlendingMode(vtkTexture::VTK_TEXTURE_BLENDING_MODE_ADD);
            actor->GetProperty()->SetTexture(vtkProperty::VTK_TEXTURE_UNIT_1, textureNode2->GetTexture());
          }
        }
        else
        {
          if(multiTexturing)
          {
            textureNode1->GetTexture()->SetBlendingMode(
              vtkTexture::VTK_TEXTURE_BLENDING_MODE_REPLACE);
          }
          // Single texturing
          cur->GetModel()->GetPointData()->SetActiveTCoords("LatLong");
          actor->SetTexture(textureNode1->GetTexture());
        }
        actor->GetProperty()->SetAmbient(1);
        assembly->AddPart(actor);
      }
      continue;
    }
    // Workaround for the isse where if refinement does not happen for some reason
    // then we don't see a tile as its visibility is turned off.
    else
    {
      llbounds[0] = cur->GetLongitudeRange()[0];
      llbounds[1] = cur->GetLongitudeRange()[1];
      llbounds[2] = cur->GetLatitudeRange()[0];
      llbounds[3] = cur->GetLatitudeRange()[1];
      vtkGeoImageNode* textureNode1 = textureTree1->GetBestImageForBounds(llbounds);
      vtkGeoImageNode* textureNode2 = 0;
      // See if we already have an actor for this geometry
      vtkActor* existingActor = 0;
      for (int p = 0; p < props->GetNumberOfItems(); ++p)
      {
        vtkActor* actor = vtkActor::SafeDownCast(props->GetItemAsObject(p));
        bool sameTexture = false;
        if (multiTexturing)
        {
          sameTexture = (
              !textureNode1 ||
              actor->GetProperty()->GetNumberOfTextures() < 1 ||
              actor->GetProperty()->GetTexture(vtkProperty::VTK_TEXTURE_UNIT_0) == textureNode1->GetTexture()
            ) && (
              !textureNode2 ||
              actor->GetProperty()->GetNumberOfTextures() < 2 ||
              actor->GetProperty()->GetTexture(vtkProperty::VTK_TEXTURE_UNIT_1) == textureNode2->GetTexture()
            );
        }
        else
        {
          sameTexture = !textureNode1 || actor->GetTexture() == textureNode1->GetTexture();
        }
        if (actor && actor->GetMapper()->GetInputDataObject(0, 0) == cur->GetModel() && sameTexture)
        {
          existingActor = actor;
          existingActor->VisibilityOn();
          visibleActors++;

          // Move the actor to the end of the list so it is less likely removed.
          actor->Register(this);
          assembly->RemovePart(actor);
          assembly->AddPart(actor);
          actor->Delete();
          break;
        }
      }
    }

    s.push(cur->GetChild(0));
    s.push(cur->GetChild(1));
    s.push(cur->GetChild(2));
    s.push(cur->GetChild(3));
  }

  timer->StopTimer();
  vtkDebugMacro("Visible Actors: " << visibleActors);
  vtkDebugMacro("AddActors time: " << timer->GetElapsedTime());
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::PrintSelf(ostream & os, vtkIndent indent)
{
  this->Superclass::PrintSelf( os, indent );
  os << indent << "GeoSource: " << this->GeoSource << "\n";
  os << indent << "Origin: (" << this->Origin[0] << ", "
     << this->Origin[1] << ", " << this->Origin[2] << ")\n";
  os << indent << "MaxLevel: " << this->MaxLevel << "\n";
  this->PrintTree(os, indent, this->Root); // Root
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::SaveDatabase(const char* path, int depth)
{
  if (!this->Root)
  {
    this->Initialize();
  }
  std::stack< vtkSmartPointer<vtkGeoTerrainNode> > s;
  s.push(this->Root);
  while (!s.empty())
  {
    vtkSmartPointer<vtkGeoTerrainNode> node = s.top();
    s.pop();

    // Write out file.
    vtkSmartPointer<vtkPolyData> storedData = vtkSmartPointer<vtkPolyData>::New();
    storedData->ShallowCopy(node->GetModel());
    vtkSmartPointer<vtkXMLPolyDataWriter> writer = vtkSmartPointer<vtkXMLPolyDataWriter>::New();
    char fn[512];
    sprintf(fn, "%s/tile_%d_%ld.vtp", path, node->GetLevel(), node->GetId());
    writer->SetFileName(fn);
    writer->SetInputData(storedData);
    writer->Write();

    if (node->GetLevel() == depth)
    {
      continue;
    }

    // Recurse over children.
    for (int i = 0; i < 4; ++i)
    {
      vtkSmartPointer<vtkGeoTerrainNode> child =
        vtkSmartPointer<vtkGeoTerrainNode>::New();
      if (this->GeoSource->FetchChild(node, i, child))
      {
        s.push(child);
      }
    }
  }
}

//----------------------------------------------------------------------------
void vtkGeoTerrain::PrintTree(ostream & os, vtkIndent indent, vtkGeoTerrainNode* parent)
{
  os << indent << "Error: " << parent->GetError() << endl;
  os << indent << "Level: " << parent->GetLevel() << "  "
               << "Id: " << parent->GetId() << endl;
  os << indent << "LatitudeRange: " << parent->GetLatitudeRange()[0]
    << "," << parent->GetLatitudeRange()[1] << endl;
  os << indent << "LongitudeRange: " << parent->GetLongitudeRange()[0]
    << "," << parent->GetLongitudeRange()[1] << endl;
  os << indent << "ProjectionBounds: " << parent->GetProjectionBounds()[0]
    << "," << parent->GetProjectionBounds()[1]
    << "," << parent->GetProjectionBounds()[2]
    << "," << parent->GetProjectionBounds()[3] << endl;
  os << indent << "Number of cells: " << parent->GetModel()->GetNumberOfCells() << endl;
  if (parent->GetChild(0) == 0)
  {
    return;
  }
  for (int i = 0; i < 4; ++i)
  {
    this->PrintTree(os, indent.GetNextIndent(), parent->GetChild(i));
  }
}

