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

  Program:   ParaView
  Module:    vtkSMViewProxy.cxx

  Copyright (c) Kitware, Inc.
  All rights reserved.
  See Copyright.txt or http://www.paraview.org/HTML/Copyright.html 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 "vtkSMViewProxy.h"

#include "vtkClientServerStream.h"
#include "vtkCommand.h"
#include "vtkErrorCode.h"
#include "vtkGenericRenderWindowInteractor.h"
#include "vtkImageData.h"
#include "vtkMath.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkProcessModule.h"
#include "vtkPVOptions.h"
#include "vtkPVView.h"
#include "vtkPVXMLElement.h"
#include "vtkRendererCollection.h"
#include "vtkRenderer.h"
#include "vtkRenderWindow.h"
#include "vtkSmartPointer.h"
#include "vtkSMParaViewPipelineControllerWithRendering.h"
#include "vtkSMProperty.h"
#include "vtkSMProxyManager.h"
#include "vtkSMRepresentationProxy.h"
#include "vtkSMSession.h"
#include "vtkSMSessionProxyManager.h"
#include "vtkSMUncheckedPropertyHelper.h"
#include "vtkSMUtilities.h"

#include <assert.h>

namespace
{
  const char* vtkGetRepresentationNameFromHints(
    const char* viewType, vtkPVXMLElement* hints, int port)
    {
    if (!hints)
      {
      return NULL;
      }

    for (unsigned int cc=0, max=hints->GetNumberOfNestedElements(); cc<max; ++cc)
      {
      vtkPVXMLElement* child = hints->GetNestedElement(cc);
      if (child == NULL || child->GetName() == NULL)
        {
        continue;
        }

      // LEGACY: support DefaultRepresentations hint.
      // <Hints>
      //    <DefaultRepresentations representation="Foo" />
      // </Hints>
      if (strcmp(child->GetName(), "DefaultRepresentations") == 0)
        {
        return child->GetAttribute("representation");
        }

      // <Hints>
      //    <Representation port="outputPort" view="ViewName" type="ReprName" />
      // </Hints>
      else if (strcmp(child->GetName(), "Representation") == 0 &&
        // has an attribute "view" that matches the viewType.
        child->GetAttribute("view") &&
        strcmp(child->GetAttribute("view"), viewType) == 0 &&
        child->GetAttribute("type") != NULL)
        {
        // if port is present, it must match "port".
        int xmlPort;
        if (child->GetScalarAttribute("port", &xmlPort) == 0 || xmlPort == port)
          {
          return child->GetAttribute("type");
          }
        }
      }
    return NULL;
    }
};

bool vtkSMViewProxy::TransparentBackground = false;

vtkStandardNewMacro(vtkSMViewProxy);
//----------------------------------------------------------------------------
vtkSMViewProxy::vtkSMViewProxy()
{
  this->SetLocation(vtkProcessModule::CLIENT_AND_SERVERS);
  this->DefaultRepresentationName = 0;
  this->Enable = true;
}

//----------------------------------------------------------------------------
vtkSMViewProxy::~vtkSMViewProxy()
{
  this->SetDefaultRepresentationName(0);
}

