//=========================================================================
//  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 "vtkCMBGeometry2DReader.h"

#include "vtkAppendPolyData.h"
#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCleanPolyData.h"
#include "vtkErrorCode.h"
#include "vtkGDALVectorReader.h"
#include "vtkIdTypeArray.h"
#include "vtkInformation.h"
#include "vtkInformationVector.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkNew.h"
#include "vtkObjectFactory.h"
#include "vtkPassArrays.h"
#include "vtkPolyData.h"
#include "vtkPolyDataNormals.h"
#include "vtkStringArray.h"
#include "vtkTable.h"

#include "smtk/extension/vtk/meshing/vtkDiscoverRegions.h"
#include "smtk/extension/vtk/meshing/vtkRegionsToLoops.h"
#include "smtk/extension/vtk/meshing/vtkSplitPlanarLines.h"
#include "vtkTableWriter.h"
#include "vtkXMLPolyDataWriter.h"

#include <vtksys/SystemTools.hxx>

#include <sys/stat.h>
#include <sys/types.h>

vtkStandardNewMacro(vtkCMBGeometry2DReader);

vtkCMBGeometry2DReader::vtkCMBGeometry2DReader()
{
  this->FileName = NULL;
  this->SetNumberOfInputPorts(0);
  for (int i = 0; i < 4; ++i)
  {
    this->RelativeMargin[i] = 5.; // in percent
    this->AbsoluteMargin[i] = 1.;
    this->AbsoluteBounds[i] = i % 2 ? -1. : +1.;
  }
  this->BoundaryFile = NULL;
  this->BoundaryStyle = NONE;
}

vtkCMBGeometry2DReader::~vtkCMBGeometry2DReader()
{
  this->SetFileName(0);
  this->SetBoundaryFile(NULL);
}

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

  os << indent << "File Name: " << (this->FileName ? this->FileName : "(none)") << "\n";
  os << indent << "BoundaryStyle: " << this->BoundaryStyle << "\n";
}

void vtkCMBGeometry2DReader::SetRelativeMarginString(const char* text)
{
  double vals[4];
  if (this->GetMarginFromString(text, vals))
  {
    this->SetRelativeMargin(vals);
  }
}

void vtkCMBGeometry2DReader::SetAbsoluteMarginString(const char* text)
{
  double vals[4];
  if (this->GetMarginFromString(text, vals))
  {
    this->SetAbsoluteMargin(vals);
  }
}

void vtkCMBGeometry2DReader::SetAbsoluteBoundsString(const char* text)
{
  double vals[4];
  int numVals = this->GetMarginFromString(text, vals);
  if (numVals != 4)
  { // Assume we should reset the bounds to be invalid
    vals[0] = vals[2] = +1.;
    vals[1] = vals[3] = -1.;
  }
  this->SetAbsoluteBounds(vals);
}

int vtkCMBGeometry2DReader::GetMarginFromString(const char* text, double vals[4])
{
  int numVals = sscanf(text, "%lf, %lf, %lf, %lf", vals, vals + 1, vals + 2, vals + 3);
  switch (numVals)
  {
    case 1:
      vals[1] = vals[2] = vals[3] = vals[0];
      break;
    case 2:
      vals[3] = vals[2] = vals[1];
      vals[1] = vals[0];
      break;
    case 3:
      vals[3] = vals[2];
      break;
    case 4:
      break;
    default:
    {
      vtkErrorMacro(<< "Unable to parse string into 1 or more comma-separated values: \"" << text
                    << "\"");
    }
  }
  return numVals;
}

int vtkCMBGeometry2DReader::RequestInformation(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* vtkNotUsed(outputVector))
{
  if (!this->FileName)
  {
    vtkErrorMacro("FileName has to be specified!");
    return 0;
  }

  return 1;
}

