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

  Program:   Visualization Toolkit
  Module:    vtkAngleRepresentation2D.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.

=========================================================================*/
#include "vtkAngleRepresentation2D.h"
#include "vtkCoordinate.h"
#include "vtkInteractorObserver.h"
#include "vtkLeaderActor2D.h"
#include "vtkMath.h"
#include "vtkObjectFactory.h"
#include "vtkPointHandleRepresentation2D.h"
#include "vtkRenderer.h"
#include "vtkWindow.h"

vtkStandardNewMacro(vtkAngleRepresentation2D);

//----------------------------------------------------------------------
vtkAngleRepresentation2D::vtkAngleRepresentation2D()
{
  // By default, use one of these handles
  this->HandleRepresentation = vtkPointHandleRepresentation2D::New();

  this->Ray1 = vtkLeaderActor2D::New();
  this->Ray1->GetPositionCoordinate()->SetCoordinateSystemToWorld();
  this->Ray1->GetPosition2Coordinate()->SetCoordinateSystemToWorld();
  this->Ray1->SetArrowStyleToOpen();
  this->Ray1->SetArrowPlacementToPoint2();

  this->Ray2 = vtkLeaderActor2D::New();
  this->Ray2->GetPositionCoordinate()->SetCoordinateSystemToWorld();
  this->Ray2->GetPosition2Coordinate()->SetCoordinateSystemToWorld();
  this->Ray2->SetArrowStyleToOpen();
  this->Ray2->SetArrowPlacementToPoint2();

  this->Arc = vtkLeaderActor2D::New();
  this->Arc->GetPositionCoordinate()->SetCoordinateSystemToWorld();
  this->Arc->GetPosition2Coordinate()->SetCoordinateSystemToWorld();
  this->Arc->SetArrowPlacementToNone();
  this->Arc->SetLabel("Angle");
  this->Arc->SetLabelFormat(this->LabelFormat);
  //  this->Arc->AutoLabelOn();
}

//----------------------------------------------------------------------
vtkAngleRepresentation2D::~vtkAngleRepresentation2D()
{
  this->Ray1->Delete();
  this->Ray2->Delete();
  this->Arc->Delete();
}

