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

  Program:   Visualization Toolkit
  Module:    vtkTextRepresentation.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 "vtkTextRepresentation.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkTextRenderer.h"
#include "vtkObjectFactory.h"
#include "vtkRenderer.h"
#include "vtkStdString.h"
#include "vtkCommand.h"
#include "vtkWindow.h"

class vtkTextRepresentationObserver : public vtkCommand
{
public:
  static vtkTextRepresentationObserver* New()
    { return new vtkTextRepresentationObserver; }

  void SetTarget(vtkTextRepresentation* t)
  {
    this->Target = t;
  }
  void Execute(vtkObject* o, unsigned long event, void *p) override
  {
    if (this->Target)
    {
        if(o && vtkTextActor::SafeDownCast(o))
        {
          this->Target->ExecuteTextActorModifiedEvent(o, event, p);
        }
        else if( o && vtkTextProperty::SafeDownCast(o))
        {
          this->Target->ExecuteTextPropertyModifiedEvent(o, event, p);
        }
    }
  }
protected:
  vtkTextRepresentationObserver() { this->Target = nullptr; }
  vtkTextRepresentation* Target;

};

vtkStandardNewMacro(vtkTextRepresentation);

//-------------------------------------------------------------------------
vtkTextRepresentation::vtkTextRepresentation()
{
  this->Observer = vtkTextRepresentationObserver::New();
  this->Observer->SetTarget(this);

  this->TextActor = vtkTextActor::New();
  this->InitializeTextActor();

  this->SetShowBorder(vtkBorderRepresentation::BORDER_ACTIVE);
  this->BWActor->VisibilityOff();
  this->WindowLocation = AnyLocation;
}

//-------------------------------------------------------------------------
vtkTextRepresentation::~vtkTextRepresentation()
{
  this->SetTextActor(nullptr);
  this->Observer->SetTarget(nullptr);
  this->Observer->Delete();

}

//-------------------------------------------------------------------------
void vtkTextRepresentation::SetTextActor(vtkTextActor *textActor)
{
  if ( textActor != this->TextActor )
  {
    if ( this->TextActor )
    {
      this->TextActor->GetTextProperty()->RemoveObserver(this->Observer);
      this->TextActor->RemoveObserver(this->Observer);
      this->TextActor->Delete();
    }
    this->TextActor = textActor;
    if(this->TextActor)
    {
      this->TextActor->Register(this);
    }

    this->InitializeTextActor();
    this->Modified();
  }
}

//-------------------------------------------------------------------------
void vtkTextRepresentation::SetText(const char* text)
{
  if (this->TextActor)
  {
    this->TextActor->SetInput(text);
  }
  else
  {
    vtkErrorMacro("No Text Actor present. Cannot set text.");
  }
}

//-------------------------------------------------------------------------
const char* vtkTextRepresentation::GetText()
{
  if (this->TextActor)
  {
    return this->TextActor->GetInput();
  }
  vtkErrorMacro("No text actor present. No showing any text.");
  return nullptr;
}

//-------------------------------------------------------------------------
void vtkTextRepresentation::BuildRepresentation()
{
  // Ask the superclass the size and set the text
  int *pos1 = this->PositionCoordinate->GetComputedDisplayValue(this->Renderer);
  int *pos2 = this->Position2Coordinate->GetComputedDisplayValue(this->Renderer);

  if ( this->TextActor )
  {
    this->TextActor->GetPositionCoordinate()->SetValue(pos1[0],pos1[1]);
    this->TextActor->GetPosition2Coordinate()->SetValue(pos2[0],pos2[1]);
  }

  // Note that the transform is updated by the superclass
  this->Superclass::BuildRepresentation();
}

//-------------------------------------------------------------------------
void vtkTextRepresentation::GetActors2D(vtkPropCollection *pc)
{
  pc->AddItem(this->TextActor);
  this->Superclass::GetActors2D(pc);
}

