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

  Program:   Visualization Toolkit
  Module:    vtkDepthSortPainter.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.

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

// .NAME vtkDepthSortPainter
// .SECTION Thanks
// <verbatim>
//
//  This file is part of the PointSprites plugin developed and contributed by
//
//  Copyright (c) CSCS - Swiss National Supercomputing Centre
//                EDF - Electricite de France
//
//  John Biddiscombe, Ugo Varetto (CSCS)
//  Stephane Ploix (EDF)
//
// </verbatim>

#include "vtkDepthSortPainter.h"

#include "vtkCamera.h"
#include "vtkCell.h"
#include "vtkCellArray.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkDataSet.h"
#include "vtkDepthSortPolyData.h"
#include "vtkFloatArray.h"
#include "vtkIdTypeArray.h"
#include "vtkMath.h"
#include "vtkMatrix4x4.h"
#include "vtkObjectFactory.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkProperty.h"
#include "vtkRenderer.h"
#include "vtkScalarsToColors.h"
#include "vtkSortDataArray.h"
#include "vtkTexture.h"

#include <algorithm>
#include <functional>
#include <vector>

#include "vtkCellData.h"
#include "vtkImageData.h"
#include "vtkPNGWriter.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include <cmath>

//-----------------------------------------------------------------------------
vtkStandardNewMacro(vtkDepthSortPainter)
  //-----------------------------------------------------------------------------
  vtkCxxSetObjectMacro(vtkDepthSortPainter, DepthSortPolyData, vtkDepthSortPolyData);
vtkCxxSetObjectMacro(vtkDepthSortPainter, OutputData, vtkDataObject);

vtkDepthSortPainter::vtkDepthSortPainter()
{
  this->DepthSortEnableMode = ENABLE_SORT_IF_NO_DEPTH_PEELING;
  this->CachedIsTextureSemiTranslucent = 1;
  this->CachedIsColorSemiTranslucent = 1;
  this->DepthSortPolyData = vtkDepthSortPolyData::New();
  this->OutputData = NULL;
}
//-----------------------------------------------------------------------------
vtkDepthSortPainter::~vtkDepthSortPainter()
{
  this->SetDepthSortPolyData(NULL);
  this->SetOutputData(NULL);
}
//-----------------------------------------------------------------------------
void vtkDepthSortPainter::PrintSelf(ostream& os, vtkIndent indent)
{
  this->Superclass::PrintSelf(os, indent);
}

//-----------------------------------------------------------------------------
void vtkDepthSortPainter::PrepareForRendering(vtkRenderer* renderer, vtkActor* actor)
{
  // first set the DepthSortPolyData ivars
  if (this->DepthSortPolyData != NULL)
  {
    this->DepthSortPolyData->SetCamera(renderer->GetActiveCamera());
    this->DepthSortPolyData->SetProp3D(actor);
    this->DepthSortPolyData->SetDirectionToBackToFront();
  }

  // check if we need to update
  if (this->GetMTime() < this->SortTime && this->DepthSortPolyData->GetMTime() < this->SortTime &&
    this->GetInput()->GetMTime() < this->SortTime)
  {
    return;
  }

  // update the OutputData, initialize it with a shallow copy of the input
  this->SetOutputData(NULL);
  vtkDataObject* input = this->GetInput();
  if (!input)
    return;

  vtkDataObject* output = input->NewInstance();
  output->ShallowCopy(input);
  this->SetOutputData(output);
  output->Delete();

  if (this->DepthSortPolyData != NULL && this->NeedSorting(renderer, actor))
  {
    if (input->IsA("vtkCompositeDataSet"))
    {
      vtkCompositeDataSet* cdInput = vtkCompositeDataSet::SafeDownCast(input);
      vtkCompositeDataSet* cdOutput = vtkCompositeDataSet::SafeDownCast(this->OutputData);
      vtkCompositeDataIterator* iter = cdInput->NewIterator();
      for (iter->InitTraversal(); !iter->IsDoneWithTraversal(); iter->GoToNextItem())
      {
        vtkDataSet* pdInput = vtkDataSet::SafeDownCast(iter->GetCurrentDataObject());
        vtkDataSet* pdOutput = vtkDataSet::SafeDownCast(cdOutput->GetDataSet(iter));
        if (pdInput && pdOutput)
        {
          this->Sort(pdOutput, pdInput, renderer, actor);
        }
      }

      iter->Delete();
    }
    else
    {
      this->Sort(vtkDataSet::SafeDownCast(this->OutputData), vtkDataSet::SafeDownCast(input),
        renderer, actor);
    }
    this->SortTime.Modified();
  }
}

void vtkDepthSortPainter::Sort(vtkDataSet* output, vtkDataSet* input,
  vtkRenderer* vtkNotUsed(renderer), vtkActor* vtkNotUsed(actor))
{
  this->DepthSortPolyData->SetInputData(input);

  this->DepthSortPolyData->Update();

  vtkPolyData* polyData = this->DepthSortPolyData->GetOutput();

  output->ShallowCopy(polyData);
}