int vtkCMBGeometry2DReader::RequestData(vtkInformation* vtkNotUsed(request),
  vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector)
{
  vtkInformation* outInfo = outputVector->GetInformationObject(0);
  vtkPolyData* output = vtkPolyData::SafeDownCast(outInfo->Get(vtkDataObject::DATA_OBJECT()));
  std::string fileNameStr = this->FileName;
  vtksys::SystemTools::ConvertToUnixSlashes(fileNameStr);
  std::string fullName = vtksys::SystemTools::CollapseFullPath(fileNameStr.c_str());
  struct stat fs;
  if (stat(fileNameStr.c_str(), &fs) != 0)
  {
    vtkErrorMacro(<< "Unable to open file: " << fileNameStr);
    this->SetErrorCode(vtkErrorCode::CannotOpenFileError);
    return 0;
  }
  if (fileNameStr.find(".shp") != std::string::npos)
  {
    vtkNew<vtkGDALVectorReader> rdr;
    vtkNew<vtkCleanPolyData> cln;
    vtkNew<vtkAppendPolyData> app;
    vtkNew<vtkSplitPlanarLines> slf;
    vtkNew<vtkDiscoverRegions> drg;
    vtkNew<vtkRegionsToLoops> rtl;
    vtkNew<vtkPolyDataNormals> pdn;
    vtkNew<vtkXMLPolyDataWriter> wri;
    vtkNew<vtkTableWriter> twr;

    rdr->SetFileName(fileNameStr.c_str());
    //rdr->AddFeatureIdsOn();
    rdr->SetAppendFeatures(1);
    rdr->Update();

    cln->SetInputConnection(rdr->GetOutputPort());
    cln->Update();
    vtkMultiBlockDataSet* mbds = vtkMultiBlockDataSet::SafeDownCast(cln->GetOutputDataObject(0));
    // Now we do some manual processing:
    // (a) create a polydata for a bounding box
    // (b) add a pedigree ID indicating the original ID of the geometry.
    vtkPolyData* rdp = mbds ? vtkPolyData::SafeDownCast(mbds->GetBlock(0)) : NULL;

    // Remove the "label" attribute if it exists... string arrays
    // can cause problems.
    vtkNew<vtkPassArrays> psa;
    psa->SetInputDataObject(rdp);
    psa->RemoveArraysOn();
    psa->UseFieldTypesOn();
    psa->AddFieldType(vtkDataObject::CELL);
    psa->AddCellDataArray("label");
    psa->Update();

    if (this->BoundaryStyle != NONE)
    {
      // ctp will hold a clipping polygon either created manually
      // as a bounding rectangle or read from a separate shapefile.
      vtkNew<vtkPolyData> ctp;
      vtkNew<vtkPoints> ctpPts;
      vtkNew<vtkCellArray> ctpLin;
      // Add pedigree IDs to the data we are about to append
      vtkNew<vtkIdTypeArray> cid;
      cid->SetName("_vtkPedigreeIds");
      if (this->BoundaryStyle == IMPORTED_POLYGON)
      {
        if (!this->BoundaryFile || !this->BoundaryFile[0])
        {
          vtkErrorMacro(<< "Must have a valid boundary filename to import.\n");
          return 0;
        }
        vtkNew<vtkGDALVectorReader> rbd;
        vtkNew<vtkCleanPolyData> cpd;
        rbd->SetFileName(this->BoundaryFile);
        //rbd->AddFeatureIdsOn();
        rbd->SetAppendFeatures(1);
        rbd->Update();

        cpd->SetInputConnection(rbd->GetOutputPort());
        cpd->Update();
        mbds = vtkMultiBlockDataSet::SafeDownCast(cpd->GetOutputDataObject(0));
        // Now we do some manual processing:
        // (a) create a polydata for a bounding box
        // (b) add a pedigree ID indicating the original ID of the geometry.
        vtkPolyData* rdp2 = mbds ? vtkPolyData::SafeDownCast(mbds->GetBlock(0)) : NULL;

        // Remove the "label" attribute if it exists... string arrays
        // can cause problems.
        vtkNew<vtkPassArrays> ps2;
        ps2->SetInputDataObject(rdp2);
        ps2->RemoveArraysOn();
        ps2->UseFieldTypesOn();
        ps2->AddFieldType(vtkDataObject::CELL);
        ps2->AddCellDataArray("label");
        ps2->Update();

        ctp->ShallowCopy(ps2->GetOutputDataObject(0));
        cid->SetNumberOfTuples(ctp->GetNumberOfCells());
        cid->FillComponent(0, -1.);
      }
      else // generate a rectangle, somehow
      {
        cid->SetNumberOfTuples(1);
        cid->SetValue(0, -1);
        double bds[6];
        double margin[4];
        if (this->BoundaryStyle == RELATIVE_MARGIN)
        {
          this->GetRelativeMargin(margin);
          // convert from percent to world coordinates:
          double dataLength = rdp->GetLength();
          for (int i = 0; i < 4; ++i)
          {
            margin[i] *= dataLength / 100.;
          }
        }
        else if (this->BoundaryStyle == ABSOLUTE_MARGIN)
        {
          this->GetAbsoluteMargin(margin);
        }
        else // (this->BoundaryStyle == ABSOLUTE_BOUNDS)
        {
          this->GetAbsoluteBounds(margin);
          for (int i = 0; i < 4; ++i)
          {
            bds[i] = margin[i];
            margin[i] = 0.;
          }
        }
        rdp->GetBounds(bds);
        ctp->SetPoints(ctpPts.GetPointer());
        ctp->SetLines(ctpLin.GetPointer());
        ctpPts->SetNumberOfPoints(4);
        ctpPts->SetPoint(0, bds[0] - margin[0], bds[2] - margin[2], bds[4]);
        ctpPts->SetPoint(1, bds[1] + margin[1], bds[2] - margin[2], bds[4]);
        ctpPts->SetPoint(2, bds[1] + margin[1], bds[3] + margin[3], bds[4]);
        ctpPts->SetPoint(3, bds[0] - margin[0], bds[3] + margin[3], bds[4]);
        vtkIdType rectConn[] = { 0, 1, 2, 3, 0 };
        ctp->InsertNextCell(VTK_POLY_LINE, sizeof(rectConn) / sizeof(rectConn[0]), rectConn);
      }
      ctp->GetCellData()->SetPedigreeIds(cid.GetPointer());
      if (!rdp->GetCellData()->GetPedigreeIds())
      {
        vtkNew<vtkIdTypeArray> rid;
        rid->SetName("_vtkPedigreeIds");
        vtkIdType ncell = rdp->GetNumberOfCells();
        rid->SetNumberOfTuples(ncell);
        for (vtkIdType i = 0; i < ncell; ++i)
        {
          rid->SetValue(i, i);
        }
        rdp->GetCellData()->SetPedigreeIds(rid.GetPointer());
      }

      app->AddInputDataObject(rdp);
      app->AddInputDataObject(ctp.GetPointer());

      slf->SetInputConnection(app->GetOutputPort());
    }
    else
    {
      slf->SetInputConnection(psa->GetOutputPort());
    }
    slf->Update();

    drg->SetInputConnection(slf->GetOutputPort());
    drg->GenerateRegionInteriorPointsOn();
    drg->Update();

    rtl->SetInputConnection(drg->GetOutputPort());
    rtl->SetInputConnection(1, drg->GetOutputPort(1));
    rtl->SetInputConnection(2, drg->GetOutputPort(2));
    rtl->Update();

    pdn->SetInputConnection(rtl->GetOutputPort());
    pdn->NonManifoldTraversalOn();
    pdn->ConsistencyOn();
    pdn->SplittingOff();
    pdn->ComputePointNormalsOff();
    pdn->ComputeCellNormalsOn();
    pdn->Update();

    output->ShallowCopy(pdn->GetOutput());
  }
  else // unrecognized file type
  {
    vtkErrorMacro(<< "Unrecognized file type: " << fileNameStr);
    this->SetErrorCode(vtkErrorCode::UnrecognizedFileTypeError);
    return 0;
  }

  // Append File name to output
  vtkNew<vtkStringArray> filenameFD;
  filenameFD->SetName("FileName");
  filenameFD->InsertNextValue(fullName);
  output->GetFieldData()->AddArray(filenameFD.GetPointer());
  return 1;
}
