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

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.kitware.com/VolViewCopyright.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 "vtkKWCaptionWidget.h"

#include "vtkCallbackCommand.h"
#include "vtkCamera.h"
#include "vtkCaptionActor2D.h"
#include "vtkCaptionRepresentation.h"
#include "vtkHandleWidget.h"
#include "vtkObjectFactory.h"
#include "vtkPointHandleRepresentation3D.h"
#include "vtkProperty2D.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkRenderWindowInteractor.h"
#include "vtkTextActor.h"
#include "vtkTextProperty.h"
#include "vtkWidgetCallbackMapper.h"
#include "vtkWidgetEvent.h"

// For initial picking of anchor point within a volume
#include "vtkDataSet.h"
#include "vtkAbstractVolumeMapper.h"
#include "vtkVolume.h"
#include "vtkObjectBase.h"
#include "vtkPoints.h"
#include "vtkBox.h"
#include "vtkSplineFilter.h"
#include "vtkPoints.h"
#include "vtkVolumeProperty.h"
#include "vtkPiecewiseFunction.h"
#include "vtkCellArray.h"
#include "vtkProbeFilter.h"
#include "vtkPolyData.h"
#include "vtkDataArray.h"
#include "vtkPointData.h"

vtkStandardNewMacro(vtkKWCaptionWidget);
vtkCxxRevisionMacro(vtkKWCaptionWidget, "$Revision: 1.9 $");

#ifdef KWCommonPro_USE_XML_RW
#include "XML/vtkXMLKWCaptionWidgetReader.h"
#include "XML/vtkXMLKWCaptionWidgetWriter.h"
#endif

vtkKWGetXMLReaderWriterObjectsImplementationMacro(vtkKWCaptionWidget, vtkXMLKWCaptionWidgetReader, vtkXMLKWCaptionWidgetWriter);

vtkCxxSetObjectMacro(vtkKWCaptionWidget,PickingVolume,vtkVolume);

//-------------------------------------------------------------------------
vtkKWCaptionWidget::vtkKWCaptionWidget()
{
  this->ResizableOff();
  //this->ProcessEventsOff();
  this->HandleWidget->ProcessEventsOff();
  this->UseAnchorPointOpacity = 0;
  this->AccumulatedAnchorOpacityLimit = 0.15;
  this->PickingVolume = NULL;
}

//-------------------------------------------------------------------------
vtkKWCaptionWidget::~vtkKWCaptionWidget()
{
  if(this->PickingVolume)
    {
    this->SetPickingVolume(NULL);
    }
}


//----------------------------------------------------------------------
void vtkKWCaptionWidget::WidgetIsDefined()
{
  this->WidgetState = vtkCaptionWidget::Manipulate;
  this->HandleWidget->ProcessEventsOn();
  this->ReleaseFocus();
  this->GetRepresentation()->BuildRepresentation(); // update this->Caption
  this->SetEnabled(this->GetEnabled()); // show/hide the handles properly
  this->Render();
}

//----------------------------------------------------------------------
int vtkKWCaptionWidget::IsWidgetDefined()
{
  return this->WidgetState == vtkCaptionWidget::Manipulate;
}

//----------------------------------------------------------------------
void vtkKWCaptionWidget::CreateDefaultRepresentation()
{
  if ( this->WidgetRep )
    {
    return;
    }
  this->Superclass::CreateDefaultRepresentation();

  vtkCaptionRepresentation *rep2d = 
    vtkCaptionRepresentation::SafeDownCast(this->WidgetRep);

  if (rep2d)
    {
    rep2d->SetShowBorderToOff();
    rep2d->GetPositionCoordinate()->SetValue(0.1, 0.4);
    //rep2d->GetPosition2Coordinate()->SetValue(0.18, 0.06);

    rep2d->GetAnchorRepresentation()->ActiveRepresentationOff();
    rep2d->GetAnchorRepresentation()->SetHandleSize(0.001);
    rep2d->GetAnchorRepresentation()->SetHotSpotSize(15);

    vtkCaptionActor2D* actor = rep2d->GetCaptionActor2D();
    actor->GetTextActor()->ScaledTextOff();
    actor->GetTextActor()->UseBorderAlignOn();
    actor->SetCaption("Label here");
    //actor->SetPadding(1.5);
    actor->AttachEdgeOnlyOn();
    actor->BorderOff();
    actor->GetProperty()->SetLineWidth(2.0);

    vtkTextProperty* textProp = 
      actor->GetCaptionTextProperty();
    textProp->BoldOff();
    textProp->ItalicOn();
    textProp->ShadowOn();
    textProp->SetVerticalJustificationToCentered();
    textProp->SetJustificationToCentered();

    //actor->GetProperty()->SetColor(1.0, 0, 0);
    rep2d->SetFontFactor(1.6);
    }
}