int vtkDepthSortPainter::NeedSorting(vtkRenderer* renderer, vtkActor* actor)
{
  if (!actor || !renderer)
    return false;

  if (this->GetDepthSortEnableMode() == ENABLE_SORT_NEVER)
    return false;

  if (this->GetDepthSortEnableMode() == ENABLE_SORT_IF_NO_DEPTH_PEELING &&
    renderer->GetUseDepthPeeling())
    return false;

  if (actor->GetProperty()->GetOpacity() < 1)
    return true;

  // if the color array has an alpha component, return true.
  // rem : this alpha component can come from the vtkTwoScalarsToColors painter,
  // and thus cannot be simply deduced from the opacity and lut.
  vtkUnsignedCharArray* colors = NULL;
  vtkPolyData* input = vtkPolyData::SafeDownCast(this->GetInput());
  if (input)
  {
    colors = vtkUnsignedCharArray::SafeDownCast(input->GetPointData()->GetScalars());
    if (!colors)
    {
      colors = vtkUnsignedCharArray::SafeDownCast(input->GetCellData()->GetScalars());
    }
    if (!colors)
    {
      colors = vtkUnsignedCharArray::SafeDownCast(input->GetFieldData()->GetArray("Color"));
    }
    if (colors && this->IsColorSemiTranslucent(colors))
      return true;
  }

  // if the texture is either fully opaque or fully transparent, return false
  if (actor->GetTexture() != NULL && !this->IsTextureSemiTranslucent(actor->GetTexture()))
    return false;

  return actor->HasTranslucentPolygonalGeometry();
}

int vtkDepthSortPainter::IsTextureSemiTranslucent(vtkTexture* tex)
{
  if (tex == NULL)
  {
    return -1; // undetermined
  }
  if (tex == this->CachedTexture && this->CachedIsTextureSemiTranslucentTime > tex->GetMTime() &&
    this->CachedIsTextureSemiTranslucentTime > this->GetMTime())
  {
    return this->CachedIsTextureSemiTranslucent;
  }
  this->CachedIsTextureSemiTranslucent = 1;
  this->CachedTexture = tex;
  this->CachedIsTextureSemiTranslucentTime.Modified();

  if (!tex->GetMapColorScalarsThroughLookupTable() && tex->GetInput())
  {
    vtkImageData* image = tex->GetInput();
    vtkUnsignedCharArray* data =
      vtkUnsignedCharArray::SafeDownCast(image->GetPointData()->GetScalars());
    if (data)
    {
      int ncomp = data->GetNumberOfComponents();
      if (ncomp % 2 == 0)
      {
        int partiallyTranslucent = false;
        unsigned char* ptr = data->GetPointer(0);
        for (vtkIdType i = 0; i < data->GetNumberOfTuples(); i++)
        {
          unsigned char alpha = ptr[i * ncomp + ncomp - 1];
          // the texture is not translucent if it is either
          // fully transparent (alpha=0) or fully opaque (alpha = 255)
          if (alpha != 0 && alpha != 255)
          {
            partiallyTranslucent = true;
            break;
          }
        }
        if (!partiallyTranslucent)
        {
          this->CachedIsTextureSemiTranslucent = 0;
          return 0;
        }
        else
        {
          return 1;
        }
      }
      else
      {
        return 1;
      }
    }
    else // if(data)
    {
      this->CachedIsTextureSemiTranslucent = -1;
      return -1; // undetermined
    }
  }
  else
  {
    vtkScalarsToColors* lut = tex->GetLookupTable();
    if (lut && lut->IsOpaque())
    {
      this->CachedIsTextureSemiTranslucent = 0;
      return 0;
    }
    else
    {
      this->CachedIsTextureSemiTranslucent = 1;
      return 1;
    }
  }
  /*
    this->CachedIsTextureSemiTranslucent = -1;
    return -1;
  */
}

int vtkDepthSortPainter::IsColorSemiTranslucent(vtkUnsignedCharArray* color)
{
  if (color == this->CachedColors && color->GetMTime() < this->CachedIsColorSemiTranslucentTime &&
    this->GetMTime() < this->CachedIsColorSemiTranslucentTime)
  {
    return this->CachedIsColorSemiTranslucent;
  }
  this->CachedColors = color;
  this->CachedIsColorSemiTranslucentTime.Modified();
  if (color == NULL)
  {
    this->CachedIsColorSemiTranslucent = -1;
    return -1;
  }

  int ncomp = color->GetNumberOfComponents();
  vtkIdType ntuples = color->GetNumberOfTuples();

  if (ncomp % 2 != 0)
  {
    this->CachedIsColorSemiTranslucent = 0;
    return 0;
  }
  vtkIdType i;
  unsigned char* values = color->GetPointer(0);
  for (i = 0; i < ntuples; i++)
  {
    if (values[ncomp - 1] != 0 || values[ncomp - 1] != 255)
    {
      this->CachedIsColorSemiTranslucent = 1;
      return 1;
    }
    values += ncomp;
  }
  this->CachedIsColorSemiTranslucent = 0;
  return 0;
}

//-----------------------------------------------------------------------------
vtkDataObject* vtkDepthSortPainter::GetOutput()
{
  return vtkDataObject::SafeDownCast(this->OutputData);
}