//----------------------------------------------------------------------
double vtkAngleRepresentation2D::GetAngle()
{
  return this->Arc->GetAngle();
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::GetPoint1WorldPosition(double pos[3])
{
  if (this->Point1Representation)
  {
    this->Point1Representation->GetWorldPosition(pos);
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::GetCenterWorldPosition(double pos[3])
{
  if (this->CenterRepresentation)
  {
    this->CenterRepresentation->GetWorldPosition(pos);
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::GetPoint2WorldPosition(double pos[3])
{
  if (this->Point2Representation)
  {
    this->Point2Representation->GetWorldPosition(pos);
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::SetPoint1DisplayPosition(double x[3])
{
  if (!this->Point1Representation)
  {
    vtkErrorMacro("SetPoint1DisplayPosition: no point1 representation");
    return;
  }
  this->Point1Representation->SetDisplayPosition(x);
  double p[3];
  this->Point1Representation->GetWorldPosition(p);
  this->Point1Representation->SetWorldPosition(p);
  this->BuildRepresentation();
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::SetCenterDisplayPosition(double x[3])
{
  if (!this->CenterRepresentation)
  {
    vtkErrorMacro("SetCenterDisplayPosition: no center representation");
    return;
  }
  this->CenterRepresentation->SetDisplayPosition(x);
  double p[3];
  this->CenterRepresentation->GetWorldPosition(p);
  this->CenterRepresentation->SetWorldPosition(p);
  this->BuildRepresentation();
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::SetPoint2DisplayPosition(double x[3])
{
  if (!this->Point2Representation)
  {
    vtkErrorMacro("SetPoint2DisplayPosition: no point2 representation");
    return;
  }
  this->Point2Representation->SetDisplayPosition(x);
  double p[3];
  this->Point2Representation->GetWorldPosition(p);
  this->Point2Representation->SetWorldPosition(p);
  this->BuildRepresentation();
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::GetPoint1DisplayPosition(double pos[3])
{
  if (this->Point1Representation)
  {
    this->Point1Representation->GetDisplayPosition(pos);
    pos[2] = 0.0;
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::GetCenterDisplayPosition(double pos[3])
{
  if (this->CenterRepresentation)
  {
    this->CenterRepresentation->GetDisplayPosition(pos);
    pos[2] = 0.0;
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::GetPoint2DisplayPosition(double pos[3])
{
  if (this->Point2Representation)
  {
    this->Point2Representation->GetDisplayPosition(pos);
    pos[2] = 0.0;
  }
  else
  {
    pos[0] = pos[1] = pos[2] = 0.0;
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::BuildRepresentation()
{
  if (this->Point1Representation == nullptr || this->CenterRepresentation == nullptr ||
    this->Point2Representation == nullptr || this->Arc == nullptr)
  {
    // for now, return. Could create defaults here.
    return;
  }

  if (this->GetMTime() > this->BuildTime ||
    this->Point1Representation->GetMTime() > this->BuildTime ||
    this->CenterRepresentation->GetMTime() > this->BuildTime ||
    this->Point2Representation->GetMTime() > this->BuildTime ||
    (this->Renderer && this->Renderer->GetVTKWindow() &&
      this->Renderer->GetVTKWindow()->GetMTime() > this->BuildTime))
  {
    this->Superclass::BuildRepresentation();

    // Local coordinate values
    double p1w[3], p2w[3], cw[3], p1d[3], p2d[3], cd[3], vector2[3], vector1[3];
    this->GetPoint1WorldPosition(p1w);
    this->GetCenterWorldPosition(cw);
    this->GetPoint2WorldPosition(p2w);
    this->GetPoint1DisplayPosition(p1d);
    this->GetCenterDisplayPosition(cd);
    this->GetPoint2DisplayPosition(p2d);

    // Update the rays
    this->Ray1->GetPosition2Coordinate()->SetValue(p1w);
    this->Ray1->GetPositionCoordinate()->SetValue(cw);
    this->Ray2->GetPositionCoordinate()->SetValue(cw);
    this->Ray2->GetPosition2Coordinate()->SetValue(p2w);

    // Compute the angle.
    // NOTE: There is some concern that there may be fluctuations in the angle
    // value as the camera moves, etc. This calculation may have to be dampened.
    vector1[0] = p1w[0] - cw[0];
    vector1[1] = p1w[1] - cw[1];
    vector1[2] = p1w[2] - cw[2];
    vector2[0] = p2w[0] - cw[0];
    vector2[1] = p2w[1] - cw[1];
    vector2[2] = p2w[2] - cw[2];
    vtkMath::Normalize(vector1);
    vtkMath::Normalize(vector2);
    double angle = acos(vtkMath::Dot(vector1, vector2));
    char string[512];
    snprintf(string, sizeof(string), this->LabelFormat, vtkMath::DegreesFromRadians(angle));
    this->Arc->SetLabel(string);

    // Place the label and place the arc
    double l1 = sqrt(vtkMath::Distance2BetweenPoints(cd, p1d));
    double l2 = sqrt(vtkMath::Distance2BetweenPoints(cd, p2d));

    // If too small or no render get out
    if (l1 <= 5.0 || l2 <= 5.0 || !this->Renderer)
    {
      this->ArcVisibility = 0;
      return;
    }

    // Place the end points for the arc away from the tip of the two rays
    this->ArcVisibility = 1;
    this->Arc->SetLabelFormat(this->LabelFormat);
    const double rayPosition = 0.80;
    int i;
    double a1[3], a2[3], t1, t2, w1[4], w2[4], radius;
    double ray1[3], ray2[3], v[3], z[3];
    if (l1 < l2)
    {
      radius = rayPosition * l1;
      t1 = rayPosition;
      t2 = (l1 / l2) * rayPosition;
    }
    else
    {
      radius = rayPosition * l2;
      t1 = (l2 / l1) * rayPosition;
      t2 = rayPosition;
    }
    for (i = 0; i < 3; i++)
    {
      ray1[i] = p1d[i] - cd[i];
      ray2[i] = p2d[i] - cd[i];
      a1[i] = cd[i] + t1 * ray1[i];
      a2[i] = cd[i] + t2 * ray2[i];
    }
    double l = sqrt(vtkMath::Distance2BetweenPoints(a1, a2));
    vtkInteractorObserver::ComputeDisplayToWorld(this->Renderer, a1[0], a1[1], a1[2], w1);
    vtkInteractorObserver::ComputeDisplayToWorld(this->Renderer, a2[0], a2[1], a2[2], w2);
    this->Arc->GetPositionCoordinate()->SetValue(w1);
    this->Arc->GetPosition2Coordinate()->SetValue(w2);
    if (l <= 0.0)
    {
      this->Arc->SetRadius(0.0);
    }
    else
    {
      vtkMath::Cross(ray1, ray2, v);
      z[0] = z[1] = 0.0;
      z[2] = 1.0;
      if (vtkMath::Dot(v, z) > 0.0)
      {
        this->Arc->SetRadius(-radius / l);
      }
      else
      {
        this->Arc->SetRadius(radius / l);
      }
    }
    this->BuildTime.Modified();
  }
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::ReleaseGraphicsResources(vtkWindow* w)
{
  if (this->Ray1)
  {
    this->Ray1->ReleaseGraphicsResources(w);
  }
  if (this->Ray2)
  {
    this->Ray2->ReleaseGraphicsResources(w);
  }
  if (this->Arc)
  {
    this->Arc->ReleaseGraphicsResources(w);
  }
}

//----------------------------------------------------------------------
int vtkAngleRepresentation2D::RenderOverlay(vtkViewport* v)
{
  this->BuildRepresentation();

  int count = 0;
  if (this->Ray1 && this->Ray1Visibility)
  {
    count += this->Ray1->RenderOverlay(v);
  }
  if (this->Ray2 && this->Ray2Visibility)
  {
    count += this->Ray2->RenderOverlay(v);
  }
  if (this->Arc && this->ArcVisibility)
  {
    count += this->Arc->RenderOverlay(v);
  }

  return count;
}

//----------------------------------------------------------------------
void vtkAngleRepresentation2D::PrintSelf(ostream& os, vtkIndent indent)
{
  // Superclass typedef defined in vtkTypeMacro() found in vtkSetGet.h
  this->Superclass::PrintSelf(os, indent);

  os << indent << "Ray1: ";
  if (this->Ray1)
  {
    this->Ray1->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Ray2: ";
  if (this->Ray2)
  {
    this->Ray2->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }

  os << indent << "Arc: ";
  if (this->Arc)
  {
    this->Arc->PrintSelf(os, indent.GetNextIndent());
  }
  else
  {
    os << "(none)\n";
  }
}
