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

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

#include <QtCore>
#include <QGridLayout>
#include <QtGui>

#include "QVTKWidget.h"

#include "pqDataRepresentation.h"
#include "pqMultiSliceAxisWidget.h"
#include "pqPropertyLinks.h"
#include "pqRepresentation.h"

#include "vtkAxis.h"
#include "vtkChartXY.h"
#include "vtkDataRepresentation.h"
#include "vtkMath.h"
#include "vtkPlot.h"
#include "vtkPVDataInformation.h"
#include "vtkPVRenderView.h"
#include "vtkSMMultiSliceViewProxy.h"
#include "vtkSMProperty.h"
#include "vtkSMPropertyHelper.h"
#include "vtkSMRenderViewProxy.h"
#include "vtkSMRepresentationProxy.h"
#include "vtkView.h"

#define MULTI_SLICE_AXIS_THIKNESS 80
#define MULTI_SLICE_AXIS_ACTIVE_SIZE 20
#define MULTI_SLICE_AXIS_EDGE_MARGIN 10

//-----------------------------------------------------------------------------
pqMultiSliceView::pqMultiSliceView(
    const QString& viewType, const QString& group, const QString& name,
    vtkSMViewProxy* viewProxy, pqServer* server, QObject* p)
  : pqRenderView(viewType, group, name, viewProxy, server, p)
{
  // Nothing is changing anything.
  this->UserIsInteracting = false;

  // When data change make sure the bounds are updated
  QObject::connect(this, SIGNAL(updateDataEvent()),
                   this, SLOT(updateAxisBounds()));

  // Make sure if the proxy change by undo-redo, we properly update the Qt UI
  viewProxy->GetProperty("XSlicesValues")->AddObserver(
        vtkCommand::ModifiedEvent, this,
        &pqMultiSliceView::updateViewModelCallBack);
  viewProxy->GetProperty("YSlicesValues")->AddObserver(
        vtkCommand::ModifiedEvent, this,
        &pqMultiSliceView::updateViewModelCallBack);
  viewProxy->GetProperty("ZSlicesValues")->AddObserver(
        vtkCommand::ModifiedEvent, this,
        &pqMultiSliceView::updateViewModelCallBack);

  for(int i=0;i<9;++i)
    {
    this->NormalValuesHolder[i] = this->OriginValuesHolder[i] = 0;
    }
}

