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

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

#include "smtk/io/Logger.h"

#include "smtk/attribute/Attribute.h"
#include "smtk/attribute/ComponentItem.h"
#include "smtk/attribute/DoubleItem.h"
#include "smtk/attribute/GroupItem.h"
#include "smtk/attribute/IntItem.h"
#include "smtk/attribute/ModelEntityItem.h"
#include "smtk/attribute/ResourceItem.h"
#include "smtk/attribute/StringItem.h"
#include "smtk/attribute/VoidItem.h"

#include "smtk/model/Group.h"
#include "smtk/model/Model.h"

#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/json/jsonCore.h"

#include "smtk/session/rgg/operators/CreateModel_xml.h"
using namespace smtk::model;
using json = nlohmann::json;

namespace smtk
{
namespace session
{
namespace rgg
{
static unsigned int matToColSize = 22;
static struct
{
  std::string mat;
  std::vector<double> color; // rgba
} matToCol[] = { { "NoCellMaterial", { 1.0, 1.0, 1.0, 1.0 } },
  { "coolant", { 0.3, 0.5, 1.0, .5 } }, { "duct", { 0.3, 0.3, 1.0, .5 } },
  { "follower", { 0.75, 0.2, 0.75, 1.0 } }, { "fuel", { 1.0, 0.1, 0.1, 1.0 } },
  { "gap", { 0.0, 0.0, 0.0, 0.0 } }, { "gasplenum", { 0.3, 1.0, 0.5, 1.0 } },
  { "graphite", { .4, .4, .4, 1.0 } }, { "interassemblygap", { 0.0, 0.0, 0.0, 0.0 } },
  { "metal", { .6, .6, .6, 1.0 } }, { "outerduct", { 0.2, 0.2, 0.2, 1.0 } },
  { "water", { 0.651, 0.741, 0.859, 0.5 } }, { "absorber", { 0.7, 0.2, 0.7, 1.0 } },
  { "activecore", { 1.0, 0.5, 0.3, 1.0 } }, { "cladding", { 0.75, 0.75, 0.75, 1.0 } },
  { "reflector", { 0.5, 0.5, 1.0, 1.0 } }, { "shield", { 0.996, 0.698, 0.298, 1.0 } },
  { "guidetube", { 0.6, 0.6, 0.6, 1.0 } }, { "controlrod", { 0.729, 0.894, 0.702, 1.0 } },
  { "loadpad", { .4, .4, .4, 1.0 } }, { "sodium", { 1.0, 1.0, 0.4, 0.7 } },
  { "restraintring", { .4, .4, .4, 1.0 } } };

CreateModel::Result CreateModel::operateInternal()
{
  Result result;

  // There are two possible create modes
  //
  // 1. Create a model within an existing resource
  // 2. Create a new model, but using the session of an existing resource
  // 3. Import a model into a new resource

  smtk::session::rgg::Resource::Ptr resource = nullptr;
  smtk::session::rgg::Session::Ptr session = nullptr;

  // Modes 2 and 3 require an existing resource for input
  smtk::attribute::ResourceItem::Ptr existingResourceItem =
    this->parameters()->findResource("resource");

  if (existingResourceItem && existingResourceItem->isEnabled())
  {
    smtk::session::rgg::Resource::Ptr existingResource =
      std::static_pointer_cast<smtk::session::rgg::Resource>(existingResourceItem->value());

    session = existingResource->session();

    smtk::attribute::StringItem::Ptr sessionOnlyItem =
      this->parameters()->findString("session only");
    if (sessionOnlyItem->value() == "import into this file")
    {
      // If the "session only" value is set to "this file", then we use the
      // existing resource
      resource = existingResource;
    }
    else
    {
      // If the "session only" value is set to "this session", then we create a
      // new resource with the session from the exisiting resource
      resource = smtk::session::rgg::Resource::create();
      resource->setSession(session);
    }
  }
  else
  {
    // If no existing resource is provided, then we create a new session and
    // resource.
    resource = smtk::session::rgg::Resource::create();
    session = smtk::session::rgg::Session::create();
    resource->setSession(session);
  }

  smtk::model::Model model = resource->addModel(/* par. dim. */ 3, /* emb. dim. */ 3);
  model.setSession(smtk::model::SessionRef(resource, session->sessionId()));
  model.setIntegerProperty(SMTK_GEOM_STYLE_PROP, smtk::model::DISCRETE);
  if (model.name().empty())
  {
    model.assignDefaultName();
  }

  smtk::model::Group coreGroup = resource->addGroup(0, "group"); // Assign the name later
  model.addGroup(coreGroup);
  BitFlags mask(0);
  mask |= smtk::model::AUX_GEOM_ENTITY;
  mask |= smtk::model::INSTANCE_ENTITY;
  coreGroup.setMembershipMask(mask);

  CreateModel::populateCore(this, coreGroup);

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

  {
    smtk::attribute::ResourceItem::Ptr created = result->findResource("resource");
    created->setValue(resource);
  }

  {
    smtk::attribute::ComponentItem::Ptr created = result->findComponent("created");
    created->appendValue(model.component());
    created->appendValue(coreGroup.component());
  }

  if (!result)
  {
    result = this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }
  return result;
}

void CreateModel::populateCore(smtk::operation::Operation* op, smtk::model::Group& coreGroup)
{
  coreGroup.setStringProperty("rggType", smtk::session::rgg::Core::typeDescription);
  coreGroup.setStringProperty("selectable", smtk::session::rgg::Core::typeDescription);
  Core core;
  smtk::attribute::StringItem::Ptr nameItem = op->parameters()->findString("name");
  if (nameItem && nameItem->isEnabled())
  {
    std::string coreName = nameItem->value(0);
    coreGroup.setName(coreName);
    core.setName(coreName);
  }

  smtk::model::Model model = coreGroup.owningModel();

  // Hex or rectinlinear
  std::string optype = op->parameters()->findString("geometry type")->value();
  if (optype == "Hex")
  {
    core.setGeomType(Core::GeomType::Hex);
    smtk::attribute::DoubleItemPtr thicknessI = op->parameters()->findDouble("duct thickness");
    core.setDuctThickness(thicknessI->value(0));
    // Convenience property
    model.setIntegerProperty(Core::geomDescription, static_cast<int>(Core::GeomType::Hex));

    smtk::attribute::IntItemPtr latticeSizeItem = op->parameters()->
        findInt("hex lattice size");
    core.setLatticeSize(latticeSizeItem->value());

  }
  else if (optype == "Rect")
  {
    core.setGeomType(Core::GeomType::Rect);
    smtk::attribute::DoubleItemPtr thicknessXI = op->parameters()->findDouble("duct thickness X");
    smtk::attribute::DoubleItemPtr thicknessYI = op->parameters()->findDouble("duct thickness Y");
    core.setDuctThickness(thicknessXI->value(0), thicknessYI->value(0));
    // Convenience property
    model.setIntegerProperty(Core::geomDescription, static_cast<int>(Core::GeomType::Rect));

    smtk::attribute::IntItemPtr latticeSizeItem = op->parameters()->
        findInt("rect lattice size");
    core.setLatticeSize(latticeSizeItem->value(0), latticeSizeItem->value(1));
  }

  // Common items for hex and rectilinear coreGroup
  smtk::attribute::DoubleItemPtr heightI = op->parameters()->findDouble("height");
  smtk::attribute::DoubleItemPtr zOriginI = op->parameters()->findDouble("z origin");
  if (heightI->numberOfValues() != 1 || zOriginI->numberOfValues() != 1)
  {
    smtkErrorMacro(op->log(), " Fail to set the duct height on the rgg coreGroup");
  }
  else
  {
    core.setZOrigin(zOriginI->value(0));
    core.setHeight(heightI->value(0));
  }

  json coreJson = core;
  std::string coreString = coreJson.dump();
  coreGroup.setStringProperty(Core::propDescription, coreString);
}

size_t CreateModel::materialNum(smtk::model::Model model)
{
  if (model.isValid() && model.hasStringProperty("materials"))
  { // Check if users have define some materials and they are loaded into SMTK
    smtk::model::StringList materialsList = model.stringProperty("materials");
    return materialsList.size();
  }
  return matToColSize;
}

std::string CreateModel::getMaterial(const size_t& index, smtk::model::Model model)
{
  if (model.isValid() && model.hasStringProperty("materials"))
  { // Check if users have define some materials and they are loaded into SMTK
    smtk::model::StringList materialsList = model.stringProperty("materials");
    if (index < materialsList.size())
    {
      return materialsList[index];
    }
  }
  if (index >= matToColSize)
  {
    return std::string();
  }
  return matToCol[index].mat;
}

std::vector<double> CreateModel::getMaterialColor(
  const size_t& index,  smtk::model::Model model)
{
  if (model.isValid() && model.hasStringProperty("materials"))
  { // Check if users have define some materials and they are loaded into SMTK
    smtk::model::StringList materialsList = model.stringProperty("materials");
    if (index < materialsList.size() && model.hasFloatProperty(materialsList[index]) &&
      (model.floatProperty(materialsList[index]).size() == 4))
    {
      return model.floatProperty(materialsList[index]);
    }
  }
  if (index >= matToColSize)
  {
    return {};
  }
  return matToCol[index].color;
}

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