//=========================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt 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 "vtkMasterPolyDataNormals.h"

#include "vtkObjectFactory.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkNew.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkFloatArray.h"
#include "vtkIntArray.h"
#include "vtkPointData.h"
#include "vtkPolyDataNormals.h"
#include "ModelParserHelper.h"

vtkStandardNewMacro(vtkMasterPolyDataNormals);

namespace

{

struct Storage
{
  Storage(){}
  virtual ~Storage(){}
  virtual int value(int pos)=0;
};

template<typename vtkArrayType>
struct vtkArrayStorage : public Storage
{
  vtkArrayStorage(vtkDataArray* array)
    {
    this->Array = vtkArrayType::SafeDownCast(array);
    }
  virtual ~vtkArrayStorage()
    {
    this->Array = NULL;
    }
  virtual int value(int pos)
    {
    return static_cast<int>(this->Array->GetValue(pos));
    }
private:
  vtkArrayType* Array;
};

template<>
struct vtkArrayStorage<vtkDataArray> : public Storage
{
  vtkArrayStorage(vtkDataArray* array)
    {
    this->Array = array;
    }

  virtual ~vtkArrayStorage()
    {
    this->Array = NULL;
    }
  virtual int value(int pos)
    {
    return static_cast<int>(this->Array->GetTuple1(pos));
    }

private:
  vtkDataArray* Array;
};

struct ArrayWrapper
{
  ArrayWrapper(vtkDataArray* array)
    {
    if(array->GetDataType()==VTK_INT)
      {
      this->ArrayStorage = new vtkArrayStorage<vtkIntArray>(array);
      }
    else if(array->GetDataType()==VTK_ID_TYPE)
      {
      this->ArrayStorage = new vtkArrayStorage<vtkIdTypeArray>(array);
      }
    else
      {
      this->ArrayStorage = new vtkArrayStorage<vtkDataArray>(array);
      }
    }

  ~ArrayWrapper()
    {
    delete this->ArrayStorage;
    }


  int value(int pos)
    {
    return this->ArrayStorage->value(pos);
    }

private:
  Storage* ArrayStorage;
};

//need a way to generalize access to an
//int or id type array

}

//----------------------------------------------------------------------------
vtkMasterPolyDataNormals::vtkMasterPolyDataNormals()
{
}

//----------------------------------------------------------------------------
vtkMasterPolyDataNormals::~vtkMasterPolyDataNormals()
{
}

