//=========================================================================
//  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.
//=========================================================================

//Explicit include to avoid error
#include "smtk/io/Logger.h"

#include "smtk/model/Edge.h"
#include "smtk/model/EdgeUse.h"
#include "smtk/model/Entity.h"
#include "smtk/model/Face.h"
#include "smtk/model/FaceUse.h"
#include "smtk/model/Loop.h"
#include "smtk/session/aeva/Geometry.h"
#include "smtk/session/aeva/Predicates.h"
#include "smtk/session/aeva/Resource.h"

#include "smtk/geometry/Generator.h"

#include "vtkCellArray.h"
#include "vtkCellData.h"
#include "vtkCompositeDataSet.h"
#include "vtkDataObject.h"
#include "vtkGeometryFilter.h"
#include "vtkIdTypeArray.h"
#include "vtkImageData.h"
#include "vtkIntArray.h"
#include "vtkNew.h"
#include "vtkPointData.h"
#include "vtkPoints.h"
#include "vtkPolyData.h"
#include "vtkSmartPointer.h"
#include "vtkUnstructuredGrid.h"

namespace smtk
{
namespace session
{
namespace aeva
{

constexpr const char* const Geometry::type_name;

Geometry::Geometry(const std::shared_ptr<smtk::session::aeva::Resource>& parent)
  : m_parent(parent)
{
}

smtk::geometry::Resource::Ptr Geometry::resource() const
{
  return std::dynamic_pointer_cast<smtk::geometry::Resource>(m_parent.lock());
}

void Geometry::queryGeometry(const smtk::resource::PersistentObject::Ptr& obj,
  CacheEntry& entry) const
{
  auto ent = std::dynamic_pointer_cast<smtk::model::Entity>(obj);
  if (!ent)
  {
    entry.m_generation = Invalid;
    return;
  }
  auto resource = m_parent.lock();
  auto session = resource ? resource->session() : nullptr;
  vtkSmartPointer<vtkDataObject> data = session ? session->findStorage(obj->id()) : nullptr;
  if (data)
  {
    if (vtkPolyData::SafeDownCast(data) || vtkImageData::SafeDownCast(data))
    {
      entry.m_geometry = data;
    }
    else if (vtkUnstructuredGrid::SafeDownCast(data))
    {
      if (EstimateParametricDimension(data) < 3)
      {
        vtkNew<vtkGeometryFilter> bdy;
        bdy->MergingOff();
        bdy->SetInputDataObject(0, data);
        // Copy global cell IDs so selection works.
        // Unfortunately, this creates a vtkIdTypeArray with a different name
        // and a different range. Keep the passed global IDs if they exist and
        // use the new array only as a fallback.
        bdy->PassThroughCellIdsOn();
        bdy->PassThroughPointIdsOn();
        bdy->Update();
        vtkPolyData* poly = bdy->GetOutput();
        auto* stupidCellIds =
          vtkIdTypeArray::SafeDownCast(poly->GetCellData()->GetArray("vtkOriginalCellIds"));
        auto* stupidPointIds =
          vtkIdTypeArray::SafeDownCast(poly->GetPointData()->GetArray("vtkOriginalPointIds"));
        auto* inputCellIds = data->GetAttributes(vtkDataObject::CELL)->GetGlobalIds();
        auto* inputPointIds = data->GetAttributes(vtkDataObject::POINT)->GetGlobalIds();
        auto* nonstupidCellIds = inputCellIds
          ? vtkIntArray::SafeDownCast(poly->GetCellData()->GetArray(inputCellIds->GetName()))
          : nullptr;
        auto* nonstupidPointIds = inputPointIds
          ? vtkIntArray::SafeDownCast(poly->GetPointData()->GetArray(inputPointIds->GetName()))
          : nullptr;
        if (nonstupidCellIds)
        {
          poly->GetCellData()->SetGlobalIds(nonstupidCellIds);
        }
        else if (stupidCellIds)
        {
          vtkNew<vtkIntArray> properCellIds;
          properCellIds->DeepCopy(stupidCellIds);
          properCellIds->SetName("global ids");
          poly->GetCellData()->RemoveArray(stupidCellIds->GetName());
          poly->GetCellData()->SetGlobalIds(properCellIds);
        }
        if (nonstupidPointIds)
        {
          poly->GetPointData()->SetGlobalIds(nonstupidPointIds);
        }
        else if (stupidPointIds)
        {
          vtkNew<vtkIntArray> properPointIds;
          properPointIds->DeepCopy(stupidPointIds);
          properPointIds->SetName("global ids");
          poly->GetPointData()->RemoveArray(stupidPointIds->GetName());
          poly->GetPointData()->SetGlobalIds(properPointIds);
        }
        entry.m_geometry = poly;
      }
      else
      {
        entry.m_geometry = data;
      }
    }
    else
    {
      smtkWarningMacro(
        smtk::io::Logger::instance(), "Unhandled data type " << data->GetClassName() << ".");
      entry.m_generation = Invalid;
      return;
    }
    ++entry.m_generation;
    return;
  }
  entry.m_generation = Invalid;
}

int Geometry::dimension(const smtk::resource::PersistentObject::Ptr& obj) const
{
  auto ent = std::dynamic_pointer_cast<smtk::model::Entity>(obj);
  if (ent)
  {
    return ent->dimension();
  }
  return 0;
}

Geometry::Purpose Geometry::purpose(const smtk::resource::PersistentObject::Ptr& obj) const
{
  auto ent = std::dynamic_pointer_cast<smtk::model::Entity>(obj);
  if (ent)
  {
    /* FIXME: Use ent to decide whether the entity is a surface or volumetric image.
     * For now, this assumes everything is an image.
     */
    return ent->isVolume() ? Geometry::Label : Geometry::Surface;
  }
  // FIXME: What should the default be?
  return Geometry::Surface;
}

void Geometry::update() const
{
  // Do nothing. AEVA operations set content as needed.
}

void Geometry::geometricBounds(const DataType& geom, BoundingBox& bbox) const
{
  auto* pset = vtkPointSet::SafeDownCast(geom);
  if (pset)
  {
    pset->GetBounds(bbox.data());
    return;
  }
  auto* comp = vtkCompositeDataSet::SafeDownCast(geom);
  if (comp)
  {
    comp->GetBounds(bbox.data());
    return;
  }

  // Invalid bounding box:
  bbox[0] = bbox[2] = bbox[4] = 0.0;
  bbox[1] = bbox[3] = bbox[5] = -1.0;
}

} // namespace aeva
} // namespace session
} // namespace smtk