//----------------------------------------------------------------------
void vtkKWCaptionWidget::SetEnabled(int enabling)
{
  // The label widgets are not actually enabled until they are placed.
  if ( enabling )
    {
    vtkCaptionRepresentation* rep2d = 
      vtkCaptionRepresentation::SafeDownCast(this->WidgetRep);
    if ( rep2d && this->WidgetState == vtkKWCaptionWidget::Start)
      {
      rep2d->VisibilityOff();
      }
    }

  // Done in this weird order to get everything to work right. This invocation
  // creates the default representation.
  this->Superclass::SetEnabled(enabling);
}

// The following methods are the callbacks that the label widget responds to
//-------------------------------------------------------------------------
int vtkKWCaptionWidget::SubclassSelectAction()
{
  // Freshly enabled and placing the anchor point
  if ( this->WidgetState == vtkKWCaptionWidget::Start )
    {
    this->WidgetState = vtkKWCaptionWidget::Manipulate;
    //this->ProcessEventsOn();
    this->HandleWidget->ProcessEventsOn();

    this->DefineInitialAnchorPosition();
    this->InvokeEvent(vtkCommand::StartInteractionEvent,NULL);
    // This widget has serious issues. The first time you "place" it, 
    // the cursor/mouse position is used to put the end of the arrow
    // therefore the code in SubclassEndSelectAction is never reached
    // because the pointer is outside the caption representation.
    // This is wrong as the interaction was started but never "ended".
    // It will be the next time the user acts on the caption, which will
    // trigger a pair of start/end interaction events.
    // To prevent that and fix the symmetry, let's trigger an
    // EndInteractionEvent *now*. This *is* required for external code
    // who relies on pair of Start/End interaction event.
    this->InvokeEvent(vtkCommand::EndInteractionEvent,NULL);
    return 1;
    }
  return 0;
}

//-------------------------------------------------------------------------
int vtkKWCaptionWidget::SubclassEndSelectAction()
{
  if ( this->WidgetState == vtkKWCaptionWidget::Start ||
       this->WidgetRep->GetInteractionState() == vtkBorderRepresentation::Outside)
    {
    return 1;
    }

  // Return state to not selected
  this->ReleaseFocus();
  this->WidgetState = vtkKWCaptionWidget::Manipulate;
  reinterpret_cast<vtkCaptionRepresentation*>(this->WidgetRep)->MovingOff();

  // stop adjusting
  this->EventCallbackCommand->SetAbortFlag(1);
  this->EndInteraction();
  this->InvokeEvent(vtkCommand::EndInteractionEvent,NULL);
  return 1;
}

//-------------------------------------------------------------------------
int vtkKWCaptionWidget::SubclassMoveAction()
{
  if ( this->WidgetState == vtkKWCaptionWidget::Start)
    {
    return 1;
    }

  // compute some info we need for all cases
  int X = this->Interactor->GetEventPosition()[0];
  int Y = this->Interactor->GetEventPosition()[1];

  // Set the cursor appropriately
  if ( this->WidgetState == vtkKWCaptionWidget::Manipulate )
    {
    int stateBefore = this->WidgetRep->GetInteractionState();
    this->WidgetRep->ComputeInteractionState(X, Y);
    int stateAfter = this->WidgetRep->GetInteractionState();
    this->SetCursor(stateAfter);

    if ( this->Selectable || stateAfter != vtkCaptionRepresentation::Inside )
      {
      reinterpret_cast<vtkCaptionRepresentation*>(this->WidgetRep)->MovingOff();
      }
    else
      {
      reinterpret_cast<vtkCaptionRepresentation*>(this->WidgetRep)->MovingOn();
      }

    if ( reinterpret_cast<vtkCaptionRepresentation*>(this->WidgetRep)->GetShowBorder() == 
      vtkCaptionRepresentation::BORDER_ACTIVE && stateBefore != stateAfter && 
      (stateBefore == vtkCaptionRepresentation::Outside || stateAfter == vtkCaptionRepresentation::Outside) )
      {
      this->Render();
      }
    return 1;
  }

  if(!this->Resizable && 
    this->WidgetRep->GetInteractionState() != vtkCaptionRepresentation::Inside)
    {
    return 1;
    }

  // Okay, adjust the representation (the widget is currently selected)
  double newEventPosition[2];
  newEventPosition[0] = static_cast<double>(X);
  newEventPosition[1] = static_cast<double>(Y);
  this->WidgetRep->WidgetInteraction(newEventPosition);

  // start a drag
  this->EventCallbackCommand->SetAbortFlag(1);
  this->InvokeEvent(vtkCommand::InteractionEvent,NULL);
  this->Render();
  return 1;
}

