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

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))
    {
      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...
      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"));
      if (stupidCellIds)
      {
        vtkNew<vtkIntArray> properCellIds;
        properCellIds->DeepCopy(stupidCellIds);
        properCellIds->SetName("global ids");
        poly->GetCellData()->RemoveArray(stupidCellIds->GetName());
        poly->GetCellData()->SetGlobalIds(properCellIds);
      }
      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
    {
      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
