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

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

#include "vtkActor2DCollection.h"
#include "vtkAssemblyPath.h"
#include "vtkProp.h"
#include "vtkPropCollection.h"
#include "vtkWindow.h"
#include "vtkMath.h"

//----------------------------------------------------------------------------
// Create a vtkViewport with a black background, a white ambient light,
// two-sided lighting turned on, a viewport of (0,0,1,1), and backface culling
// turned off.
vtkViewport::vtkViewport()
{
  this->VTKWindow = nullptr;

  this->Background[0] = 0;
  this->Background[1] = 0;
  this->Background[2] = 0;

  this->Background2[0] = 0.2;
  this->Background2[1] = 0.2;
  this->Background2[2] = 0.2;

  this->BackgroundAlpha = 0.0;

  this->GradientBackground = false;

  this->Viewport[0] = 0;
  this->Viewport[1] = 0;
  this->Viewport[2] = 1;
  this->Viewport[3] = 1;

  this->WorldPoint[0] = 0;
  this->WorldPoint[1] = 0;
  this->WorldPoint[2] = 0;
  this->WorldPoint[3] = 0;

  this->DisplayPoint[0] = 0;
  this->DisplayPoint[1] = 0;
  this->DisplayPoint[2] = 0;

  this->ViewPoint[0] = 0;
  this->ViewPoint[1] = 0;
  this->ViewPoint[2] = 0;

  this->Aspect[0] = this->Aspect[1] = 1.0;
  this->PixelAspect[0] = this->PixelAspect[1] = 1.0;
  this->Center[0] = 0.0;
  this->Center[1] = 0.0;

  this->Size[0] = 0;
  this->Size[1] = 0;

  this->Origin[0] = 0;
  this->Origin[1] = 0;

  this->PickedProp = nullptr;
  this->PickFromProps = nullptr;
  this->PickResultProps = nullptr;
  this->PickX1 = -1;
  this->PickY1 = -1;
  this->PickX2 = -1;
  this->PickY2 = -1;
  this->PickedZ = 1.0;

  this->Props = vtkPropCollection::New();
  this->Actors2D = vtkActor2DCollection::New();
}

//----------------------------------------------------------------------------
vtkViewport::~vtkViewport()
{
  this->Actors2D->Delete();
  this->Actors2D = nullptr;

  this->RemoveAllViewProps();
  this->Props->Delete();
  this->Props = nullptr;

  if (this->VTKWindow != nullptr)
  {
    // renderer never reference counted the window.
    // loop is too hard to detect.
    // this->VTKWindow->UnRegister(this);
    this->VTKWindow = nullptr;
  }

  if ( this->PickedProp != nullptr )
  {
    this->PickedProp->UnRegister(this);
  }
  if ( this->PickResultProps != nullptr )
  {
    this->PickResultProps->Delete();
  }
}

//----------------------------------------------------------------------------
void vtkViewport::AddActor2D(vtkProp* p)
{
  this->AddViewProp(p);
}

//----------------------------------------------------------------------------
void vtkViewport::RemoveActor2D(vtkProp* p)
{
  this->Actors2D->RemoveItem(p);
  this->RemoveViewProp(p);
}

//----------------------------------------------------------------------------
int vtkViewport::HasViewProp(vtkProp *p)
{
  return (p && this->Props->IsItemPresent(p));
}

//----------------------------------------------------------------------------
void vtkViewport::AddViewProp(vtkProp *p)
{
  if (p && !this->HasViewProp(p))
  {
    this->Props->AddItem(p);
    p->AddConsumer(this);
  }
}

//----------------------------------------------------------------------------
void vtkViewport::RemoveViewProp(vtkProp *p)
{
  if (p && this->HasViewProp(p))
  {
    if (this->VTKWindow)
    {
      this->VTKWindow->MakeCurrent();
    }
    p->ReleaseGraphicsResources(this->VTKWindow);
    p->RemoveConsumer(this);
    this->Props->RemoveItem(p);
  }
}