//-------------------------------------------------------------------------
void vtkTextRepresentation::ReleaseGraphicsResources(vtkWindow *w)
{
  this->TextActor->ReleaseGraphicsResources(w);
  this->Superclass::ReleaseGraphicsResources(w);
}

//-------------------------------------------------------------------------
int vtkTextRepresentation::RenderOverlay(vtkViewport *w)
{
  int count = this->Superclass::RenderOverlay(w);
  count += this->TextActor->RenderOverlay(w);
  return count;
}

//-------------------------------------------------------------------------
int vtkTextRepresentation::RenderOpaqueGeometry(vtkViewport *w)
{
  // CheckTextBoundary resize the text actor. This needs to happen before we
  // actually render (previous version was calling this method after
  // this->TextActor->RenderOpaqueGeometry(), which seems like a bug).
  this->CheckTextBoundary();
  int count = this->Superclass::RenderOpaqueGeometry(w);
  count += this->TextActor->RenderOpaqueGeometry(w);
  return count;
}

//-------------------------------------------------------------------------
int vtkTextRepresentation::RenderTranslucentPolygonalGeometry(vtkViewport *w)
{
  int count = this->Superclass::RenderTranslucentPolygonalGeometry(w);
  count += this->TextActor->RenderTranslucentPolygonalGeometry(w);
  return count;
}

//-------------------------------------------------------------------------
vtkTypeBool vtkTextRepresentation::HasTranslucentPolygonalGeometry()
{
  int result = this->Superclass::HasTranslucentPolygonalGeometry();
  result |= this->TextActor->HasTranslucentPolygonalGeometry();
  return result;
}

//-------------------------------------------------------------------------
void vtkTextRepresentation::InitializeTextActor()
{
  if ( this->TextActor )
  {
    this->TextActor->SetTextScaleModeToProp();
    this->TextActor->SetMinimumSize(1,1);
    this->TextActor->SetMaximumLineHeight(1.0);
    this->TextActor->GetPositionCoordinate()->SetCoordinateSystemToDisplay();
    this->TextActor->GetPosition2Coordinate()->SetCoordinateSystemToDisplay();
    this->TextActor->GetPosition2Coordinate()->SetReferenceCoordinate(nullptr);
    this->TextActor->GetTextProperty()->SetJustificationToCentered();
    this->TextActor->GetTextProperty()->SetVerticalJustificationToCentered();

    this->TextActor->UseBorderAlignOn();

    this->TextProperty = this->TextActor->GetTextProperty();

    this->TextActor->GetTextProperty()->AddObserver(
      vtkCommand::ModifiedEvent, this->Observer);
    this->TextActor->AddObserver(
      vtkCommand::ModifiedEvent, this->Observer);
  }
}

//----------------------------------------------------------------------------
void vtkTextRepresentation::ExecuteTextPropertyModifiedEvent(vtkObject* object,
  unsigned long enumEvent, void*)
{
  if(!object || enumEvent != vtkCommand::ModifiedEvent)
  {
    return;
  }
  vtkTextProperty* tp = vtkTextProperty::SafeDownCast(object);
  if(!tp)
  {
    return;
  }

  this->CheckTextBoundary();
}

//----------------------------------------------------------------------------
void vtkTextRepresentation::ExecuteTextActorModifiedEvent(vtkObject* object,
  unsigned long enumEvent, void*)
{
  if(!object || enumEvent != vtkCommand::ModifiedEvent)
  {
    return;
  }
  vtkTextActor* ta = vtkTextActor::SafeDownCast(object);
  if(!ta || ta != this->TextActor)
  {
    return;
  }

  if(this->TextProperty != this->TextActor->GetTextProperty())
  {
    this->TextActor->GetTextProperty()->AddObserver(
      vtkCommand::ModifiedEvent, this->Observer);
    this->TextProperty = this->TextActor->GetTextProperty();
  }

  this->CheckTextBoundary();
}

