//=========================================================================
//  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/CellSelection.h"
#include "smtk/session/aeva/Predicates.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"
#include "smtk/session/aeva/operators/Delete.h"

#include "smtk/operation/Manager.h"
#include "smtk/operation/MarkGeometry.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/Definition.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ResourceItem.h"

#include "smtk/common/UUIDGenerator.h"

#include "vtk/aeva/ext/vtkAppendFilterIntIds.h"

#include "vtkCompositeDataSet.h"
#include "vtkDataSet.h"
#include "vtkDataSetAttributes.h"
#include "vtkIntArray.h"
#include "vtkThreshold.h"
#include "vtkUnsignedCharArray.h"

#include <array>

namespace smtk
{
namespace session
{
namespace aeva
{

namespace
{

class CellSelectionAlias
{
public:
  CellSelectionAlias(const std::shared_ptr<smtk::resource::Component>& component,
    std::weak_ptr<smtk::operation::Manager> operationManager)
    : m_selection(std::static_pointer_cast<CellSelection>(component->shared_from_this()))
    , m_operationManager(std::move(operationManager))
  {
    // std::cout << "Holding " << m_selection->id() << "\n";
  }
  CellSelectionAlias(const std::shared_ptr<smtk::resource::Component>& component)
    : CellSelectionAlias(component, std::weak_ptr<smtk::operation::Manager>())
  {
  }
  ~CellSelectionAlias()
  {
    // std::cout << "Deleting " << m_selection->id() << "\n";
    Delete::Ptr deleter;
    if (auto operationManager = m_operationManager.lock())
    {
      deleter = operationManager->create<Delete>();
      deleter->parameters()->associate(m_selection);
      deleter->parameters()->findResource("resource")->appendValue(m_selection->resource());
      deleter->setSuppressOutput(true);
      operationManager->launchers()(deleter);
    }
    else
    {
      deleter = Delete::create();
      deleter->parameters()->associate(m_selection);
      deleter->parameters()->findResource("resource")->appendValue(m_selection->resource());
      deleter->setSuppressOutput(true);
      deleter->operate();
    }
  }