//----------------------------------------------------------------------------
void vtkViewport::RemoveAllViewProps()
{
  vtkProp *aProp;
  vtkCollectionSimpleIterator pit;
  for (this->Props->InitTraversal(pit);
       (aProp = this->Props->GetNextProp(pit)); )
  {
    aProp->ReleaseGraphicsResources(this->VTKWindow);
    aProp->RemoveConsumer(this);
  }
  this->Props->RemoveAllItems();
}

//----------------------------------------------------------------------------
// look through the props and get all the actors
vtkActor2DCollection *vtkViewport::GetActors2D()
{
  vtkProp *aProp;

  // clear the collection first
  this->Actors2D->RemoveAllItems();

  vtkCollectionSimpleIterator pit;
  for (this->Props->InitTraversal(pit);
       (aProp = this->Props->GetNextProp(pit)); )
  {
    aProp->GetActors2D(this->Actors2D);
  }
  return this->Actors2D;
}

// Convert display coordinates to view coordinates.
void vtkViewport::DisplayToView()
{
  if ( this->VTKWindow )
  {
    double vx,vy,vz;
    int sizex,sizey;

    /* get physical window dimensions */
    const int *size = this->VTKWindow->GetSize();
    if (size == nullptr)
    {
      return;
    }
    sizex = size[0];
    sizey = size[1];

    vx = 0.0;
    if (sizex != 0.0)
    {
      vx = 2.0 * (this->DisplayPoint[0] - sizex*this->Viewport[0])/
        (sizex*(this->Viewport[2]-this->Viewport[0])) - 1.0;
    }

    vy = 0.0;
    if (sizey != 0.0)
    {
      vy = 2.0 * (this->DisplayPoint[1] - sizey*this->Viewport[1])/
        (sizey*(this->Viewport[3]-this->Viewport[1])) - 1.0;
    }

    vz = this->DisplayPoint[2];

    this->SetViewPoint(vx,vy,vz);
  }
}

//----------------------------------------------------------------------------
// Convert view coordinates to display coordinates.
void vtkViewport::ViewToDisplay()
{
  if ( this->VTKWindow )
  {
    double dx,dy;
    int sizex,sizey;

    /* get physical window dimensions */
    const int *size = this->VTKWindow->GetSize();
    if (!size)
    {
      return;
    }
    sizex = size[0];
    sizey = size[1];

    dx = (this->ViewPoint[0] + 1.0) *
      (sizex*(this->Viewport[2]-this->Viewport[0])) / 2.0 +
        sizex*this->Viewport[0];
    dy = (this->ViewPoint[1] + 1.0) *
      (sizey*(this->Viewport[3]-this->Viewport[1])) / 2.0 +
        sizey*this->Viewport[1];

    this->SetDisplayPoint(dx,dy,this->ViewPoint[2]);
  }
}

//----------------------------------------------------------------------------
// Convert view point coordinates to world coordinates.
void vtkViewport::ViewToWorld()
{
  this->SetWorldPoint(this->ViewPoint[0], this->ViewPoint[1],
                      this->ViewPoint[2], 1);
}

//----------------------------------------------------------------------------
// Convert world point coordinates to view coordinates.
void vtkViewport::WorldToView()
{

  this->SetViewPoint(this->WorldPoint[0], this->WorldPoint[1],
                     this->WorldPoint[2]);

}

//----------------------------------------------------------------------------
// Return the size of the viewport in display coordinates.
int *vtkViewport::GetSize()
{
  if ( this->VTKWindow )
  {
    int  lowerLeft[2];
    double *vport = this->GetViewport();

    double vpu, vpv;
    vpu = vport[0];
    vpv = vport[1];
    this->NormalizedDisplayToDisplay(vpu,vpv);
    lowerLeft[0] = static_cast<int>(vpu+0.5);
    lowerLeft[1] = static_cast<int>(vpv+0.5);
    double vpu2, vpv2;
    vpu2 = vport[2];
    vpv2 = vport[3];
    this->NormalizedDisplayToDisplay(vpu2,vpv2);
    this->Size[0] = static_cast<int>(vpu2 + 0.5) - lowerLeft[0];
    this->Size[1] = static_cast<int>(vpv2 + 0.5) - lowerLeft[1];
  }
  else
  {
    this->Size[0] = this->Size[1] = 0;
  }

  return this->Size;
}