//-------------------------------------------------------------------------
void vtkKWCaptionWidget::DefineInitialAnchorPosition()
{
  // Freshly enabled and placing the anchor point
  vtkCaptionRepresentation* rep2d = 
    vtkCaptionRepresentation::SafeDownCast(this->WidgetRep);
  if ( rep2d && rep2d->GetRenderer() )
    {
    int X = this->Interactor->GetEventPosition()[0];
    int Y = this->Interactor->GetEventPosition()[1];

    this->GrabFocus(this->EventCallbackCommand);

    rep2d->VisibilityOn();

    double anchorP[3];
    vtkRenderer* viewport = rep2d->GetRenderer();

    int picked = 0;
    if(this->UseAnchorPointOpacity && this->PickingVolume)
      {
      picked = this->PickPositionWithOpacity(X, Y, viewport, 
        this->AccumulatedAnchorOpacityLimit, anchorP);
      }
    
    if(!picked)
      {
      double display[3], worldP[4], cameraFP[4];
      vtkRenderer* viewport = rep2d->GetRenderer();
      viewport->GetActiveCamera()->GetFocalPoint(cameraFP);
      cameraFP[3]=1.0;
      viewport->SetWorldPoint(cameraFP);
      viewport->WorldToDisplay();
      double* displayP = viewport->GetDisplayPoint();

      display[0] = static_cast<double>(X);
      display[1] = static_cast<double>(Y);
      display[2] = displayP[2];

      viewport->SetDisplayPoint(display);
      viewport->DisplayToWorld();
      viewport->GetWorldPoint(worldP);
      for(int i=0; i<3; i++)
        {
        anchorP[i]=worldP[i]/worldP[3];
        }
      }

    rep2d->SetAnchorPosition(anchorP);
    rep2d->GetAnchorRepresentation()->SetWorldPosition(anchorP);

    // Label position
    int* winSize;
    winSize = viewport->GetRenderWindow()->GetSize();
    double posX, posY=0.4;

    if(X <= (winSize[0]*0.5) && X > (winSize[0]*0.3))
      {
      posX = 0.05;
      }
    else if(X > (winSize[0]*0.5) && X < (winSize[0]*0.7))
      {
      posX = 0.75;
      }
    else
      {
      posX = 0.4;
      }

    if(winSize[1]>0)
      {
      double ypos = Y/(double)winSize[1] - 0.04;
      posY = (ypos>0) ? ypos: 0.0;
      }

    rep2d->GetPositionCoordinate()->SetValue(posX, posY);
    this->ReleaseFocus();
    }

  // Clean up
  this->EventCallbackCommand->SetAbortFlag(1);
  this->Render();
}

