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

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

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

#include "smtk/io/Logger.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/PublicPointerDefs.h"
#include "smtk/model/AuxiliaryGeometry.h"
#include "smtk/model/Group.h"
#include "smtk/model/Instance.h"
#include "smtk/model/Model.h"
#include "smtk/model/Resource.h"
#include "smtk/model/Resource.txx"

#include "smtk/model/operators/CreateInstances.h"
#include "smtk/session/rgg/operators/OperationHelper.h"

#include "smtk/session/rgg/operators/EditAssembly_xml.h"

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

namespace smtk
{
namespace session
{
namespace rgg
{
EditAssembly::Result EditAssembly::operateInternal()
{

  EntityRefArray entities = this->parameters()->associatedModelEntities<EntityRefArray>();
  if (entities.empty() || (!entities[0].isGroup() && !entities[0].isModel()))
  {
    smtkErrorMacro(this->log(), "Cannot edit a non group type assembly nor model");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }

  // Get assy representation
  std::string assyRepStr;
  smtk::attribute::StringItemPtr assyRepItem = this->parameters()->
      findString("assembly representation");

  if (assyRepItem != nullptr && !assyRepItem->value(0).empty())
  {
    assyRepStr = assyRepItem->value(0);
  }
  Assembly assy = json::parse(assyRepStr);

  Result result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);

  smtk::attribute::ComponentItem::Ptr createdItem = result->findComponent("created");
  smtk::attribute::ComponentItem::Ptr modifiedItem = result->findComponent("modified");
  smtk::attribute::ComponentItem::Ptr tessChangedItem = result->findComponent("tess_changed");
  smtk::attribute::ComponentItem::Ptr expungedItem = result->findComponent("expunged");

  smtk::model::Group assyGroup;
  if (entities[0].isModel())
  {
    EntityRef parent = entities[0];
    assyGroup = parent.resource()->addGroup(0, "group");
    smtk::model::Model model = entities[0].as<smtk::model::Model>();
    model.addGroup(assyGroup);
    BitFlags mask(0);
    mask |= smtk::model::AUX_GEOM_ENTITY;
    mask |= smtk::model::INSTANCE_ENTITY;
    assyGroup.setMembershipMask(mask);

    assyGroup.setName(assy.name());
    assyGroup.setStringProperty("label", assy.label());
    assyGroup.setStringProperty("rggType", Assembly::typeDescription);
    assyGroup.setStringProperty("selectable", Assembly::typeDescription);
    assyGroup.setColor(assy.color());
    assyGroup.setStringProperty(Assembly::propDescription, assyRepStr);

    modifiedItem->appendValue(parent.component(), false);
  }
  else
  {
    assyGroup = entities[0].as<smtk::model::Group>();

    assyGroup.setName(assy.name());
    assyGroup.setStringProperty("label", assy.label());
    assyGroup.setColor(assy.color());
    assyGroup.setStringProperty(Assembly::propDescription, assyRepStr);
  }
  auto resource = entities[0].resource();

  smtk::model::EntityRefArray expunged, modified, tobeDeleted;

  // Remove all current entities in the group
  auto groupMembers = assyGroup.members<smtk::model::EntityRefArray>();
  tobeDeleted.insert(tobeDeleted.begin(), groupMembers.begin(), groupMembers.end());
  for (auto& e : tobeDeleted)
  {
    expungedItem->appendValue(e.component());
  }

  EntityRef duct(resource, assy.associatedDuct());
  Assembly::UuidToCoordinates ductToCoords;
  ductToCoords[assy.associatedDuct()] = {{0.0, 0.0, 0.0}};
  OperationHelper::glyphDucts(resource,
                              createdItem,
                              modifiedItem, tessChangedItem,
                              assyGroup, ductToCoords, assy.label());


  const auto& entityToCoordinates = assy.entityToCoordinates();
  OperationHelper::glyphPins(resource,
                              createdItem,
                              modifiedItem, tessChangedItem,
                              assyGroup, entityToCoordinates, assy.label());
  if (entities[0].isModel())
  {
    createdItem->appendValue(assyGroup.component());
  }
  else
  {
    modifiedItem->appendValue(assyGroup.component(), false);
  }

  // Delete child aux geoms after being been appended to expunged item since once they
  // are deleted, the component of each aux geom would become invalid.
  resource->deleteEntities(tobeDeleted, modified, expunged, this->m_debugLevel > 0);
  return result;
}

const char* EditAssembly::xmlDescription() const
{
  return EditAssembly_xml;
}

} // namespace rgg
} //namespace session
} // namespace smtk
