//=============================================================================
// 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/rgg/operators/EditDuct.h"

#include "smtk/session/rgg/Resource.h"
#include "smtk/session/rgg/Session.h"

#include "smtk/io/Logger.h"

#include "smtk/PublicPointerDefs.h"
#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/GroupItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"
#include "smtk/model/AuxiliaryGeometry.h"
#include "smtk/model/Resource.h"
#include "smtk/model/Resource.txx"

#include "smtk/session/rgg/operators/CreateModel.h"
#include "smtk/session/rgg/operators/OperationHelper.h"

#include "smtk/session/rgg/json/jsonDuct.h"

#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/Duct.h"

#include "smtk/session/rgg/EditDuct_xml.h"

#include <string> // std::to_string
using namespace smtk::model;
using json = nlohmann::json;

namespace smtk
{
namespace session
{
namespace rgg
{

EditDuct::Result EditDuct::operateInternal()
{
  EntityRefArray entities = this->parameters()->associatedModelEntities<EntityRefArray>();
  if (entities.empty() || (!entities[0].isAuxiliaryGeometry() && !entities[0].isModel()))
  {
    smtkErrorMacro(this->log(), "Cannot edit a non auxiliary geometry entity nor model");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }
  // Get duct representation
  std::string ductRepStr;
  smtk::attribute::StringItemPtr ductRepStrI = this->parameters()->findString("duct representation");
  if (ductRepStrI != nullptr && !ductRepStrI->value(0).empty())
  {
    ductRepStr = ductRepStrI->value(0);
  }
  Duct duct = json::parse(ductRepStr);

  Result result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);
  if (entities[0].isModel())
  {
    EntityRef model = entities[0];

    smtk::model::AuxiliaryGeometry newDuctAux =
        model.resource()->addAuxiliaryGeometry(model.as<smtk::model::Model>(), 3);

    newDuctAux.setName(duct.name());
    newDuctAux.setStringProperty("rggType", smtk::session::rgg::Duct::typeDescription);
    newDuctAux.setStringProperty("selectable", smtk::session::rgg::Duct::typeDescription);
    newDuctAux.setStringProperty(Duct::propDescription, ductRepStr);

    // A list contains all segments and layers of the duct
    std::vector<EntityRef> subAuxGeoms;
    OperationHelper::populateChildrenInDuctAux(newDuctAux, subAuxGeoms, duct);

    // Update the latest duct in the model
    result->findComponent("modified")->appendValue(model.component());
    smtk::attribute::ComponentItem::Ptr createdItem = result->findComponent("created");
    createdItem->appendValue(newDuctAux.component());
    for (auto& c : subAuxGeoms)
    {
      createdItem->appendValue(c.component());
    }
  }
  else
  {
    smtk::model::EntityRefArray expunged, modified, tobeDeleted;

    smtk::model::AuxiliaryGeometry ductAux = entities[0].as<AuxiliaryGeometry>();
    auto resource = std::dynamic_pointer_cast<smtk::session::rgg::Resource>(ductAux.resource());
    // Remove all current child auxiliary geometries first
    EntityRefArray children = ductAux.embeddedEntities<EntityRefArray>();
    tobeDeleted.insert(tobeDeleted.end(), children.begin(), children.end());

    ductAux.setName(duct.name());
    ductAux.setStringProperty(Duct::propDescription, ductRepStr);

    // A list contains all segments and layers of the duct
    std::vector<EntityRef> subAuxGeoms;
    OperationHelper::populateChildrenInDuctAux(ductAux, subAuxGeoms, duct);

    smtk::attribute::ComponentItem::Ptr createdItem = result->findComponent("created");
    smtk::attribute::ComponentItem::Ptr modifiedItem = result->findComponent("modified");
    smtk::attribute::ComponentItem::Ptr expungedItem = result->findComponent("expunged");
    smtk::attribute::ComponentItem::Ptr tessChangedItem = result->findComponent("tess_changed");
    for (auto& c : subAuxGeoms)
    {
      createdItem->appendValue(c.component());
    }

    modifiedItem->appendValue(ductAux.component());

    // Append to be deleted child aux geoms to expunged item first then delete them. Otherwise
    // once they are deleted, the component of each aux geom would become invalid.
    for (auto& e : tobeDeleted)
    {
      expungedItem->appendValue(e.component());
    }

    resource->deleteEntities(tobeDeleted, modified, expunged, this->m_debugLevel > 0);
    for (auto& m : modified)
    {
      modifiedItem->appendValue(m.component());
    }

    // Update all assemblies/core who use this entity
    OperationHelper::updateRelatedAssyAndCore(ductAux, createdItem, modifiedItem,
                                              expungedItem, tessChangedItem,
                                              this->m_debugLevel > 0);
  }

  return result;
}

const char* EditDuct::xmlDescription() const
{
  return EditDuct_xml;
}
} // namespace rgg
} //namespace session
} // namespace smtk