//----------------------------------------------------------------------
int vtkKWCaptionWidget::PickPositionWithOpacity(int X, int Y, 
  vtkRenderer* viewport, double stopOpacity, double anchorP[3])
{
  if(!this->PickingVolume)
    {
    return 0;
    }

  vtkVolume* vol = this->PickingVolume;
  double display[3], worldP1[4], worldP2[4];

  double e[2];
  e[0] = static_cast<double>(X);
  e[1] = static_cast<double>(Y);

  // Viewport Near point
  display[0] = e[0];
  display[1] = e[1];
  display[2] = 0;
  viewport->SetDisplayPoint(display);
  viewport->DisplayToWorld();
  viewport->GetWorldPoint(worldP1);

  // Viewport Far point
  display[2] = 1;
  viewport->SetDisplayPoint(display);
  viewport->DisplayToWorld();
  viewport->GetWorldPoint(worldP2);
  
  // Prop Near Point
  double r[3], o[3], placedP1[3], placedP2[3], t;

  double* bounds=vol->GetBounds();
  r[0] = (worldP2[0] - worldP1[0]);
  r[1] = (worldP2[1] - worldP1[1]);
  r[2] = (worldP2[2] - worldP1[2]);
  o[0] = worldP1[0];
  o[1] = worldP1[1];
  o[2] = worldP1[2];
  if(!vtkBox::IntersectBox(bounds,o,r,placedP1,t))
    {
    return 0;
    }

  // Prop Far Point
  r[0] = (worldP1[0] - worldP2[0]);
  r[1] = (worldP1[1] - worldP2[1]);
  r[2] = (worldP1[2] - worldP2[2]);
  o[0] = worldP2[0];
  o[1] = worldP2[1];
  o[2] = worldP2[2];
  vtkBox::IntersectBox(bounds,o,r,placedP2,t);

  vtkPoints *linePoints = vtkPoints::New();
  linePoints->InsertNextPoint(placedP1);
  linePoints->InsertNextPoint(placedP2);

  // Connect points into a line
  int numberOfPoints = linePoints->GetNumberOfPoints();
  vtkCellArray *lineCells = vtkCellArray::New();
  lineCells->InsertNextCell(numberOfPoints);
  for (int i = 0; i < numberOfPoints; i ++)
    {
    lineCells->InsertCellPoint(i);       
    }

  // Create a proper vtkPolyData object from the line
  vtkPolyData *lineData = vtkPolyData::New();
  lineData->SetPoints(linePoints);
  lineData->SetLines(lineCells);       
  
  // Subdivide the line with a spline function
  vtkSplineFilter *splineFilter = vtkSplineFilter::New();
  splineFilter->SetInput(lineData);

  // Probe the Prop input (ImageData) with the line
  vtkProbeFilter* probefilter = vtkProbeFilter::New();
  probefilter->SetSource(vol->GetMapper()->GetDataSetInput());
  probefilter->SetInputConnection(splineFilter->GetOutputPort());

  probefilter->Update();

  linePoints->Delete();
  lineCells->Delete();
  lineData->Delete();
  splineFilter->Delete();

  // Get the probed line point data, loop through all the scalars,
  // and apply the stopping opacity criteria on the accumulated opacities.
  vtkPolyData* placedLineData = probefilter->GetPolyDataOutput();
  vtkPointData* pointData = placedLineData->GetPointData();
  int numP = placedLineData->GetNumberOfPoints();
  vtkVolumeProperty* volProp = vol->GetProperty();
  int indep = volProp->GetIndependentComponents();
  vtkDataArray* dArray = pointData->GetScalars();
  int numComp = dArray->GetNumberOfComponents();

  double opacitySum = 0;
  int index= 0;
  if(dArray)
    {
    for(index=0; index<numP;index++)
      {
      double* pScalar = dArray->GetTuple(index);
      if(indep)
        {
        for(int j=0; j<numComp; j++)
          {
          vtkPiecewiseFunction* pwTF = volProp->GetScalarOpacity(j);
          opacitySum+=pwTF->GetValue(pScalar[j])*volProp->GetComponentWeight(j);
          }
        }
      else
        {
        if(numComp == VTK_MAX_VRCOMP && 
          dArray->GetDataType()!= VTK_UNSIGNED_CHAR)
          {
          //can not handle this
          break;
          }
          vtkPiecewiseFunction* pwTF = volProp->GetScalarOpacity(numComp-1);
          opacitySum+=pwTF->GetValue(pScalar[numComp-1]);
        }
      if(opacitySum >= stopOpacity)
        {
        break;
        }
      }

    if(opacitySum >= stopOpacity && index<numP)
      {
      placedLineData->GetPoint(index, anchorP);
      }
    else // no point found, pick the ray middle point.
      {
      index = numP/2;
      placedLineData->GetPoint(index, anchorP);
      }
      
    probefilter->Delete();
    return 1;
    }

  probefilter->Delete();
  return 0;
}

//----------------------------------------------------------------------
void vtkKWCaptionWidget::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
  os << indent << "HandleWidget: " << this->HandleWidget << "\n";
  os << indent << "AccumulatedAnchorOpacityLimit: " << this->AccumulatedAnchorOpacityLimit << "\n";
  os << indent << "PickingVolume: " << this->PickingVolume << "\n";
  os << indent << "UseAnchorPointOpacity: " << this->UseAnchorPointOpacity << "\n";
}
