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

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

#include "smtk/io/Logger.h"

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

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

#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/Resource.h"
#include "smtk/session/rgg/operators/CreateModel.h"

#define PUGIXML_HEADER_ONLY
#include "pugixml/src/pugixml.cpp"

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

#include <fstream>

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

using namespace smtk::model;
using namespace smtk::session::rgg::XMLAttribute;

namespace smtk
{
namespace session
{
namespace rgg
{

bool ReadRXFFile::ableToOperate()
{
  if (!this->parameters()->isValid())
  {
    return false;
  }

  std::string filename = this->parameters()->findFile("filename")->value();
  if (filename.empty())
  {
    return false;
  }
  return true;
}

ReadRXFFile::Result ReadRXFFile::operateInternal()
{
  Result result = this->createResult(smtk::operation::Operation::Outcome::FAILED);

  smtk::session::rgg::Resource::Ptr resource;
  smtk::model::EntityRef model;
  if (this->parameters()->associations()->numberOfValues() == 0)
  {
    auto createModelOp = smtk::session::rgg::CreateModel::create();

    auto createModelOpResult = createModelOp->operate();
    if (createModelOpResult->findInt("outcome")->value() !=
      static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
    {
      smtkErrorMacro(this->log(), "Could not create a model");
      return result;
    }

    resource = std::dynamic_pointer_cast<smtk::session::rgg::Resource>(
      createModelOpResult->findResource("resource")->value());
    model = createModelOpResult->findComponent("created")->valueAs<smtk::model::Entity>();
  }
  else
  {
    EntityRefArray entities = this->parameters()->associatedModelEntities<EntityRefArray>();
    if (entities.empty() || !entities[0].isModel())
    {
      smtkErrorMacro(this->log(), "An invalid model is provided for read rxf file op");
      return result;
    }
    model = entities[0];
  }

  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");

  // Verify the file
  smtk::attribute::FileItemPtr filenameItem = this->parameters()->findFile("filename");

  std::string filename = filenameItem->value();
  if (filename.empty())
  {
    smtkErrorMacro(this->log(), "A filename must be provided.\n");
    return result;
  }

  std::ifstream file(filename.c_str());
  if (!file.good())
  {
    smtkErrorMacro(this->log(), "Could not open file \"" << filename << "\".\n");
    return result;
  }

  std::string data((std::istreambuf_iterator<char>(file)), (std::istreambuf_iterator<char>()));

  if (data.empty())
  {
    smtkErrorMacro(this->log(), "No XML objects in file\"" << filename << "\".\n");
    return result;
  }

  // Start parsing
  pugi::xml_document document;
  pugi::xml_parse_result presult = document.load_buffer(data.c_str(), data.size());
  if (presult.status != pugi::status_ok)
  {
    smtkErrorMacro(this->log(), "Cannot parse XML objects in file\"" << filename << "\".\n");
    return result;
  }
  pugi::xml_node rootElement = document.child(CORE_TAG.c_str());

  // Read materials and store the info on the model. Without doing so, smtk
  // is not able to assign the right material color
  {
    pugi::xml_node materialNode = rootElement.child(MATERIALS_TAG.c_str());
    if (!ReadRXFFileHelper::parseMaterial(materialNode, model))
    {
      smtkErrorMacro(this->log(), "Encounter errors when parsing materials.");
    }
  }

  // Parse the core first. We will populate its assemblies later
  if (!ReadRXFFileHelper::parseCore(rootElement, model))
  {
    smtkErrorMacro(this->log(), "Encounter erros when parsing the basic core info.");
  }

  // Read pins
  smtk::model::EntityRefArray pins;
  {
    if (!ReadRXFFileHelper::parsePins(rootElement, model, pins))
    {
      smtkErrorMacro(this->log(), "Encounter errors when parsing pins.");
    }
  }

  // Read ducts and their sub parts
  const auto ductsN = rootElement.child(DUCTS_TAG.c_str());
  smtk::model::EntityRefArray ducts;
  {
    if (!ReadRXFFileHelper::parseDucts(ductsN, model, ducts))
    {
      smtkErrorMacro(this->log(), "Encounter errors when parsing ducts and core properties.");
    }
  }


  // Create labelToPin and nameToDuct maps for quick search purpose
  //  when creating assembly, core
  std::unordered_map<std::string, smtk::model::EntityRef> labelToPin;
  std::unordered_map<std::string, smtk::model::EntityRef> nameToDuct;
  for (auto pin : pins)
  {
    if (pin.hasStringProperty("label"))
    {
      labelToPin[pin.stringProperty("label")[0]] = pin;
    }
  }
  for (auto duct : ducts)
  {
    AuxiliaryGeometry ductAux = duct.as<smtk::model::AuxiliaryGeometry>();
    if (ductAux.isValid() && ductAux.auxiliaryGeometries().size())
    {
      nameToDuct[duct.name()] = duct;
    }
  }

  // Read assemblies
  // Since instances needed to be marked as "tess_changed",
  // we need to use a second array here
  smtk::model::EntityRefArray assys;
  {
    if (!ReadRXFFileHelper::parseAssemblies(
          rootElement, model, assys, labelToPin, nameToDuct, createdItem,
          modifiedItem, tessChangedItem))
    {
      smtkErrorMacro(this->log(), "Encounter errors when parsing assemblies.");
    }
  }

  // Populate core
  {
    std::unordered_map<std::string, smtk::model::EntityRef> labelToAssy;
    for (auto& assy : assys)
    {
      if (assy.hasStringProperty("label"))
      {
        labelToAssy[assy.stringProperty("label")[0]] = assy;
      }
    }
    if (!ReadRXFFileHelper::populateCoreInfo(rootElement, model, labelToAssy,
                                             createdItem, modifiedItem,
                                             tessChangedItem))
    {
      smtkErrorMacro(this->log(), "Encounter errors when parsing the core.");
    }
  }
  smtk::model::EntityRefArray coreArray =
    model.resource()->findEntitiesByProperty("rggType", smtk::session::rgg::Core::typeDescription);
  if (coreArray.size() == 0)
  {
    smtkErrorMacro(this->log(), "An invalid core is provided for read rxf file op");
    return result;
  }

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

  if (resource != nullptr)
  {
    result->findResource("resource")->setValue(resource);
    result->findComponent("created")->appendValue(model.component());
  }

  for (auto& c : ducts)
  {
    createdItem->appendValue(c.component());
  }
  for (auto& c : pins)
  {
    createdItem->appendValue(c.component());
  }
  for (auto& c : assys)
  {
    createdItem->appendValue(c.component());
  }

  modifiedItem->appendValue(model.component());
  modifiedItem->appendValue(coreArray[0].component());

  return result;
}

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