//----------------------------------------------------------------------------
// Return the origin of the viewport in display coordinates.
int *vtkViewport::GetOrigin()
{
  if ( this->VTKWindow )
  {
    int* winSize = this->VTKWindow->GetSize();

    // Round the origin up a pixel
    this->Origin[0] = static_cast<int>(this->Viewport[0] *
                                       static_cast<double>(winSize[0]) + 0.5);
    this->Origin[1] = static_cast<int>(this->Viewport[1] *
                                       static_cast<double>(winSize[1]) + 0.5);
  }
  else
  {
    this->Origin[0] = this->Origin[1] = 0;
  }

  return this->Origin;
}

//----------------------------------------------------------------------------
// Return the center of this Viewport in display coordinates.
double *vtkViewport::GetCenter()
{
  if ( this->VTKWindow )
  {
    // get physical window dimensions
    const int *size = this->GetVTKWindow()->GetSize();
    if (size)
    {
      this->Center[0] = ((this->Viewport[2]+this->Viewport[0])
                         /2.0*size[0]);
      this->Center[1] = ((this->Viewport[3]+this->Viewport[1])
                         /2.0*size[1]);
    }
  }
  else
  {
    this->Center[0] = this->Center[1] = 0;
  }

  return this->Center;
}

//----------------------------------------------------------------------------
// Is a given display point in this Viewport's viewport.
int vtkViewport::IsInViewport(int x,int y)
{
  if ( this->VTKWindow )
  {
    // get physical window dimensions
    const int *size = this->GetVTKWindow()->GetSize();
    if (size)
    {
    if ((this->Viewport[0]*size[0] <= x)&&
        (this->Viewport[2]*size[0] >= x)&&
        (this->Viewport[1]*size[1] <= y)&&
        (this->Viewport[3]*size[1] >= y))
    {
      return 1;
    }
    }
  }

  return 0;
}

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

  os << indent << "Aspect: (" << this->Aspect[0] << ", "
    << this->Aspect[1] << ")\n";

  os << indent << "PixelAspect: (" << this->PixelAspect[0] << ", "
    << this->PixelAspect[1] << ")\n";

  os << indent << "Background: (" << this->Background[0] << ", "
    << this->Background[1] << ", "  << this->Background[2] << ")\n";

  os << indent << "Background2: (" << this->Background2[0] << ", "
    << this->Background2[1] << ", "  << this->Background2[2] << ")\n";

  os << indent << "BackgroundAlpha: " << this->BackgroundAlpha << "\n";

  os << indent << "GradientBackground: "
    << (this->GradientBackground ? "On" : "Off") << "\n";

  os << indent << "Viewport: (" << this->Viewport[0] << ", "
    << this->Viewport[1] << ", " << this->Viewport[2] << ", "
      << this->Viewport[3] << ")\n";

  os << indent << "Displaypoint: (" << this->DisplayPoint[0] << ", "
    << this->DisplayPoint[1] << ", " << this->DisplayPoint[2] << ")\n";

  os << indent << "Viewpoint: (" << this->ViewPoint[0] << ", "
    << this->ViewPoint[1] << ", " << this->ViewPoint[2] << ")\n";

  os << indent << "Worldpoint: (" << this->WorldPoint[0] << ", "
    << this->WorldPoint[1] << ", " << this->WorldPoint[2] << ", "
      << this->WorldPoint[3] << ")\n";

  os << indent << "Pick Position X1 Y1: " << this->PickX1
     << " " << this->PickY1 << endl;
  os << indent << "Pick Position X2 Y2: " << this->PickX2
     << " " << this->PickY2 << endl;
  os << indent << "PickedZ: " << this->PickedZ << "\n";
  os << indent << "Props:\n";
  this->Props->PrintSelf(os,indent.GetNextIndent());
  os << indent << "PickResultProps:\n";
  if ( this->PickResultProps != nullptr )
  {
    this->PickResultProps->PrintSelf(os,indent.GetNextIndent());
  }
  else
  {
    os << indent << "nullptr\n";
  }

}

