#include "vtkCMBTriangleMultiBlockMesher.h"
#include "cmbFaceMesherInterface.h"
#include "vtkMultiBlockDataSet.h"
#include "cmbFaceMeshHelper.h"
#include "vtkCMBMeshServerLauncher.h"
#include "vtkCMBPrepareForTriangleMesher.h"
#include "vtkCell.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkInformation.h"
#include "vtkDataArray.h"
#include "vtkErrorCode.h"
#include "vtkFloatArray.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPolyData.h"
#include "vtkPointData.h"
#include <sstream>
#include <vtksys/SystemTools.hxx>

vtkStandardNewMacro(vtkCMBTriangleMultiBlockMesher);


using namespace CmbFaceMesherClasses;

//--------------------------------------------------------------------
vtkCMBTriangleMultiBlockMesher::vtkCMBTriangleMultiBlockMesher()
{
  MinAngle            = 20.0f;
  UseMinAngle         = false;
  PreserveBoundaries  = true;
  PreserveEdges       = false;
  MaxArea             = 1.0/8.0;
  ComputedMaxArea     = MaxArea;
  UseUniqueAreas      = false;
  MaxAreaMode         = RelativeToBoundsAndSegments;
  VerboseOutput       = false;

  this->SetNumberOfInputPorts(1);
  this->SetNumberOfOutputPorts(1);
}
//--------------------------------------------------------------------
//This multiblockdataset algorithm uses a polydata as input instead of
//multiblockdataset
int vtkCMBTriangleMultiBlockMesher::FillInputPortInformation(int vtkNotUsed(port), vtkInformation* info)
{
  info->Set(vtkAlgorithm::INPUT_REQUIRED_DATA_TYPE(),"vtkPolyData");
  return 1;
}
//--------------------------------------------------------------------
vtkCMBTriangleMultiBlockMesher::~vtkCMBTriangleMultiBlockMesher()
{
}
//--------------------------------------------------------------------
void vtkCMBTriangleMultiBlockMesher::PrintSelf(ostream& os, vtkIndent indent)
{
  const char* areaModeType[4] =
    {
    "NoMaxArea",
    "AbsoluteArea",
    "RelativeToBounds",
    "RelativeToBoundsAndSegments"
    };
  os << indent << this->GetClassName() << endl;
  os << indent << "  Use Minimum Angle: " << UseMinAngle << endl;
  os << indent << "      Minimum Angle: " <<    MinAngle << endl;
  os << indent << "Preserve Boundaries: " << PreserveBoundaries << endl;
  os << indent << "     Preserve Edges: " << PreserveEdges << endl;
  os << indent << "           Max Area: " << MaxArea << endl;
  os << indent << "  Computed Max Area: " << ComputedMaxArea << endl;
  os << indent << "      Max Area Mode: " << areaModeType[MaxAreaMode] << endl;
  os << indent << "   Use Unique Areas: " << UseUniqueAreas << endl;
  os << indent << "     Verbose Output: " << VerboseOutput << endl;
  this->Superclass::PrintSelf(os,indent);
}
//--------------------------------------------------------------------
int vtkCMBTriangleMultiBlockMesher::RequestData(vtkInformation * /*request*/,
    vtkInformationVector **inputVector,
    vtkInformationVector *outputVector)
{
  //input is a vtkPolyData with field data specified by the
  //Map interface. This information is used for meshing
  vtkPolyData* input = vtkPolyData::GetData(inputVector[0]);
  vtkMultiBlockDataSet* output = vtkMultiBlockDataSet::GetData(outputVector);

  std::map<vtkIdType, ModelFaceRep* > pid2Face; // Create a face for every polygon

  // Build the data structures required for meshing
  vtkCMBPrepareForTriangleMesher* mapInterface = vtkCMBPrepareForTriangleMesher::New();
  mapInterface->SetPolyData(input);
  mapInterface->GetPolyId2ModelFaceRepMap(pid2Face);
  mapInterface->Delete();

  // Find global area information
  double* bnds = input->GetBounds();
  double totalPolyDataArea = (bnds[1]-bnds[0]) * (bnds[3]-bnds[2]);

  output->SetNumberOfBlocks(pid2Face.size());
  unsigned blocknum = 0;
  // Mesh each polygon individually then append all their polydata together
  vtkNew<vtkCMBMeshServerLauncher> meshServer;
  std::map<vtkIdType, ModelFaceRep* >::iterator faceIter = pid2Face.begin();
  for(; faceIter != pid2Face.end(); faceIter++)
    {
    vtkPolyData* outputMesh = vtkPolyData::New();
    vtkIdType faceId = (*faceIter).first;
    ModelFaceRep* face = (*faceIter).second;

    // Find local area information
    double currArea = totalPolyDataArea;
    double currNumSeg = input->GetNumberOfLines();

    if (UseUniqueAreas)
      {
      double faceBnds[4];
      face->bounds(faceBnds);
      currArea = (faceBnds[2]-faceBnds[0]) * (faceBnds[3]-faceBnds[1]);
      currNumSeg = face->numberOfEdges();
      }
    switch(MaxAreaMode)
      {
      case NoMaxArea:
        break;
      case AbsoluteArea:
        //If it is absolutly known what the max triangle size should
        //be just set it
        this->ComputedMaxArea = MaxArea;
        break;
      case RelativeToBounds:
        //Use the MaxArea as a ratio if areas are supposed to be
        //calculated relative to bounds
        this->ComputedMaxArea = currArea * MaxArea;
        break;
      case RelativeToBoundsAndSegments:
        // For added fidelity you can incorporate how coarse
        // or complicated a polygon is by dividing area by the
        // number of line segments in the polygon
        this->ComputedMaxArea = currArea / currNumSeg * MaxArea;
        break;
      default:
        vtkErrorMacro("ERROR: Invalid Max Area Mode");
        break;
      }

    cmbFaceMesherInterface ti(face->numberOfVertices(),
                              face->numberOfEdges(),
                              face->numberOfHoles(),
                              this->PreserveEdges);
    ti.setUseMaxArea(this->MaxAreaMode != NoMaxArea);
    ti.setMaxArea(this->ComputedMaxArea);
    ti.setUseMinAngle(this->UseMinAngle);
    ti.setMinAngle(this->MinAngle);
    ti.setPreserveBoundaries(this->PreserveBoundaries);
    ti.setVerboseOutput(this->VerboseOutput);
    ti.setOutputMesh(outputMesh);
    face->fillTriangleInterface(&ti);
    ti.buildFaceMesh(meshServer.GetPointer(),faceId,0);
    output->SetBlock(blocknum++, outputMesh);
    outputMesh->Delete();
    }

  return true;
}