//-----------------------------------------------------------------------------
pqMultiSliceView::~pqMultiSliceView()
{
}
//-----------------------------------------------------------------------------
QWidget* pqMultiSliceView::createWidget()
{
  pqPropertyLinks *links = new pqPropertyLinks(this);
  vtkSMProxy* smproxy = this->getProxy();

  // Get the internal widget that we want to decorate
  this->InternalWidget = qobject_cast<QVTKWidget*>(this->pqRenderView::createWidget());

  // Build the widget hierarchy
  QWidget* container = new QWidget();
  container->setStyleSheet("background-color: white");
  container->setAutoFillBackground(true);

  QGridLayout* gridLayout = new QGridLayout(container);
  this->InternalWidget->setParent(container);

  // Init top axis
  this->AxisX = new pqMultiSliceAxisWidget(container);
  this->AxisX->setAxisType(vtkAxis::LEFT);
  this->AxisX->setRange(-10,10);
  this->AxisX->setTitle("X");
  this->AxisX->SetEdgeMargin(MULTI_SLICE_AXIS_EDGE_MARGIN);
  this->AxisX->SetActiveSize(MULTI_SLICE_AXIS_ACTIVE_SIZE);
  this->AxisX->setFixedWidth(MULTI_SLICE_AXIS_THIKNESS);
  this->AxisX->renderView();

  this->AxisY = new pqMultiSliceAxisWidget(container);
  this->AxisY->setAxisType(vtkAxis::TOP);
  this->AxisY->setRange(-10,10);
  this->AxisY->setTitle("Y");
  this->AxisY->SetEdgeMargin(MULTI_SLICE_AXIS_EDGE_MARGIN);
  this->AxisY->SetActiveSize(MULTI_SLICE_AXIS_ACTIVE_SIZE);
  this->AxisY->setFixedHeight(MULTI_SLICE_AXIS_THIKNESS - 4);
  this->AxisY->renderView();

  this->AxisZ = new pqMultiSliceAxisWidget(container);
  this->AxisZ->setAxisType(vtkAxis::RIGHT);
  this->AxisZ->setRange(-10,10);
  this->AxisZ->setTitle("Z");
  this->AxisZ->SetEdgeMargin(MULTI_SLICE_AXIS_EDGE_MARGIN);
  this->AxisZ->SetActiveSize(MULTI_SLICE_AXIS_ACTIVE_SIZE);
  this->AxisZ->setFixedWidth(MULTI_SLICE_AXIS_THIKNESS);
  this->AxisZ->renderView();

  this->AxisXYZ[0] = this->AxisX;
  this->AxisXYZ[1] = this->AxisY;
  this->AxisXYZ[2] = this->AxisZ;

  // Setup links so the UI updates when the property changes.
  links->addPropertyLink(this->AxisX, "title", SIGNAL(titleChanged(const QString&)),
    smproxy, smproxy->GetProperty("XTitle"));
  this->AxisX->connect(links, SIGNAL(smPropertyChanged()), SLOT(renderView()));

  links->addPropertyLink(this->AxisY, "title", SIGNAL(titleChanged(const QString&)),
    smproxy, smproxy->GetProperty("YTitle"));
  this->AxisY->connect(links, SIGNAL(smPropertyChanged()), SLOT(renderView()));

  links->addPropertyLink(this->AxisZ, "title", SIGNAL(titleChanged(const QString&)),
    smproxy, smproxy->GetProperty("ZTitle"));
  this->AxisZ->connect(links, SIGNAL(smPropertyChanged()), SLOT(renderView()));


  gridLayout->addWidget(this->AxisY, 0, 1);  // TOP
  gridLayout->addWidget(this->AxisX, 1, 0); // LEFT
  gridLayout->addWidget(this->AxisZ, 1, 2); // RIGHT
  gridLayout->addWidget(this->InternalWidget,1,1);
  gridLayout->setContentsMargins(0,0,0,0);
  gridLayout->setSpacing(0);

  // Properly do the binding between the proxy and the 3D widget
  vtkSMRenderViewProxy* renModule = this->getRenderViewProxy();
  if (this->InternalWidget && renModule)
    {
    this->InternalWidget->SetRenderWindow(renModule->GetRenderWindow());
    }

  for (int cc=0; cc < 3; cc++)
    {
    // Make sure we are aware when the slice positions changes
    this->connect(this->AxisXYZ[cc], SIGNAL(sliceAdded(int)), SLOT(onSliceAdded(int)));
    this->connect(this->AxisXYZ[cc], SIGNAL(sliceRemoved(int)), SLOT(onSliceRemoved(int)));
    this->connect(this->AxisXYZ[cc], SIGNAL(sliceModified(int)), SLOT(onSliceModified(int)));
    // Attach click listener to slice marks
    this->connect(this->AxisXYZ[cc], SIGNAL(markClicked(int,int,double)), SLOT(onSliceClicked(int,int,double)));
    }

  // Make sure the UI reflect the proxy state
  this->updateViewModelCallBack(NULL, 0, NULL);

  return container;
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::updateAxisBounds()
{
  double bounds[6];
  vtkSMMultiSliceViewProxy* viewPxy = vtkSMMultiSliceViewProxy::SafeDownCast(
    this->getProxy());
  Q_ASSERT(viewPxy);

  viewPxy->GetDataBounds(bounds);
  if (vtkMath::AreBoundsInitialized(bounds))
    {
    this->AxisX->setRange(bounds[0], bounds[1]);
    this->AxisY->setRange(bounds[2], bounds[3]);
    this->AxisZ->setRange(bounds[4], bounds[5]);
    }
  else
    {
    this->AxisX->setRange(-10, 10);
    this->AxisY->setRange(-10, 10);
    this->AxisZ->setRange(-10, 10);
    }

  // Make sure we render the new range
  this->AxisX->renderView();
  this->AxisY->renderView();
  this->AxisZ->renderView();
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::updateSlices()
{
  // Make sure we know that the origin of the changes come from the UI
  this->UserIsInteracting = true;

  int nbSliceX = 0;
  const double* sliceX = this->AxisX->getVisibleSlices(nbSliceX);
  int nbSliceY = 0;
  const double* sliceY = this->AxisY->getVisibleSlices(nbSliceY);
  int nbSliceZ = 0;
  const double* sliceZ = this->AxisZ->getVisibleSlices(nbSliceZ);

  // Update view which will notify its representation that care about those info
  vtkSMViewProxy* view = this->getViewProxy();
  vtkSMPropertyHelper(view, "XSlicesValues").Set(sliceX, nbSliceX);
  vtkSMPropertyHelper(view, "YSlicesValues").Set(sliceY, nbSliceY);
  vtkSMPropertyHelper(view, "ZSlicesValues").Set(sliceZ, nbSliceZ);
  view->UpdateVTKObjects();

  this->render();

  // Get back to an idle mode
  this->UserIsInteracting = false;
}

//-----------------------------------------------------------------------------
int pqMultiSliceView::getAxisIndex(QObject* axis)
{
  for (int cc=0; cc < 3; cc++)
    {
    if (this->AxisXYZ[cc] == axis)
      {
      return cc;
      }
    }
  return -1;
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::onSliceAdded(int activeSliceIndex)
{
  QObject* aSender = this->sender();
  int axisIndex = this->getAxisIndex(aSender);

  Q_ASSERT(axisIndex >= 0 && axisIndex <=2);
  this->updateSlices();

  // Notify that the slices location have changed
  emit sliceAdded(axisIndex, activeSliceIndex);
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::onSliceRemoved(int activeSliceIndex)
{
  QObject* aSender = this->sender();
  int axisIndex = this->getAxisIndex(aSender);
  Q_ASSERT(axisIndex >= 0 && axisIndex <=2);
  this->updateSlices();

  // Notify that the slices location have changed
  emit sliceRemoved(axisIndex, activeSliceIndex);
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::onSliceModified(int activeSliceIndex)
{
  QObject* aSender = this->sender();
  int axisIndex = this->getAxisIndex(aSender);
  Q_ASSERT(axisIndex >= 0 && axisIndex <=2);
  this->updateSlices();

  // Notify that the slices location have changed
  emit sliceModified(axisIndex, activeSliceIndex);
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::updateViewModelCallBack(vtkObject*,unsigned long, void*)
{
  if(this->UserIsInteracting)
    {
    return; // We don't want to update our data model
    }

  std::vector<double> xSlices =
      vtkSMPropertyHelper(this->getViewProxy(), "XSlicesValues").GetDoubleArray();
  std::vector<double> ySlices =
      vtkSMPropertyHelper(this->getViewProxy(), "YSlicesValues").GetDoubleArray();
  std::vector<double> zSlices =
      vtkSMPropertyHelper(this->getViewProxy(), "ZSlicesValues").GetDoubleArray();

  assert("The maximum number of slice can not be bigger than 255" &&
         ( 255 >
           std::max(
             std::max(
               xSlices.size(), ySlices.size()), zSlices.size())));

  // Build a tmp array of visibility set to true
  bool visibility[255];
  memset(visibility, true, 255);

  double emptyDouble = 0;
  if( xSlices.size() > 0 )
    {
    this->AxisX->updateSlices(&xSlices[0], visibility, static_cast<int>(xSlices.size()));
    }
  else // Reset slices
    {
    this->AxisX->updateSlices(&emptyDouble, visibility, 0);
    }

  if( ySlices.size() > 0 )
    {
    this->AxisY->updateSlices(&ySlices[0], visibility, static_cast<int>(ySlices.size()));
    }
  else // Reset slices
    {
    this->AxisY->updateSlices(&emptyDouble, visibility, 0);
    }

  if( zSlices.size() > 0 )
    {
    this->AxisZ->updateSlices(&zSlices[0], visibility, static_cast<int>(zSlices.size()));
    }
  else // Reset slices
    {
    this->AxisZ->updateSlices(&emptyDouble, visibility, 0);
    }

  this->render();
}
//-----------------------------------------------------------------------------
const double* pqMultiSliceView::GetVisibleSlices(int axisIndex, int &numberOfSlices)
{
  switch(axisIndex)
    {
  case 0:
    return this->AxisX->getVisibleSlices(numberOfSlices);
  case 1:
    return this->AxisY->getVisibleSlices(numberOfSlices);
  case 2:
    return this->AxisZ->getVisibleSlices(numberOfSlices);
    }

  // Invalid axis
  numberOfSlices = 0;
  return NULL;
}

//-----------------------------------------------------------------------------
const double* pqMultiSliceView::GetAllSlices(int axisIndex, int &numberOfSlices)
{
  switch(axisIndex)
    {
  case 0:
    return this->AxisX->getSlices(numberOfSlices);
  case 1:
    return this->AxisY->getSlices(numberOfSlices);
  case 2:
    return this->AxisZ->getSlices(numberOfSlices);
    }

  // Invalid axis
  numberOfSlices = 0;
  return NULL;
}

//-----------------------------------------------------------------------------
const double* pqMultiSliceView::GetSliceNormal(int axisIndex)
{
  if(axisIndex < 0 || axisIndex > 2)
    {
    return NULL;
    }

  const char* propertyNames[3] = {"XSlicesNormal", "YSlicesNormal", "ZSlicesNormal"};
  std::vector<double> values =
      vtkSMPropertyHelper(this->getViewProxy(), propertyNames[axisIndex]).GetDoubleArray();
  for(int i=0;i<3;++i)
    {
    this->NormalValuesHolder[i+ (3*axisIndex)] = values[i];
    }
  return &this->NormalValuesHolder[(3*axisIndex)];
}

//-----------------------------------------------------------------------------
const double* pqMultiSliceView::GetSliceOrigin(int axisIndex)
{
  if(axisIndex < 0 || axisIndex > 2)
    {
    return NULL;
    }

  const char* propertyNames[3] = {"XSlicesOrigin", "YSlicesOrigin", "ZSlicesOrigin"};
  std::vector<double> values =
      vtkSMPropertyHelper(this->getViewProxy(), propertyNames[axisIndex]).GetDoubleArray();
  for(int i=0;i<3;++i)
    {
    this->OriginValuesHolder[i+ (3*axisIndex)] = values[i];
    }
  return &this->OriginValuesHolder[(3*axisIndex)];
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::onSliceClicked(int button, int modifier, double value)
{
  QObject* senderObject = QObject::sender();
  if(senderObject == this->AxisX.data())
    {
    emit sliceClicked(0, value, button, modifier);
    }
  else if(senderObject == this->AxisY.data())
    {
    emit sliceClicked(1, value, button, modifier);
    }
  else if(senderObject == this->AxisZ.data())
    {
    emit sliceClicked(2, value, button, modifier);
    }
}
//-----------------------------------------------------------------------------
void pqMultiSliceView::setCursor(const QCursor &c)
{
  if(this->InternalWidget)
    {
    this->InternalWidget->setCursor(c);
    }
}
//-----------------------------------------------------------------------------
QVTKWidget* pqMultiSliceView::getInternalWidget()
{
  return this->InternalWidget;
}

//-----------------------------------------------------------------------------
bool pqMultiSliceView::getOutlineVisibility()
{
  return vtkSMPropertyHelper(this->getViewProxy(), "ShowOutline").GetAsInt() != 0;
}

//-----------------------------------------------------------------------------
void pqMultiSliceView::setOutlineVisibility(bool visible)
{
  vtkSMPropertyHelper(this->getViewProxy(), "ShowOutline").Set(visible ? 1 : 0);
  this->getViewProxy()->UpdateVTKObjects();
}