//----------------------------------------------------------------------------
void vtkViewport::LocalDisplayToDisplay(double &vtkNotUsed(u), double &v)
{
  if ( this->VTKWindow )
  {
    /* get physical window dimensions */
    const int *size = this->VTKWindow->GetSize();
    if (size)
    {
      v = size[1] - v - 1;
    }
  }
}

//----------------------------------------------------------------------------
void vtkViewport::DisplayToLocalDisplay(double &vtkNotUsed(u), double &v)
{
  if ( this->VTKWindow )
  {
    /* get physical window dimensions */
    const int *size = this->VTKWindow->GetSize();
    if (size)
    {
      v = size[1] - v - 1;
    }
  }
}

//----------------------------------------------------------------------------
void vtkViewport::DisplayToNormalizedDisplay(double &u, double &v)
{
  if ( this->VTKWindow )
  {
    /* get physical window dimensions */
    const int *size = this->VTKWindow->GetSize();

    if (size &&
        size[0] != 0 &&
        size[1] != 0)
    {
      u = u/size[0];
      v = v/size[1];
    }
  }
}

//----------------------------------------------------------------------------
void vtkViewport::NormalizedDisplayToViewport(double &u, double &v)
{
  if ( this->VTKWindow )
  {
    // get the pixel value for the viewport origin
    double vpou, vpov;
    vpou = this->Viewport[0];
    vpov = this->Viewport[1];
    this->NormalizedDisplayToDisplay(vpou,vpov);

    // get the pixel value for the coordinate
    this->NormalizedDisplayToDisplay(u,v);

    // subtract the vpo
    u = u - vpou;
    v = v - vpov;
  }
}

//----------------------------------------------------------------------------
void vtkViewport::ViewportToNormalizedViewport(double &u, double &v)
{
  if ( this->VTKWindow )
  {
    /* get physical window dimensions */
/*
  double vpsizeu, vpsizev;
  const int *size = this->VTKWindow->GetSize();
  vpsizeu = size[0]*(this->Viewport[2] - this->Viewport[0]);
  vpsizev = size[1]*(this->Viewport[3] - this->Viewport[1]);

  u = u/(vpsizeu - 1.0);
  v = v/(vpsizev - 1.0);
*/
    const int *size = this->GetSize();
    if (size &&
        size[0] != 0 &&
        size[1] != 0)
    {
      u = u/size[0];
      v = v/size[1];
    }
  }
}

//----------------------------------------------------------------------------
void vtkViewport::NormalizedViewportToView(double &x, double &y,
                                           double &vtkNotUsed(z))
{
  if(this->VTKWindow)
  {
    // for tiling we must consider the tiledViewport
    double *tvport = this->VTKWindow->GetTileViewport();

    // what part of the full viewport is the current tiled viewport?
    double *vport = this->GetViewport();
    double nvport[4];
    this->GetViewport(nvport);

    // clip the viewport to the tiled viewport
    if (nvport[0] < tvport[0])
    {
      nvport[0] = tvport[0];
    }
    if (nvport[1] < tvport[1])
    {
      nvport[1] = tvport[1];
    }
    if (nvport[2] > tvport[2])
    {
      nvport[2] = tvport[2];
    }
    if (nvport[3] > tvport[3])
    {
      nvport[3] = tvport[3];
    }

    x = x*(vport[2] - vport[0]) + vport[0];
    y = y*(vport[3] - vport[1]) + vport[1];

    x = (x - nvport[0])/(nvport[2] - nvport[0]);
    y = (y - nvport[1])/(nvport[3] - nvport[1]);

    x = (2.0*x - 1.0);
    y = (2.0*y - 1.0);
  }
}

