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

#include "smtk/session/rgg/Material.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/model/Model.h"

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

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

namespace smtk
{
namespace session
{
namespace rgg
{

EditMaterial::Result EditMaterial::operateInternal()
{
  // Access the associated model
  EntityRefArray entities = this->parameters()->associatedModelEntities<EntityRefArray>();
  if (entities.empty() || !entities[0].isModel())
  {
    smtkErrorMacro(this->log(), "An invalid model is provided for Edit Material op");
    return this->createResult(smtk::operation::Operation::Outcome::FAILED);
  }
  smtk::model::EntityRef model = entities[0];

  // Access the material label (a short form of the name; mostly unused).
  std::string label = this->parameters()->findAs<attribute::StringItem>("label")->value();

  // Access the material color.
  smtk::attribute::DoubleItemPtr colorI =
    this->parameters()->findAs<attribute::DoubleItem>("color");
  smtk::model::FloatList color = smtk::model::FloatList(colorI->begin(), colorI->end());

  // Construct an instance of our material class, which converts to/from a
  // string representation.
  Material material;

  // Access the material name.
  material.setName(this->parameters()->findAs<attribute::StringItem>("name")->value());

  // Access the material temperature in Kelvin.
  material.setTemperature(
    this->parameters()->findAs<attribute::DoubleItem>("temperature")->value());

  // Access the material thermal expansion coefficient (set to 0 by default).
  material.setThermalExpansion(
    this->parameters()->findAs<attribute::DoubleItem>("thermalExpansion")->value());

  // Access the material density. This value is qualified by density type
  // (below).
  material.setDensity(this->parameters()->findAs<attribute::DoubleItem>("density")->value());

  // Access the density type (atoms/barn-cm or g/cm^3).
  material.setDensityType(
    this->parameters()->findAs<attribute::StringItem>("densityType")->value());

  // Access the material composition type (weight fractions, atom fractions,
  // atom densities or weight densities).
  material.setCompositionType(
    this->parameters()->findAs<attribute::StringItem>("compositionType")->value());

  // Each component has a name and an associated content value (weight/atom
  // fraction/density).
  smtk::attribute::StringItemPtr componentsI =
    this->parameters()->findAs<attribute::StringItem>("component");
  for (auto comp = componentsI->begin(); comp != componentsI->end(); ++comp)
  {
    material.addComponent(*comp);
  }

  smtk::attribute::DoubleItemPtr contentI =
    this->parameters()->findAs<attribute::DoubleItem>("content");
  for (auto content = contentI->begin(); content != contentI->end(); ++content)
  {
    material.addContent(*content);
  }

  // The material name is used as the lookup index for both the label and
  // color.
  model.setStringProperty(material.name(), label);
  model.setFloatProperty(material.name(), color);

  // The name is stored using the label "materials". If it does not yet exist,
  // we create it and populate it with a default "No Cell Material" material.
  if (!model.hasStringProperty("materials"))
  {
    std::string nm = "No Cell Material";
    model.setStringProperty(nm, nm);
    model.setFloatProperty(nm, { 1, 1, 1, 1 });

    std::vector<std::string> materialVec = { nm, material.name() };
    model.setStringProperty("materials", materialVec);
  }
  else
  {
    model.stringProperty("material").push_back(material.name());
  }

  // To avoid collision with the preexisting mechanisms for material
  // description, we construct a new string property with the label
  // "material_descriptions" that holds the SON descriptions of all of our
  // material.
  if (!model.hasStringProperty(Material::label))
  {
    // If the property does not yet exist, create it and seed it with the
    // current material description.
    model.setStringProperty(Material::label, std::vector<std::string>(1, material));
  }
  else
  {
    // If the property does exist, we search for a description of the current
    // material. If one already exists, we overwrite it with our new
    // description. Otherwise, we append our new material description to the
    // property list.
    StringList& materialDescriptions = model.stringProperty(Material::label);
    bool found = false;
    std::stringstream ss;
    ss << "material ( " << material.name() << " )";
    for (std::size_t i = 0; i < materialDescriptions.size(); i++)
    {
      if (materialDescriptions[i].find(ss.str()) != std::string::npos)
      {
        found = true;
        materialDescriptions[i] = material;
      }
    }
    if (!found)
    {
      model.stringProperty(Material::label).push_back(material);
    }
  }

  auto result = this->createResult(smtk::operation::Operation::Outcome::SUCCEEDED);
  result->findComponent("modified")->appendValue(model.component());
  return result;
}

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