  const std::shared_ptr<CellSelection>& selection() const { return m_selection; }

private:
  std::shared_ptr<CellSelection> m_selection;
  std::weak_ptr<smtk::operation::Manager> m_operationManager;
};

}

std::weak_ptr<CellSelection> CellSelection::s_instance;
constexpr const char* const CellSelection::type_name;

CellSelection::CellSelection(const std::shared_ptr<Resource>& parent, vtkDataObject* selectedCells)
{
  m_resource = parent;
  std::array<double, 6> bounds;
  if (auto* dataSet = dynamic_cast<vtkDataSet*>(selectedCells))
  {
    dataSet->GetBounds(bounds.data());
  }
  else if (auto* composite = dynamic_cast<vtkCompositeDataSet*>(selectedCells))
  {
    composite->GetBounds(bounds.data());
  }
  else
  {
    bounds = std::array<double, 6>{ 0, 0, 0, 0, 0, 0 };
  }

  smtk::model::BitFlags dimension = smtk::model::DIMENSION_0;
  int dim = EstimateParametricDimension(selectedCells);
  if (dim < 0)
  {
    for (int dd = 0; dd < 2;
         ++dd) // NB: Not dd < 3 since we don't want surface selns reported as volumes.
    {
      if (bounds[2 * dd + 1] > bounds[2 * dd])
      {
        dimension <<= 1;
      }
    }
  }
  else
  {
    dimension <<= dim;
  }
  m_entityFlags = smtk::model::CELL_ENTITY | dimension;

  if (parent && selectedCells)
  {
    auto session = parent->session();
    session->addStorage(this->id(), selectedCells);
    // We want to do this but can't:
    // smtk::operation::MarkGeometry(parent).markModified(this->shared_from_this());
  }
}

vtkSmartPointer<vtkDataObject> CellSelection::data() const
{
  vtkSmartPointer<vtkDataObject> result;
  auto resource = std::dynamic_pointer_cast<Resource>(m_resource.lock());
  if (!resource)
  {
    return result;
  }
  result = resource->session()->findStorage(this->id());
  return result;
}

void CellSelection::replaceData(vtkDataObject* selectedCells)
{
  auto resource = std::dynamic_pointer_cast<Resource>(m_resource.lock());
  if (!resource)
  {
    return;
  }
  resource->session()->addStorage(this->id(), selectedCells);
  smtk::operation::MarkGeometry(resource).markModified(this->shared_from_this());
}

std::shared_ptr<CellSelection> CellSelection::create(const std::shared_ptr<Resource>& parent,
  vtkDataObject* selectedCells,
  const std::weak_ptr<smtk::operation::Manager>& operationManager)
{
  std::shared_ptr<smtk::resource::Component> tmp(new CellSelection(parent, selectedCells));
  std::shared_ptr<CellSelectionAlias> alias(new CellSelectionAlias(tmp, operationManager));
  std::shared_ptr<smtk::resource::Component> shared(alias, alias->selection().get());

  // NB: Because cellSelection is not added to \a parent, geometry for
  //     the cell selection will only render if the pertinent backend
  //     has already been registered for the resource:
  smtk::operation::MarkGeometry(parent).markModified(tmp);

  auto cellSeln = std::static_pointer_cast<CellSelection>(shared);
  s_instance = cellSeln;

  return cellSeln;
}

std::shared_ptr<CellSelection> CellSelection::create(const std::shared_ptr<Resource>& parent,
  const std::set<vtkIdType>& cellIdsIn,
  const std::weak_ptr<smtk::operation::Manager>& operationManager)
{
  if (!parent)
  {
    return std::shared_ptr<CellSelection>();
  }

  std::set<vtkIdType> cellIds(cellIdsIn);
  const auto& session = parent->session();
  vtkNew<vtkAppendFilterIntIds> append;
  auto it2 = cellIds.begin();
  for (auto it = cellIds.begin(); it != cellIds.end(); it = it2)
  {
    // Grab the proper primary
    smtk::common::UUID uid = session->primaryGeometryForCell(*it);
    ++it2;
    auto primaryData = session->findStorage(uid);
    if (!primaryData)
    {
      cellIds.erase(it);
      continue;
    }
    auto* input = primaryData->NewInstance();
    input->ShallowCopy(primaryData);
    auto* pgids =
      vtkIntArray::SafeDownCast(input->GetAttributes(vtkDataObject::CELL)->GetGlobalIds());
    vtkNew<vtkUnsignedCharArray> select;
    select->SetName("SelectionValue");
    vtkIdType nc = NumberOfCells(input);
    select->SetNumberOfTuples(nc);
    // Mark cells in the primary that were requested.
    // This loop then removes the ID (the global IDs mean
    // there can be only one match) and continues until
    // there are no more cells in the primary.
    for (vtkIdType ii = 0; ii < nc; ++ii)
    {
      vtkIdType pgid = pgids->GetValue(ii);
      if (cellIds.find(pgid) != cellIds.end())
      {
        select->SetValue(ii, 1);
        if (*it2 == pgid)
        {
          ++it2;
        }
        cellIds.erase(pgid);
      }
      else
      {
        select->SetValue(ii, 0);
      }
    }
    // Threshold any marked cells to create a side set.
    input->GetAttributes(vtkDataObject::CELL)->AddArray(select);
    vtkNew<vtkThreshold> threshold;
    threshold->SetInputDataObject(0, input);
    threshold->SetInputArrayToProcess(0, 0, 0, vtkDataObject::CELL, "SelectionValue");
    threshold->SetLowerThreshold(1);
    threshold->SetUpperThreshold(255);
    threshold->SetThresholdFunction(vtkThreshold::THRESHOLD_BETWEEN);
    threshold->Update();
    // Append side set entries from this primary geometry to
    // the output for the whole selection (which may cover multiple
    // primary geometries).
    append->AddInputData(threshold->GetOutputDataObject(0));
  }
  append->Update();
  auto* selectedCells = append->GetOutputDataObject(0);
  if (selectedCells)
  {
    selectedCells->GetAttributes(vtkDataObject::CELL)->RemoveArray("SelectionValue");
  }
  return CellSelection::create(parent, selectedCells, operationManager);
}

}
}
}