//----------------------------------------------------------------------------
void vtkViewport::NormalizedDisplayToDisplay(double &u, double &v)
{
  if ( this->VTKWindow )
  {
    /* get physical window dimensions */
    const int *size = this->VTKWindow->GetSize();
    if (size)
    {
      u = u*size[0];
      v = v*size[1];
    }
  }
}


//----------------------------------------------------------------------------
void vtkViewport::ViewportToNormalizedDisplay(double &u, double &v)
{
  if ( this->VTKWindow )
  {
    // get the pixel value for the viewport origin
    double vpou, vpov;
    vpou = this->Viewport[0];
    vpov = this->Viewport[1];
    this->NormalizedDisplayToDisplay(vpou,vpov);

    // add the vpo
    u = u + vpou;
    v = v + vpov;

    // get the pixel value for the coordinate
    this->DisplayToNormalizedDisplay(u,v);
  }
}

//----------------------------------------------------------------------------
void vtkViewport::NormalizedViewportToViewport(double &u, double &v)
{
  if ( this->VTKWindow )
  {
    /* get physical window dimensions */
/*
  double vpsizeu, vpsizev;
  const int *size = this->VTKWindow->GetSize();
  vpsizeu = size[0]*(this->Viewport[2] - this->Viewport[0]);
  vpsizev = size[1]*(this->Viewport[3] - this->Viewport[1]);
  u = u * (vpsizeu - 1.0);
  v = v * (vpsizev - 1.0);
*/
    const int *size = this->GetSize();
    if (size)
    {
      u = u * size[0];
      v = v * size[1];
    }
  }
}

//----------------------------------------------------------------------------
void vtkViewport::ViewToNormalizedViewport(double &x, double &y,
                                           double &vtkNotUsed(z))
{
  if(this->VTKWindow)
  {
    // for tiling we must consider the tiledViewport
    double *tvport = this->VTKWindow->GetTileViewport();

    // what part of the full viewport is the current tiled viewport?
    double *vport = this->GetViewport();
    double nvport[4];
    this->GetViewport(nvport);

    // clip the viewport to the tiled viewport
    if (nvport[0] < tvport[0])
    {
      nvport[0] = tvport[0];
    }
    if (nvport[1] < tvport[1])
    {
      nvport[1] = tvport[1];
    }
    if (nvport[2] > tvport[2])
    {
      nvport[2] = tvport[2];
    }
    if (nvport[3] > tvport[3])
    {
      nvport[3] = tvport[3];
    }

    x =  (x + 1.0) / 2.0;
    y =  (y + 1.0) / 2.0;

    // now x and y are in the normalized viewport of the clipped viewport
    // we need to convert that to the normalized viewport of the entire
    // viewport
    x = nvport[0] + x*(nvport[2] - nvport[0]);
    y = nvport[1] + y*(nvport[3] - nvport[1]);
    x = (x - vport[0])/(vport[2] - vport[0]);
    y = (y - vport[1])/(vport[3] - vport[1]);
  }
}

void vtkViewport::ComputeAspect()
{
  if ( this->VTKWindow )
  {
    // get the bounds of the window
    const int *size = this->VTKWindow->GetSize();
    if (!size)
    {
      return;
    }
    double *vport = this->GetViewport();

    int lowerLeft[2], upperRight[2];
    lowerLeft[0] = static_cast<int>(vport[0]*size[0] + 0.5);
    lowerLeft[1] = static_cast<int>(vport[1]*size[1] + 0.5);
    upperRight[0] = static_cast<int>(vport[2]*size[0] + 0.5);
    upperRight[1] = static_cast<int>(vport[3]*size[1] + 0.5);
    upperRight[0]--;
    upperRight[1]--;

    double aspect[2];
    if((upperRight[0]-lowerLeft[0]+1)!=0 && (upperRight[1]-lowerLeft[1]+1)!=0)
    {
      aspect[0] = static_cast<double>(upperRight[0]-lowerLeft[0]+1)/
        static_cast<double>(upperRight[1]-lowerLeft[1]+1)*this->PixelAspect[0];
    }
    else
    {
      // it happens if the vtkWindow is attached to the vtkViewport but
      // the vtkWindow is not initialized yet, so size[0]==0 and size[1]==0
      aspect[0]=this->PixelAspect[0];
    }
    aspect[1] = 1.0*this->PixelAspect[1];

    this->SetAspect(aspect);
  }
}