//----------------------------------------------------------------------------
int vtkMasterPolyDataNormals::RequestData(
  vtkInformation* vtkNotUsed(request),
  vtkInformationVector** inputVector,
  vtkInformationVector* outputVector)
{
  // get the info objects
  vtkInformation *inInfo = inputVector[0]->GetInformationObject(0);
  vtkInformation *outInfo = outputVector->GetInformationObject(0);

  // get the input and ouptut
  vtkPolyData *input = vtkPolyData::SafeDownCast(
    inInfo->Get(vtkDataObject::DATA_OBJECT()));
  vtkPolyData *output = vtkPolyData::SafeDownCast(
    outInfo->Get(vtkDataObject::DATA_OBJECT()));

  if(input->GetNumberOfStrips() > 0)
    {
    vtkErrorMacro("Can't process input with strips! 1st decompose the strips.");
    return 0;
    }

  vtkDataArray *baseRegionIds = input->GetCellData()->GetArray(
                                 ModelParserHelper::GetShellTagName());
  if(!baseRegionIds)
    {
    vtkErrorMacro("Region CellData required!");
    return 0;
    }

  ::ArrayWrapper regionIDs(baseRegionIds);


  // input info
  vtkCellArray *inputPolys = input->GetPolys();
  int numPolys = input->GetNumberOfPolys();

  // setup the output
  output->SetPoints(input->GetPoints());
  output->GetPointData()->ShallowCopy( input->GetPointData() );
  output->GetCellData()->CopyAllocate(input->GetCellData(), input->GetNumberOfPolys());

  // polys for the output
  vtkNew<vtkCellArray> newPolys;
  newPolys->Allocate( inputPolys->GetNumberOfConnectivityEntries() );
  output->SetPolys( newPolys.GetPointer() );

  // normals for the output
  vtkNew<vtkFloatArray> normals;
  normals->SetNumberOfComponents(3);
  normals->SetNumberOfTuples(numPolys);
  normals->SetName("Normals");

  // just so we're aren't doing the BuildCells on the input
  vtkNew<vtkPolyData> workingPD;
  workingPD->ShallowCopy(input);
  // per chance there are any Verts or Lines, want to ignore them
  workingPD->SetVerts(0);
  workingPD->SetLines(0);
  workingPD->BuildCells();

  // keep track of which polys have been processed
  bool *visited = new bool[numPolys];
  memset(visited, false, numPolys*sizeof(bool));
  vtkIdType *cellIndex = new vtkIdType[numPolys];

  vtkIdType polyIndex, npts, *pts;

  // setup temporary working polydata and polys
  vtkNew<vtkPolyData> tmpPolyData;
  tmpPolyData->SetPoints( input->GetPoints() );
  vtkNew<vtkCellArray> tmpPolys;
  tmpPolys->Allocate( inputPolys->GetNumberOfConnectivityEntries() );
  tmpPolyData->SetPolys( tmpPolys.GetPointer() );

  // setup PolyDataNormals filter; assume completely closed and no
  // non-manifold edges;  Do actually expect some open surfaces (the soil), but not
  // with open portion on left side, where direction is determined
  vtkNew<vtkPolyDataNormals> pdNormals;
  pdNormals->ComputePointNormalsOff();
  pdNormals->ComputeCellNormalsOn();
  pdNormals->SplittingOff();
  pdNormals->AutoOrientNormalsOn();
  pdNormals->SetInputData( tmpPolyData.GetPointer() );

  // we stop once every poly has been visited
  vtkIdType masterPolyIndex = -1;
  while (++masterPolyIndex < numPolys)
    {
    if (visited[masterPolyIndex])
      {
      continue;
      }

    int regionId = regionIDs.value(masterPolyIndex);

    tmpPolys->Reset();
    vtkIdType numTmpCells = 0;
    // add every poly with indicated regionID to temporary polyData;
    // don't need to check any before current value of polyIndex
    for (polyIndex = masterPolyIndex; polyIndex < numPolys; polyIndex++)
      {
      // if regionId equal to current cells ID, then haven't "visited" yet
      if (regionIDs.value(polyIndex) == regionId)
        {
        workingPD->GetCellPoints(polyIndex, npts, pts);
        visited[polyIndex] = true;
        tmpPolys->InsertNextCell(npts, pts);
        cellIndex[numTmpCells++] = polyIndex;
        }
      }

    // force execution of normals filter
    pdNormals->Modified();
    pdNormals->Update();

    if (numTmpCells != pdNormals->GetOutput()->GetNumberOfPolys())
      {
      vtkErrorMacro("Lost Cells...");
      return 0;
      }

    // now pull results from filter and add to output polys
    vtkFloatArray *tmpNormals = vtkFloatArray::SafeDownCast(
      pdNormals->GetOutput()->GetCellData()->GetNormals() );
    vtkCellArray *pdNormalOutputPolys = pdNormals->GetOutput()->GetPolys();
    pdNormalOutputPolys->InitTraversal();
    float normal[3];
    for (polyIndex = 0; polyIndex < numTmpCells; polyIndex++)
      {
      pdNormalOutputPolys->GetNextCell(npts, pts);
      vtkIdType cellId = newPolys->InsertNextCell(npts, pts);
      tmpNormals->GetTypedTuple(polyIndex, normal);
      normals->SetTuple(cellId, normal);
      output->GetCellData()->CopyData(input->GetCellData(),
        cellIndex[polyIndex], cellId);
      }
    }

  //cleanup
  delete [] visited;
  delete [] cellIndex;

  output->GetCellData()->SetNormals( normals.GetPointer() );
  return 1;
}

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

