//=========================================================================
//  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 "smtk/session/aeva/Predicates.h"

#include "smtk/io/Logger.h"

#include "vtkCellData.h"
#include "vtkCellType.h"
#include "vtkCompositeDataIterator.h"
#include "vtkCompositeDataSet.h"
#include "vtkDataArray.h"
#include "vtkDataSet.h"
#include "vtkDataSetAttributes.h"
#include "vtkDataSetSurfaceFilter.h"
#include "vtkIdList.h"
#include "vtkMath.h"
#include "vtkPolyData.h"
#include "vtkPolyDataNormals.h"
#include "vtkUnstructuredGrid.h"

#include <algorithm>

namespace smtk
{
namespace session
{
namespace aeva
{

vtkIdType NumberOfCells(const vtkSmartPointer<vtkDataObject>& data)
{
  auto* dset = vtkDataSet::SafeDownCast(data);
  if (dset)
  {
    return dset->GetNumberOfCells();
  }
  auto* comp = vtkCompositeDataSet::SafeDownCast(data);
  if (comp)
  {
    return comp->GetNumberOfCells();
  }
  return -1;
}

int DimensionFromCellType(int cellType)
{
  switch (cellType)
  {
    case VTK_VERTEX: // fall through
    case VTK_POLY_VERTEX:
      return 0;
    case VTK_LINE:             // fall through
    case VTK_POLY_LINE:        // fall through
    case VTK_QUADRATIC_EDGE:   // fall through
    case VTK_CUBIC_LINE:       // fall through
    case VTK_LAGRANGE_CURVE:   // fall through
    case VTK_PARAMETRIC_CURVE: // fall through
    case VTK_HIGHER_ORDER_EDGE:
      return 1;
    case VTK_TRIANGLE:                // fall through
    case VTK_TRIANGLE_STRIP:          // fall through
    case VTK_POLYGON:                 // fall through
    case VTK_PIXEL:                   // fall through
    case VTK_QUAD:                    // fall through
    case VTK_QUADRATIC_TRIANGLE:      // fall through
    case VTK_QUADRATIC_QUAD:          // fall through
    case VTK_QUADRATIC_POLYGON:       // fall through
    case VTK_BIQUADRATIC_QUAD:        // fall through
    case VTK_QUADRATIC_LINEAR_QUAD:   // fall through
    case VTK_BIQUADRATIC_TRIANGLE:    // fall through
    case VTK_LAGRANGE_TRIANGLE:       // fall through
    case VTK_LAGRANGE_QUADRILATERAL:  // fall through
    case VTK_PARAMETRIC_SURFACE:      // fall through
    case VTK_PARAMETRIC_TRI_SURFACE:  // fall through
    case VTK_PARAMETRIC_QUAD_SURFACE: // fall through
    case VTK_HIGHER_ORDER_TRIANGLE:   // fall through
    case VTK_HIGHER_ORDER_QUAD:       // fall through
    case VTK_HIGHER_ORDER_POLYGON:
      return 2;
    case VTK_TETRA:                            // fall through
    case VTK_VOXEL:                            // fall through
    case VTK_HEXAHEDRON:                       // fall through
    case VTK_WEDGE:                            // fall through
    case VTK_PYRAMID:                          // fall through
    case VTK_PENTAGONAL_PRISM:                 // fall through
    case VTK_HEXAGONAL_PRISM:                  // fall through
    case VTK_QUADRATIC_TETRA:                  // fall through
    case VTK_QUADRATIC_HEXAHEDRON:             // fall through
    case VTK_QUADRATIC_WEDGE:                  // fall through
    case VTK_QUADRATIC_PYRAMID:                // fall through
    case VTK_TRIQUADRATIC_HEXAHEDRON:          // fall through
    case VTK_QUADRATIC_LINEAR_WEDGE:           // fall through
    case VTK_BIQUADRATIC_QUADRATIC_WEDGE:      // fall through
    case VTK_BIQUADRATIC_QUADRATIC_HEXAHEDRON: // fall through
    case VTK_CONVEX_POINT_SET:                 // fall through
    case VTK_POLYHEDRON:                       // fall through
    case VTK_PARAMETRIC_TETRA_REGION:          // fall through
    case VTK_PARAMETRIC_HEX_REGION:            // fall through
    case VTK_HIGHER_ORDER_TETRAHEDRON:         // fall through
    case VTK_HIGHER_ORDER_WEDGE:               // fall through
    case VTK_HIGHER_ORDER_PYRAMID:             // fall through
    case VTK_HIGHER_ORDER_HEXAHEDRON:          // fall through
    case VTK_LAGRANGE_TETRAHEDRON:             // fall through
    case VTK_LAGRANGE_HEXAHEDRON:              // fall through
    case VTK_LAGRANGE_WEDGE:                   // fall through
    case VTK_LAGRANGE_PYRAMID:
      return 3;
    case VTK_EMPTY_CELL: // fall through
    default:
      break;
  }
  return -1;
}

int EstimateParametricDimension(const vtkSmartPointer<vtkDataObject>& data)
{
  int dimension = -1;
  if (!data)
  {
    return dimension;
  }
  auto* dset = vtkDataSet::SafeDownCast(data);
  if (dset)
  {
    vtkIdType nc = dset->GetNumberOfCells();
    auto ns = static_cast<double>(vtkMath::CeilLog2(nc));
    auto skip = static_cast<vtkIdType>(nc < 10000 ? 1 : (nc > 10e7 ? nc / 1.0e5 : nc / ns));
    for (vtkIdType ii = 0; ii < nc; ii += skip)
    {
      dimension = std::max(dimension, DimensionFromCellType(dset->GetCellType(ii)));
    }
    return dimension;
  }
  auto* cdata = vtkCompositeDataSet::SafeDownCast(data);
  if (cdata)
  {
    auto* it = cdata->NewIterator();
    it->SkipEmptyNodesOn();
    for (it->InitTraversal(); !it->IsDoneWithTraversal(); it->GoToNextItem())
    {
      dimension = std::max(dimension, EstimateParametricDimension(it->GetCurrentDataObject()));
    }
    it->Delete();
    return dimension;
  }

  return -1;
}

vtkDataObject::AttributeTypes EntityType(const vtkSmartPointer<vtkDataObject>& data)
{
  switch (EstimateParametricDimension(data))
  {
    case 0:
      return vtkDataObject::POINT;
    case 1:
    case 2:
    case 3:
      return vtkDataObject::CELL;
    default:
      break;
  }
  return vtkDataObject::NUMBER_OF_ATTRIBUTE_TYPES;
}

EdgeNeighborLambda EdgeNeighbors(const vtkSmartPointer<vtkDataObject>& data)
{
  EdgeNeighborLambda lambda = [](vtkIdType cellId, const EdgeNeighborVisitor& visitor) {
    (void)cellId;
    (void)visitor;
  };
  auto* ugrid = vtkUnstructuredGrid::SafeDownCast(data);
  auto* pdata = vtkPolyData::SafeDownCast(data);
  thread_local vtkNew<vtkIdList> neighbors;
  thread_local vtkNew<vtkIdList> pointsOfCell;
  if (ugrid)
  {
    ugrid->BuildLinks(); // Do now as otherwise lambda will not be threadsafe
    thread_local vtkNew<vtkIdList> edgePoints;
    edgePoints->SetNumberOfIds(2);
    lambda = [ugrid](vtkIdType cellId, const EdgeNeighborVisitor& visitor) {
      if (!visitor)
      {
        return;
      }
      if (DimensionFromCellType(ugrid->GetCellType(cellId)) == 2)
      {
        ugrid->GetCellPoints(cellId, pointsOfCell);
        vtkIdType np = pointsOfCell->GetNumberOfIds();
        for (vtkIdType ii = 0; ii < np; ++ii)
        {
          edgePoints->SetId(0, pointsOfCell->GetId(ii));
          edgePoints->SetId(1, pointsOfCell->GetId((ii + 1) % np));
          ugrid->GetCellNeighbors(cellId, edgePoints, neighbors);
          vtkIdType nn = neighbors->GetNumberOfIds();
          for (vtkIdType jj = 0; jj < nn; ++jj)
          {
            if (visitor(neighbors->GetId(jj)) == Break)
            {
              return;
            }
          }
        }
      }
    };
  }
  else if (pdata)
  {
    pdata->BuildLinks(); // Do now as otherwise lambda will not be threadsafe
    lambda = [pdata](vtkIdType cellId, const EdgeNeighborVisitor& visitor) {
      if (!visitor)
      {
        return;
      }
      if (DimensionFromCellType(pdata->GetCellType(cellId)) == 2)
      {
        pdata->GetCellPoints(cellId, pointsOfCell);
        vtkIdType np = pointsOfCell->GetNumberOfIds();
        vtkIdType p0 = pointsOfCell->GetId(np - 1);
        vtkIdType p1;
        for (vtkIdType ii = 0; ii < np; ++ii)
        {
          p1 = pointsOfCell->GetId(ii);
          pdata->GetCellEdgeNeighbors(cellId, p0, p1, neighbors);
          vtkIdType nn = neighbors->GetNumberOfIds();
          for (vtkIdType jj = 0; jj < nn; ++jj)
          {
            if (visitor(neighbors->GetId(jj)) == Break)
            {
              return;
            }
          }
          p0 = p1;
        }
      }
    };
  }
  return lambda;
}

void SurfaceWithNormals(const vtkSmartPointer<vtkDataObject>& data,
  vtkSmartPointer<vtkPolyData>& surf,
  vtkSmartPointer<vtkDataArray>& norm)
{
  surf = vtkPolyData::SafeDownCast(data);
  if (!surf)
  {
    vtkNew<vtkDataSetSurfaceFilter> extractSurface;
    extractSurface->PassThroughCellIdsOn();
    extractSurface->SetInputDataObject(data);
    extractSurface->Update();
    surf = extractSurface->GetOutput();
  }
  if (!surf)
  {
    smtkErrorMacro(smtk::io::Logger::instance(), "Could not generate surface.");
    return;
  }
  if (!surf->GetCellData()->GetNormals())
  {
    vtkNew<vtkPolyDataNormals> genNormals;
    genNormals->SetInputDataObject(surf);
    genNormals->ConsistencyOn();
    genNormals->SplittingOff();
    genNormals->ComputeCellNormalsOn();
    genNormals->Update();
    surf = genNormals->GetOutput();
  }
  norm = surf->GetCellData()->GetNormals();
}

}
}
}