//----------------------------------------------------------------------------
void vtkTextRepresentation::CheckTextBoundary()
{
  if(this->GetRenderer() &&
     this->TextActor->GetTextScaleMode() != vtkTextActor::TEXT_SCALE_MODE_PROP)
  {
    vtkTextRenderer* tren = vtkTextRenderer::GetInstance();
    if (!tren)
    {
      vtkErrorMacro(<<"Failed getting the vtkTextRenderer instance");
      return;
    }

    this->TextActor->ComputeScaledFont(this->GetRenderer());

    vtkWindow *win = this->Renderer->GetVTKWindow();
    if (!win)
    {
      vtkErrorMacro(<<"No render window available: cannot determine DPI.");
      return;
    }

    int text_bbox[4];
    if (!tren->GetBoundingBox(this->TextActor->GetScaledTextProperty(),
                              this->GetText(), text_bbox, win->GetDPI()))
    {
      return;
    }

    // The bounding box was the area that is going to be filled with pixels
    // given a text origin of (0, 0). Now get the real size we need, i.e.
    // the full extent from the origin to the bounding box.

    double text_size[2];
    text_size[0] = (text_bbox[1] - text_bbox[0] + 1);
    text_size[1] = (text_bbox[3] - text_bbox[2] + 1);

    this->GetRenderer()->DisplayToNormalizedDisplay (text_size[0], text_size[1]);
    this->GetRenderer()->NormalizedDisplayToViewport (text_size[0], text_size[1]);
    this->GetRenderer()->ViewportToNormalizedViewport (text_size[0], text_size[1]);

   // update the PositionCoordinate

    double* pos2 = this->Position2Coordinate->GetValue();
    if(pos2[0] != text_size[0] || pos2[1] != text_size[1])
    {
      this->Position2Coordinate->SetValue(text_size[0], text_size[1], 0);
      this->Modified();
    }
    if(this->WindowLocation != AnyLocation)
    {
      this->UpdateWindowLocation();
    }
  }
}

//----------------------------------------------------------------------------
void vtkTextRepresentation::SetWindowLocation(int enumLocation)
{
  if(this->WindowLocation == enumLocation)
  {
    return;
  }

  this->WindowLocation = enumLocation;
  this->CheckTextBoundary();
  this->Modified();
}

//----------------------------------------------------------------------------
void vtkTextRepresentation::SetPosition(double x, double y)
{
  double* pos = this->PositionCoordinate->GetValue();
  if(pos[0]==x && pos[1]==y)
  {
    return;
  }

  this->PositionCoordinate->SetValue(x, y);
  this->Modified();
}

//----------------------------------------------------------------------------
void vtkTextRepresentation::UpdateWindowLocation()
{
  if(this->WindowLocation != AnyLocation)
  {
    double* pos2 = this->Position2Coordinate->GetValue();
    switch (this->WindowLocation)
    {
      case LowerLeftCorner:
        this->SetPosition(0.01, 0.01);
        break;
      case LowerRightCorner:
        this->SetPosition(0.99-pos2[0], 0.01);
        break;
      case LowerCenter:
        this->SetPosition((1-pos2[0])/2.0, 0.01);
        break;
      case UpperLeftCorner:
        this->SetPosition(0.01, 0.99-pos2[1]);
        break;
      case UpperRightCorner:
        this->SetPosition(0.99-pos2[0],0.99-pos2[1]);
        break;
      case UpperCenter:
        this->SetPosition((1-pos2[0])/2.0,0.99-pos2[1]);
        break;
      default:
        break;
    }
  }
}

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

  os << indent << "Text Actor: " << this->TextActor << "\n";

  os << indent << "Window Location: ";
  switch ( this->WindowLocation )
  {
    case LowerLeftCorner:
      os << "LowerLeftCorner\n";
      break;
    case LowerRightCorner:
      os << "LowerRightCorner\n";
      break;
    case LowerCenter:
      os << "LowerCenter\n";
      break;
    case UpperLeftCorner:
      os << "UpperLeftCorner\n";
      break;
    case UpperRightCorner:
      os << "UpperRightCorner\n";
      break;
    case UpperCenter:
      os << "UpperCenter\n";
      break;
  }
}