//----------------------------------------------------------------------------
vtkAssemblyPath* vtkViewport::PickPropFrom(double selectionX,
                                           double selectionY,
                                           vtkPropCollection* pickfrom)
{
  this->PickFromProps = pickfrom;
  return this->PickProp(selectionX, selectionY);
}

//----------------------------------------------------------------------------
vtkAssemblyPath* vtkViewport::PickPropFrom(double selectionX1, double selectionY1,
                                           double selectionX2, double selectionY2,
                                           vtkPropCollection* pickfrom)
{
  this->PickFromProps = pickfrom;
  return this->PickProp(selectionX1, selectionY1, selectionX2, selectionY2);
}

//----------------------------------------------------------------------------
// This complicated method determines the size of the current tile in pixels
// this is useful in computeing the actual aspcet ration of the current tile
void vtkViewport::GetTiledSize(int *usize, int *vsize)
{
  int llx, lly;
  this->GetTiledSizeAndOrigin(usize,vsize,&llx,&lly);
}

//----------------------------------------------------------------------------
void vtkViewport::GetTiledSizeAndOrigin(int *usize, int *vsize,
                                        int *lowerLeftU, int *lowerLeftV)
{
  double *vport;

  // find out if we should stereo render
  vport = this->GetViewport();

  // if there is no window assume 0 1
  double tileViewPort[4];
  if (this->GetVTKWindow())
  {
    this->GetVTKWindow()->GetTileViewport(tileViewPort);
  }
  else
  {
    tileViewPort[0] = 0;
    tileViewPort[1] = 0;
    tileViewPort[2] = 1;
    tileViewPort[3] = 1;
  }

  // find the lower left corner of the viewport, taking into account the
  // lower left boundary of this tile
  double vpu = vtkMath::ClampValue(vport[0] - tileViewPort[0], 0.0, 1.0);
  double vpv = vtkMath::ClampValue(vport[1] - tileViewPort[1], 0.0, 1.0);
  // store the result as a pixel value
  this->NormalizedDisplayToDisplay(vpu,vpv);
  *lowerLeftU = static_cast<int>(vpu+0.5);
  *lowerLeftV = static_cast<int>(vpv+0.5);

  // find the upper right corner of the viewport, taking into account the
  // lower left boundary of this tile
  double vpu2 = vtkMath::ClampValue(vport[2] - tileViewPort[0], 0.0, 1.0);
  double vpv2 = vtkMath::ClampValue(vport[3] - tileViewPort[1], 0.0, 1.0);
  // also watch for the upper right boundary of the tile
  if (vpu2 > (tileViewPort[2] - tileViewPort[0]))
  {
    vpu2 = tileViewPort[2] - tileViewPort[0];
  }
  if (vpv2 > (tileViewPort[3] - tileViewPort[1]))
  {
    vpv2 = tileViewPort[3] - tileViewPort[1];
  }
  this->NormalizedDisplayToDisplay(vpu2,vpv2);
  // now compute the size of the intersection of the viewport with the
  // current tile
  *usize = static_cast<int>(vpu2 + 0.5) - *lowerLeftU;
  *vsize = static_cast<int>(vpv2 + 0.5) - *lowerLeftV;
  if (*usize < 0)
  {
    *usize = 0;
  }
  if (*vsize < 0)
  {
    *vsize = 0;
  }
}
