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

#include "smtk/session/aeva/operators/Delete.h"

#include "smtk/session/aeva/Delete_xml.h"
#include "smtk/session/aeva/Resource.h"
#include "smtk/session/aeva/Session.h"

#include "smtk/model/EntityIterator.h"
#include "smtk/model/Face.h"
#include "smtk/model/Group.h"
#include "smtk/model/IntegerData.h"
#include "smtk/model/Model.h"
#include "smtk/model/Volume.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ReferenceItem.h"
#include "smtk/attribute/Resource.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/VoidItem.h"

#include "smtk/operation/MarkGeometry.h"

#include "smtk/resource/Manager.h"

#include "smtk/common/Paths.h"

#include "itkImage.h"
#include "itkImageFileReader.h"
#include "itkImageToVTKImageFilter.h"

#include "vtkDataArray.h"
#include "vtkFieldData.h"
#include "vtkGeometryFilter.h"
#include "vtkImageData.h"
#include "vtkInformation.h"
#include "vtkIntArray.h"
#include "vtkLookupTable.h"
#include "vtkMultiBlockDataSet.h"
#include "vtkMultiThreshold.h"
#include "vtkPointData.h"
#include "vtkPolyData.h"
#include "vtkSmartPointer.h"
#include "vtkStringArray.h"
#include "vtkUnstructuredGrid.h"
#include "vtkXMLImageDataReader.h"
#include "vtkXMLPImageDataReader.h"

#include <sstream>

// On Windows MSVC 2015+, something is included that defines
// a macro named ERROR to be 0. This causes smtkErrorMacro()
// to expand into garbage (because smtk::io::Logger::ERROR
// gets expanded to smtk::io::Logger::0).
#ifdef ERROR
#undef ERROR
#endif

using CellEntity = smtk::model::CellEntity;
using CellEntities = smtk::model::CellEntities;
using Entity = smtk::model::Entity;

namespace smtk
{
namespace session
{
namespace aeva
{

namespace
{

void InsertDependencies(Entity* ent, std::set<Entity*>& deps)
{
  if (!ent)
  {
    return;
  }
  auto resource = std::dynamic_pointer_cast<Resource>(ent->resource());
  if (resource)
  {
    auto session = resource->session();
    if (session->isPrimary(*ent))
    {
      session->sideSets(ent, deps);
    }
    if (ent->isModel())
    {
      smtk::model::EntityIterator it;
      it.traverse(smtk::model::Model(resource, ent->id()), smtk::model::ITERATE_MODELS);
      for (it.begin(); !it.isAtEnd(); ++it)
      {
        if (it.current().isSessionRef() || it.current().isModel())
        {
          continue;
        }
        deps.insert(it.current().entityRecord().get());
      }
    }
  }
  // TODO: Handle groups
  /*
  auto isReference = ent->properties().contains<std::vector<smtk::model::Integer> >("group") &&
    ent->properties().at<std::vector<smtk::model::Integer> >("group")[0];
    */
}

}

Delete::Delete()
  : m_suppressOutput(false)
{
}

bool Delete::ableToOperate()
{
  if (!this->Superclass::ableToOperate())
  {
    return false;
  }

  auto deleteDeps = this->parameters()->findVoid("delete dependents")->isEnabled();
  if (deleteDeps)
  {
    return true;
  }
  // Find dependencies of associated items
  auto assocs = this->parameters()->associations();
  std::set<Entity*> dels;
  std::set<Entity*> deps;
  for (const auto& assoc : *assocs)
  {
    auto* ent = dynamic_cast<Entity*>(assoc.get());
    if (ent)
    {
      dels.insert(ent);
      InsertDependencies(ent, deps);
    }
  }
  // Subtract dels from deps, in case the user also manually
  // added dependencies to the request.
  for (const auto& del : dels)
  {
    deps.erase(del);
  }
  return deps.empty();
}

Delete::Result Delete::operateInternal()
{
  smtk::session::aeva::Resource::Ptr resource = nullptr;
  smtk::session::aeva::Session::Ptr session = nullptr;
  m_result = this->createResult(Delete::Outcome::FAILED);
  this->prepareResourceAndSession(m_result, resource, session);

  auto assocs = this->parameters()->associations();
  auto deleteDeps = this->parameters()->findVoid("delete dependents")->isEnabled();

  // Turn requested deltions into a set.
  // Find dependencies of associated items
  std::set<Entity*> dels;
  std::set<Entity*> deps;
  for (const auto& assoc : *assocs)
  {
    auto* ent = dynamic_cast<Entity*>(assoc.get());
    if (ent)
    {
      dels.insert(ent);
      InsertDependencies(ent, deps);
    }
  }
  // Subtract dels from deps, in case the user also manually
  // added dependencies to the request.
  for (const auto& del : dels)
  {
    deps.erase(del);
  }
  // Either error or not, based on deleteDeps.
  if (!deps.empty() && !deleteDeps)
  {
    smtkErrorMacro(this->log(), "Cannot delete due to " << deps.size() << " dependent entities.");
    return m_result;
  }
  if (deleteDeps)
  {
    dels.insert(deps.begin(), deps.end());
  }

  auto expunged = m_result->findComponent("expunged");
  smtk::operation::MarkGeometry geomMarker(resource);
  for (const auto& del : dels)
  {
    auto sdel = del->shared_from_this();
    expunged->appendValue(sdel);
    geomMarker.erase(del->id());
    resource->erase(del->id());
  }

  m_result->findInt("outcome")->setValue(static_cast<int>(Delete::Outcome::SUCCEEDED));
  return m_result;
}

void Delete::generateSummary(smtk::operation::Operation::Result& result)
{
  if (!m_suppressOutput)
  {
    this->Superclass::generateSummary(result);
  }
}

const char* Delete::xmlDescription() const
{
  return Delete_xml;
}

}
}
}