//----------------------------------------------------------------------------
vtkView* vtkSMViewProxy::GetClientSideView()
{
  if (this->ObjectsCreated)
    {
    return vtkView::SafeDownCast(this->GetClientSideObject());
    }
  return NULL;
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::CreateVTKObjects()
{
  if (this->ObjectsCreated)
    {
    return;
    }

  this->Superclass::CreateVTKObjects();

  // If prototype, no need to go further...
  if(this->Location == 0)
    {
    return;
    }

  if (!this->ObjectsCreated)
    {
    return;
    }

  vtkClientServerStream stream;
  stream << vtkClientServerStream::Invoke
         << VTKOBJECT(this)
         << "Initialize"
         << static_cast<int>(this->GetGlobalID())
         << vtkClientServerStream::End;
  this->ExecuteStream(stream);

  vtkObject::SafeDownCast(this->GetClientSideObject())->AddObserver(
      vtkPVView::ViewTimeChangedEvent,
      this, &vtkSMViewProxy::ViewTimeChanged);
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::ViewTimeChanged()
{
  vtkSMPropertyHelper helper1(this, "Representations");
  for (unsigned int cc=0; cc  < helper1.GetNumberOfElements(); cc++)
    {
    vtkSMRepresentationProxy* repr = vtkSMRepresentationProxy::SafeDownCast(
      helper1.GetAsProxy(cc));
    if (repr)
      {
      repr->ViewTimeChanged();
      }
    }

  vtkSMPropertyHelper helper2(this, "HiddenRepresentations", true);
  for (unsigned int cc=0; cc  < helper2.GetNumberOfElements(); cc++)
    {
    vtkSMRepresentationProxy* repr = vtkSMRepresentationProxy::SafeDownCast(
      helper2.GetAsProxy(cc));
    if (repr)
      {
      repr->ViewTimeChanged();
      }
    }
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::StillRender()
{
  // bug 0013947
  // on Mac OSX don't render into invalid drawable, all subsequent
  // OpenGL calls fail with invalid framebuffer operation.
  if (this->IsContextReadyForRendering() == false)
    {
    return;
    }

  int interactive = 0;
  this->InvokeEvent(vtkCommand::StartEvent, &interactive);
  this->GetSession()->PrepareProgress();
  // We call update separately from the render. This is done so that we don't
  // get any synchronization issues with GUI responding to the data-updated
  // event by making some data information requests(for example). If those
  // happen while StillRender/InteractiveRender is being executed on the server
  // side then we get deadlocks.
  this->Update();

  vtkTypeUInt32 render_location = this->PreRender(interactive==1);

  if (this->ObjectsCreated)
    {
    vtkClientServerStream stream;
    stream << vtkClientServerStream::Invoke
           << VTKOBJECT(this)
           << "StillRender"
           << vtkClientServerStream::End;
    this->ExecuteStream(stream, false, render_location);
    }

  this->PostRender(interactive==1);
  this->GetSession()->CleanupPendingProgress();
  this->InvokeEvent(vtkCommand::EndEvent, &interactive);
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::InteractiveRender()
{
  int interactive = 1;
  this->InvokeEvent(vtkCommand::StartEvent, &interactive);
  this->GetSession()->PrepareProgress();

  // Interactive render will not call Update() at all. It's expected that you
  // must have either called a StillRender() or an Update() before triggering an
  // interactive render. This is critical to keep interactive rates fast when
  // working over a slow client-server connection.
  // this->Update();

  vtkTypeUInt32 render_location = this->PreRender(interactive==1);

  if (this->ObjectsCreated)
    {
    vtkClientServerStream stream;
    stream << vtkClientServerStream::Invoke
           << VTKOBJECT(this)
           << "InteractiveRender"
           << vtkClientServerStream::End;
    this->ExecuteStream(stream, false, render_location);
    }

  this->PostRender(interactive==1);
  this->GetSession()->CleanupPendingProgress();
  this->InvokeEvent(vtkCommand::EndEvent, &interactive);
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::Update()
{
  if (this->ObjectsCreated && this->NeedsUpdate)
    {
    vtkClientServerStream stream;

    // To avoid race conditions in multi-client modes, we are taking a peculiar
    // approach. Any ivar that affect parallel communication are overridden
    // using the client-side values in the same ExecuteStream() call. That
    // ensures that two clients cannot enter race condition. This results in minor
    // increase in the size of the messages sent, but overall the benefits are
    // greater.
    vtkPVView* pvview = vtkPVView::SafeDownCast(this->GetClientSideObject());
    if (pvview)
      {
      int use_cache =  pvview->GetUseCache()? 1 : 0;
      stream << vtkClientServerStream::Invoke
             << VTKOBJECT(this)
             << "SetUseCache" << use_cache
             << vtkClientServerStream::End;
      }
    stream << vtkClientServerStream::Invoke
           << VTKOBJECT(this)
           << "Update"
           << vtkClientServerStream::End;
    this->GetSession()->PrepareProgress();
    this->ExecuteStream(stream);
    this->GetSession()->CleanupPendingProgress();

    unsigned int numProducers = this->GetNumberOfProducers();
    for (unsigned int i=0; i<numProducers; i++)
      {
      vtkSMRepresentationProxy* repr = vtkSMRepresentationProxy::SafeDownCast(
        this->GetProducerProxy(i));
      if (repr)
        {
        repr->ViewUpdated(this);
        }
      else
        {
        //this->GetProducerProxy(i)->PostUpdateData();
        }
      }

    this->PostUpdateData();
    }
}

//----------------------------------------------------------------------------
vtkSMRepresentationProxy* vtkSMViewProxy::CreateDefaultRepresentation(
  vtkSMProxy* proxy, int outputPort)
{
  assert("The session should be valid" && this->Session);

  vtkSMSourceProxy* producer = vtkSMSourceProxy::SafeDownCast(proxy);
  if (
    (producer == NULL) ||
    (outputPort < 0) ||
    (static_cast<int>(producer->GetNumberOfOutputPorts()) <= outputPort) ||
    (producer->GetSession() != this->GetSession()))
    {
    return NULL;
    }

  // Update with time from the view to ensure we have up-to-date data.
  double view_time = vtkSMPropertyHelper(this, "ViewTime").GetAsDouble();
  producer->UpdatePipeline(view_time);

  const char* representationType =
    this->GetRepresentationType(producer, outputPort);
  if (!representationType)
    {
    return NULL;
    }

  vtkSMSessionProxyManager* pxm = this->GetSessionProxyManager();
  vtkSmartPointer<vtkSMProxy> p;
  p.TakeReference(pxm->NewProxy("representations", representationType));
  vtkSMRepresentationProxy* repr = vtkSMRepresentationProxy::SafeDownCast(p);
  if (repr)
    {
    repr->Register(this);
    return repr;
    }
  vtkWarningMacro("Failed to create representation (representations,"
    << representationType <<").");
  return NULL;
}

//----------------------------------------------------------------------------
const char* vtkSMViewProxy::GetRepresentationType(
  vtkSMSourceProxy* producer, int outputPort)
{
  assert(producer &&
         static_cast<int>(producer->GetNumberOfOutputPorts()) > outputPort);

  // Process producer hints to see if indicates what type of representation
  // to create for this view.
  if (const char* reprName = vtkGetRepresentationNameFromHints(
      this->GetXMLName(), producer->GetHints(), outputPort))
    {
    return reprName;
    }

  // check if we have default representation name specified in XML.
  if (this->DefaultRepresentationName)
    {
    vtkSMSessionProxyManager* pxm = this->GetSessionProxyManager();
    vtkSMProxy* prototype = pxm->GetPrototypeProxy(
      "representations", this->DefaultRepresentationName);
    if (prototype)
      {
      vtkSMProperty* inputProp = prototype->GetProperty("Input");
      vtkSMUncheckedPropertyHelper helper(inputProp);
      helper.Set(producer, outputPort);
      bool acceptable = (inputProp->IsInDomains() > 0);
      helper.SetNumberOfElements(0);

      if (acceptable)
        {
        return this->DefaultRepresentationName;
        }
      }
    }

  return NULL;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::CanDisplayData(vtkSMSourceProxy* producer, int outputPort)
{
  if (producer == NULL || outputPort < 0 ||
      static_cast<int>(producer->GetNumberOfOutputPorts()) <= outputPort ||
      producer->GetSession() != this->GetSession())
    {
    return false;
    }

  const char* type = this->GetRepresentationType(producer, outputPort);
  if (type != NULL)
    {
    vtkSMSessionProxyManager* pxm = this->GetSessionProxyManager();
    return (pxm->GetPrototypeProxy("representations", type) != NULL);
    }

  return false;
}

//----------------------------------------------------------------------------
vtkSMRepresentationProxy* vtkSMViewProxy::FindRepresentation(
  vtkSMSourceProxy* producer, int outputPort)
{
  vtkSMPropertyHelper helper (this, "Representations");
  for (unsigned int cc=0, max=helper.GetNumberOfElements(); cc < max; ++cc)
    {
    vtkSMRepresentationProxy* repr =
      vtkSMRepresentationProxy::SafeDownCast(helper.GetAsProxy(cc));
    if (repr &&
      repr->GetProperty("Input"))
      {
      vtkSMPropertyHelper helper2(repr, "Input");
      if (helper2.GetAsProxy() == producer &&
          static_cast<int>(helper2.GetOutputPort()) == outputPort)
        {
        return repr;
        }
      }
    }
  return NULL;
}

//----------------------------------------------------------------------------
int vtkSMViewProxy::ReadXMLAttributes(
  vtkSMSessionProxyManager* pm, vtkPVXMLElement* element)
{
  if (!this->Superclass::ReadXMLAttributes(pm, element))
    {
    return 0;
    }

  const char* repr_name = element->GetAttribute("representation_name");
  if (repr_name)
    {
    this->SetDefaultRepresentationName(repr_name);
    }
  return 1;
}

class vtkSMViewProxy::vtkRendererSaveInfo
{
public:
  vtkRendererSaveInfo(vtkRenderer* renderer)
    : Gradient(renderer->GetGradientBackground())
    , Textured(renderer->GetTexturedBackground())
    , Red(renderer->GetBackground()[0])
    , Green(renderer->GetBackground()[1])
    , Blue(renderer->GetBackground()[2])
  {
  }

  const bool Gradient;
  const bool Textured;
  const double Red;
  const double Green;
  const double Blue;
private:
  vtkRendererSaveInfo(const vtkRendererSaveInfo&); // Not implemented
  void operator=(const vtkRendererSaveInfo&); // Not implemented
};

//----------------------------------------------------------------------------
vtkImageData* vtkSMViewProxy::CaptureWindow(int magnification)
{
  vtkRenderWindow* window = this->GetRenderWindow();

  if (window && this->TransparentBackground)
    {
    vtkRendererCollection* renderers = window->GetRenderers();
    vtkRenderer* renderer = renderers->GetFirstRenderer();
    while (renderer)
      {
      if (renderer->GetErase())
        {
        // Found a background-writing renderer.
        break;
        }

      renderer = renderers->GetNextItem();
      }

    if (!renderer)
      {
      // No renderer?
      return NULL;
      }

    vtkRendererSaveInfo* info = this->PrepareRendererBackground(renderer, 255, 255, 255, true);
    vtkImageData* captureWhite = this->CaptureWindowSingle(magnification);

    this->PrepareRendererBackground(renderer, 0, 0, 0, false);
    vtkImageData* captureBlack = this->CaptureWindowSingle(magnification);

    vtkImageData* capture = vtkImageData::New();
    capture->CopyStructure(captureWhite);
    capture->AllocateScalars(VTK_UNSIGNED_CHAR, 4);

    const unsigned char* white;
    const unsigned char* black;
    unsigned char* out;
    vtkIdType whiteStep[3];
    vtkIdType blackStep[3];
    vtkIdType outStep[3];

    white = static_cast<const unsigned char*>(captureWhite->GetScalarPointerForExtent(captureWhite->GetExtent()));
    black = static_cast<const unsigned char*>(captureBlack->GetScalarPointerForExtent(captureBlack->GetExtent()));
    out = static_cast<unsigned char*>(capture->GetScalarPointerForExtent(capture->GetExtent()));

    captureWhite->GetIncrements(whiteStep[0], whiteStep[1], whiteStep[2]);
    captureBlack->GetIncrements(blackStep[0], blackStep[1], blackStep[2]);
    capture->GetIncrements(outStep[0], outStep[1], outStep[2]);

    int* extent = capture->GetExtent();

    for (int i = extent[4]; i <= extent[5]; ++i)
      {
      const unsigned char* whiteRow = white;
      const unsigned char* blackRow = black;
      unsigned char* outRow = out;

      for (int j = extent[2]; j <= extent[3]; ++j)
        {
        const unsigned char* whitePx = whiteRow;
        const unsigned char* blackPx = blackRow;
        unsigned char* outPx = outRow;

        for (int k = extent[0]; k <= extent[1]; ++k)
          {
          if (whitePx[0] == blackPx[0] &&
              whitePx[1] == blackPx[1] &&
              whitePx[2] == blackPx[2])
            {
            outPx[0] = whitePx[0];
            outPx[1] = whitePx[1];
            outPx[2] = whitePx[2];
            outPx[3] = 255;
            }
          else
            {
            // Some kind of translucency; use values from the black capture.
            // The opacity is the derived from the V difference of the HSV
            // colors.
            double whiteHSV[3];
            double blackHSV[3];

            vtkMath::RGBToHSV(whitePx[0] / 255., whitePx[1] / 255., whitePx[2] / 255.,
                              whiteHSV + 0, whiteHSV + 1, whiteHSV + 2);
            vtkMath::RGBToHSV(blackPx[0] / 255., blackPx[1] / 255., blackPx[2] / 255.,
                              blackHSV + 0, blackHSV + 1, blackHSV + 2);
            double alpha = 1. - (whiteHSV[2] - blackHSV[2]);

            outPx[0] = static_cast<unsigned char>(blackPx[0] / alpha);
            outPx[1] = static_cast<unsigned char>(blackPx[1] / alpha);
            outPx[2] = static_cast<unsigned char>(blackPx[2] / alpha);
            outPx[3] = static_cast<unsigned char>(255 * alpha);
            }

          whitePx += whiteStep[0];
          blackPx += blackStep[0];
          outPx += outStep[0];
          }

        whiteRow += whiteStep[1];
        blackRow += blackStep[1];
        outRow += outStep[1];
        }

      white += whiteStep[2];
      black += blackStep[2];
      out += outStep[2];
      }

    this->RestoreRendererBackground(renderer, info);

    captureWhite->Delete();
    captureBlack->Delete();
    return capture;
    }

  // Fall back to using no transparency.
  return this->CaptureWindowSingle(magnification);
}

//----------------------------------------------------------------------------
vtkSMViewProxy::vtkRendererSaveInfo* vtkSMViewProxy::PrepareRendererBackground(
  vtkRenderer* renderer,
  double r, double g, double b, bool save)
{
  vtkRendererSaveInfo* info = NULL;

  if (save)
    {
    info = new vtkRendererSaveInfo(renderer);
    }

  renderer->SetGradientBackground(false);
  renderer->SetTexturedBackground(false);
  renderer->SetBackground(r, g, b);

  return info;
}

//----------------------------------------------------------------------------
void vtkSMViewProxy::RestoreRendererBackground(vtkRenderer* renderer,
                                               vtkRendererSaveInfo* info)
{
  renderer->SetGradientBackground(info->Gradient);
  renderer->SetTexturedBackground(info->Textured);
  renderer->SetBackground(info->Red, info->Green, info->Blue);

  delete info;
}

//----------------------------------------------------------------------------
vtkImageData* vtkSMViewProxy::CaptureWindowSingle(int magnification)
{
  if (this->ObjectsCreated)
    {
    vtkClientServerStream stream;
    stream << vtkClientServerStream::Invoke
           << VTKOBJECT(this)
           << "PrepareForScreenshot"
           << vtkClientServerStream::End;
    this->ExecuteStream(stream);
    }

  vtkImageData* capture = this->CaptureWindowInternal(magnification);

  if (this->ObjectsCreated)
    {
    vtkClientServerStream stream;
    stream << vtkClientServerStream::Invoke
           << VTKOBJECT(this)
           << "CleanupAfterScreenshot"
           << vtkClientServerStream::End;
    this->ExecuteStream(stream);
    }

  if (capture)
    {
    int position[2];
    vtkSMPropertyHelper(this, "ViewPosition").Get(position, 2);

    // Update image extents based on ViewPosition
    int extents[6];
    capture->GetExtent(extents);
    for (int cc=0; cc < 4; cc++)
      {
      extents[cc] += position[cc/2]*magnification;
      }
    capture->SetExtent(extents);
    }
  return capture;
}

//-----------------------------------------------------------------------------
int vtkSMViewProxy::WriteImage(const char* filename,
  const char* writerName, int magnification)
{
  if (!filename || !writerName)
    {
    return vtkErrorCode::UnknownError;
    }

  vtkSmartPointer<vtkImageData> shot;
  shot.TakeReference(this->CaptureWindow(magnification));

  if (vtkProcessModule::GetProcessModule()->GetOptions()->GetSymmetricMPIMode())
    {
    return vtkSMUtilities::SaveImageOnProcessZero(shot, filename, writerName);
    }
  return vtkSMUtilities::SaveImage(shot, filename, writerName);
}

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

//----------------------------------------------------------------------------
void vtkSMViewProxy::SetTransparentBackground(bool val)
{
  vtkSMViewProxy::TransparentBackground = val;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::GetTransparentBackground()
{
  return vtkSMViewProxy::TransparentBackground;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::IsContextReadyForRendering()
{
  if (vtkRenderWindow* window = this->GetRenderWindow())
    {
    return window->IsDrawable();
    }
  return true;
}


//----------------------------------------------------------------------------
bool vtkSMViewProxy::HideOtherRepresentationsIfNeeded(vtkSMProxy* repr)
{
  if (repr == NULL ||
    this->GetHints() == NULL ||
    this->GetHints()->FindNestedElementByName("ShowOneRepresentationAtATime") == NULL)
    {
    return false;
    }

  vtkNew<vtkSMParaViewPipelineControllerWithRendering> controller;

  bool modified = false;
  vtkSMPropertyHelper helper(this, "Representations");
  for (unsigned int cc=0, max=helper.GetNumberOfElements(); cc < max; ++cc)
    {
    vtkSMRepresentationProxy* arepr =
      vtkSMRepresentationProxy::SafeDownCast(helper.GetAsProxy(cc));
    if (arepr && arepr != repr)
      {
      if (vtkSMPropertyHelper(arepr, "Visibility", /*quiet*/true).GetAsInt() == 1)
        {
        controller->Hide(arepr, this);
        modified = true;
        }
      }
    }
  return modified;
}

//----------------------------------------------------------------------------
bool vtkSMViewProxy::GetLocalProcessSupportsInteraction()
{
  this->CreateVTKObjects();
  vtkPVView* pvview = vtkPVView::SafeDownCast(this->GetClientSideObject());
  return pvview? pvview->GetLocalProcessSupportsInteraction() : false;
}


//----------------------------------------------------------------------------
bool vtkSMViewProxy::MakeRenderWindowInteractor(bool quiet)
{
  if (this->GetInteractor() != NULL)
    {
    // all's setup already. nothing to do.
    return true;
    }
  if (!this->GetLocalProcessSupportsInteraction())
    {
    return false;
    }

  vtkRenderWindow* renWin = this->GetRenderWindow();
  if (!renWin)
    {
    if (!quiet)
      {
      vtkWarningMacro("Not a view that has a vtkRenderWindow. Cannot setup interactor.");
      }
    return false;
    }
  if (renWin->GetMapped())
    {
    if (!quiet)
      {
      vtkErrorMacro("Window is currently mapped. "
        "Currently, interaction is only supported on unmapped windows.");
      }
    return false;
    }
  // in reality batch shouldn't have an interactor at all. However, to avoid the
  // mismatch in the vtkPVAxesWidget (orientation widget) when using pvpython or
  // pvbatch, we do create one. However, lets create a non-interactive
  // interactor in batch mode.
  vtkSmartPointer<vtkRenderWindowInteractor> iren;
  if (vtkProcessModule::GetProcessType() != vtkProcessModule::PROCESS_BATCH)
    {
    iren = renWin->MakeRenderWindowInteractor();
    }
  else
    {
    iren = vtkSmartPointer<vtkGenericRenderWindowInteractor>::New();
    // This initialize is essential. Otherwise vtkRenderWindow::Render causes
    // the interactor to initialize which in turn triggers a render!
    iren->Initialize();
    }
  this->SetupInteractor(iren);
  return this->GetInteractor() != NULL;
}
