//=========================================================================
//  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/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/ResourceItem.h"
#include "smtk/attribute/StringItem.h"

#include "smtk/model/SessionIOJSON.h"
#include "smtk/model/json/jsonResource.h"

#include "smtk/session/rgg/Assembly.h"
#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/Duct.h"
#include "smtk/session/rgg/Pin.h"
#include "smtk/session/rgg/Registrar.h"
#include "smtk/session/rgg/Resource.h"
#include "smtk/session/rgg/Session.h"

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

#include "smtk/session/rgg/json/jsonAssembly.h"
#include "smtk/session/rgg/json/jsonCore.h"
#include "smtk/session/rgg/json/jsonDuct.h"
#include "smtk/session/rgg/json/jsonPin.h"

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

#include <vector>

using namespace smtk::session::rgg;
using json = nlohmann::json;


namespace
{
  double epison = 0.0000001;
  std::string dataRoot = DATA_DIR;
}

int TestRGGReadRXFFile(int argc, char* argv[])
{
  // Create a resource manager
  smtk::resource::Manager::Ptr resourceManager = smtk::resource::Manager::create();

  // Register rgg resources to the resource manager
  {
    smtk::session::rgg::Registrar::registerTo(resourceManager);
  }

  // Create an operation manager
  smtk::operation::Manager::Ptr operationManager = smtk::operation::Manager::create();

  // Register rgg operators to the operation manager
  {
    smtk::session::rgg::Registrar::registerTo(operationManager);
  }

  // Register the resource manager to the operation manager (newly created
  // resources will be automatically registered to the resource manager).
  operationManager->registerResourceManager(resourceManager);

  smtk::model::Model model;
  smtk::resource::ResourcePtr resource;
  auto createModelOp = smtk::session::rgg::CreateModel::create();
  if (!createModelOp)
  {
    std::cerr << "No create model operator\n";
    return 1;
  }

  // Create a hex core
  createModelOp->parameters()->findString("name")->setValue("simpleHexCore");
  createModelOp->parameters()->findString("geometry type")->setDiscreteIndex(0);
  createModelOp->parameters()->findDouble("z origin")->setValue(0);
  createModelOp->parameters()->findDouble("height")->setValue(100);
  createModelOp->parameters()->findDouble("duct thickness")->setValue(10);
  createModelOp->parameters()->findInt("hex lattice size")->setValue(3);

  auto createModelOpResult = createModelOp->operate();
  if (createModelOpResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "create model operator failed\n";
    return 1;
  }

  // Use a shared pointer to track the created resource so that it's valid out side of this scope
  resource =
    createModelOpResult->findResource("resource")->value();

  model =
    createModelOpResult->findComponent("created")->valueAs<smtk::model::Entity>();
  if (!model.isValid())
  {
    std::cerr << "create model operator constructed an invalid model\n";
    return 1;
  }

  // Set material and its color
  std::vector<std::string> materials{"NoCellMaterial", "CR", "Fuel", "Dt1", "Dt2", "MatH",
                                    "Mat_Control", "Mat_Coolant"};
  model.setStringProperty("materials", materials);
  model.setFloatProperty("NoCellMaterial",{ 1.0, 1.0, 1.0, 1.0 });
  model.setFloatProperty("CR",{ 0.3, 0.5, 1.0, .5 });
  model.setFloatProperty("Fuel",{ 0.3, 0.3, 1.0, .5 });
  model.setFloatProperty("Dt1",{ 0.75, 0.2, 0.75, 1.0});
  model.setFloatProperty("Dt2",{ 1.0, 0.1, 0.1, 1.0 });
  model.setFloatProperty("MatH",{ 0.0, 0.0, 0.0, 0.0 });
  model.setFloatProperty("Mat_Control",{ 0.3, 1.0, 0.5, 1.0 });
  model.setFloatProperty("Mat_Coolant",{ .4, .4, .4, 1.0 });

  smtk::model::Group coreGroup = createModelOpResult->findComponent("created")->
      valueAs<smtk::model::Entity>(1);
  if (!coreGroup.isValid())
  {
    std::cerr << "create model operator constructed an invliad core group\n";
    return 1;
  }
  if (!coreGroup.hasStringProperty(Core::propDescription))
  {
    std::cerr << "The created core does not have json representation string property\n";
    return 1;
  }

  auto readRxfOp = smtk::session::rgg::ReadRXFFile::create();
  std::string filePath = std::string(DATA_DIR) + "/sampleCore.rxf";
  std::cout <<"Reading file: " << filePath <<std::endl;
  readRxfOp->parameters()->findFile("filename")->setValue(filePath);
  readRxfOp->parameters()->associate(model.component());

  auto readRxfopResult = readRxfOp->operate();
  if (readRxfopResult->findInt("outcome")->value() !=
    static_cast<int>(smtk::operation::Operation::Outcome::SUCCEEDED))
  {
    std::cerr << "read rxf file operator failed\n";
    std::cerr << readRxfOp->log().convertToString(true) << "\n";
    return 1;
  }
  auto modelRes = model.resource();

  // Validate pins
  auto pinAuxs = modelRes->findEntitiesByProperty("rggType", Pin::typeDescription);
  assert(pinAuxs.size() == 13); // 5 pin and 8 subpins
  for (const auto& pinAux : pinAuxs)
  {
    if (pinAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() > 0)
    {
      Pin pin = nlohmann::json::parse(pinAux.stringProperty(Pin::propDescription)[0]);
      if (pin.name() == "Cell_Fuel")
      {
        assert(pinAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 4);
        assert(pin.label() == "FC");
        assert(pin.zOrigin() - 38.0 < epison);
        const auto& pieces = pin.pieces();
        assert(pieces.size() == 2);
        assert(pieces[0].pieceType == Pin::PieceType::CYLINDER);
        assert(pieces[0].length - 220 < epison);
        assert(pieces[0].baseRadius - 0.25225 < epison);
        assert(pieces[0].topRadius - 0.25225 < epison);
        assert(pieces[1].pieceType == Pin::PieceType::CYLINDER);
        assert(pieces[1].length - 100 < epison);
        assert(pieces[1].baseRadius - 0.25225 < epison);
        assert(pieces[1].topRadius - 0.25225 < epison);
        const auto& materials = pin.layerMaterials();
        assert(materials.size() == 2);
        assert(materials[0].subMaterialIndex == 3);
        assert(materials[0].normRadius == 0.5);
        assert(materials[1].subMaterialIndex == 4);
        assert(materials[1].normRadius == 1.0);
      }
      else if(pin.name() == "Cell_BurnPoison")
      {
        assert(pinAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 1);
        assert(pin.label() == "BP");
        assert(pin.zOrigin() - 38.0 < epison);
        const auto& pieces = pin.pieces();
        assert(pieces.size() == 1);
        assert(pieces[0].pieceType == Pin::PieceType::CYLINDER);
        assert(pieces[0].length - 320 < epison);
        assert(pieces[0].baseRadius - 0.25225 < epison);
        assert(pieces[0].topRadius - 0.25225 < epison);
        const auto& materials = pin.layerMaterials();
        assert(materials.size() == 1);
        assert(materials[0].subMaterialIndex == 3);
        assert(materials[0].normRadius == 1.0);
      }
      else if(pin.name() == "Cell_SmallCoolant")
      {
        assert(pinAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 1);
        assert(pin.label() == "SC");
        assert(pin.zOrigin() - 38.0 < epison);
        const auto& pieces = pin.pieces();
        assert(pieces.size() == 1);
        assert(pieces[0].pieceType == Pin::PieceType::CYLINDER);
        assert(pieces[0].length - 320 < epison);
        assert(pieces[0].baseRadius - 0.25225 < epison);
        assert(pieces[0].topRadius - 0.25225 < epison);
        const auto& materials = pin.layerMaterials();
        assert(materials.size() == 1);
        assert(materials[0].subMaterialIndex == 29);
        assert(materials[0].normRadius == 1.0);
      }
      else if(pin.name() == "Cell_Coolant")
      {
        assert(pinAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 1);
        assert(pin.label() == "CC");
        assert(pin.zOrigin() - 38.0 < epison);
        const auto& pieces = pin.pieces();
        assert(pieces.size() == 1);
        assert(pieces[0].pieceType == Pin::PieceType::CYLINDER);
        assert(pieces[0].length - 320 < epison);
        assert(pieces[0].baseRadius - 0.25225 < epison);
        assert(pieces[0].topRadius - 0.25225 < epison);
        const auto& materials = pin.layerMaterials();
        assert(materials.size() == 1);
        assert(materials[0].subMaterialIndex == 5);
        assert(materials[0].normRadius == 1.0);
      }
      else if(pin.name() == "Cell_ControlA")
      {
        assert(pinAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 1);
        assert(pin.label() == "CA");
        assert(pin.zOrigin() - 38.0 < epison);
        const auto& pieces = pin.pieces();
        assert(pieces.size() == 1);
        assert(pieces[0].pieceType == Pin::PieceType::CYLINDER);
        assert(pieces[0].length - 320 < epison);
        assert(pieces[0].baseRadius - 0.25225 < epison);
        assert(pieces[0].topRadius - 0.25225 < epison);
        const auto& materials = pin.layerMaterials();
        assert(materials.size() == 1);
        assert(materials[0].subMaterialIndex == 8);
        assert(materials[0].normRadius == 1.0);
      }
      else
      {
        std::cerr<< "An unrecognized pin is provided!" <<std::endl;
        return 1;
      }
    }
  }

  // Validate ducts
  const auto& ductAuxs = modelRes->findEntitiesByProperty("rggType", Duct::typeDescription);
  assert(ductAuxs.size() == 23); // 2 ducts and 21 sub ducts
  for (const auto& ductAux: ductAuxs)
  {
    if (ductAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() > 0)
    {
      Duct duct = nlohmann::json::parse(ductAux.stringProperty(Duct::propDescription)[0]);
      if (duct.name() == "IC_Duct")
      {
        assert(ductAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 6);
        const auto& segs = duct.segments();
        assert(segs.size() == 2);
        assert(segs[0].baseZ - 38 < epison);
        assert(segs[0].height - 42.1 < epison);
        assert(segs[0].layers.size() == 3);
        auto temp = std::make_tuple<int, double, double>(23, 0.8, 0.8);
        assert(segs[0].layers[0] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.9, 0.9);
        assert(segs[0].layers[1] == temp);
        temp = std::make_tuple<int, double, double>(21, 1.0, 1.0);
        assert(segs[0].layers[2] == temp);

        assert(segs[0].baseZ - 80.1 < epison);
        assert(segs[0].height - 277.9 < epison);
        assert(segs[0].layers.size() == 3);
        temp = std::make_tuple<int, double, double>(23, 0.8, 0.8);
        assert(segs[0].layers[0] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.9, 0.9);
        assert(segs[0].layers[1] == temp);
        temp = std::make_tuple<int, double, double>(21, 1.0, 1.0);
        assert(segs[0].layers[2] == temp);
      }
      else if (duct.name() == "PC_Duct")
      {
        assert(ductAux.as<smtk::model::AuxiliaryGeometry>().auxiliaryGeometries().size() == 15);
        const auto& segs = duct.segments();
        assert(segs.size() == 3);
        assert(segs[0].baseZ - 38 < epison);
        assert(segs[0].height - 42.1 < epison);
        assert(segs[0].layers.size() == 5);
        auto temp = std::make_tuple<int, double, double>(23, 0.4, 0.4);
        assert(segs[0].layers[0] == temp);
        temp = std::make_tuple<int, double, double>(23, 0.7, 0.7);
        assert(segs[0].layers[1] == temp);
        temp = std::make_tuple<int, double, double>(23, 0.8, 0.8);
        assert(segs[0].layers[2] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.9, 0.9);
        assert(segs[0].layers[3] == temp);
        temp = std::make_tuple<int, double, double>(21, 1.0, 1.0);
        assert(segs[0].layers[4] == temp);

        assert(segs[1].baseZ - 80.1 < epison);
        assert(segs[1].height - 17.9 < epison);
        assert(segs[1].layers.size() == 5);
        temp = std::make_tuple<int, double, double>(31, 0.5, 0.5);
        assert(segs[1].layers[0] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.7, 0.7);
        assert(segs[1].layers[1] == temp);
        temp = std::make_tuple<int, double, double>(31, 0.8, 0.8);
        assert(segs[1].layers[2] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.9, 0.9);
        assert(segs[1].layers[3] == temp);
        temp = std::make_tuple<int, double, double>(21, 1.0, 1.0);
        assert(segs[1].layers[4] == temp);

        assert(segs[2].baseZ - 98 < epison);
        assert(segs[2].height - 260 < epison);
        assert(segs[2].layers.size() == 5);
        temp = std::make_tuple<int, double, double>(31, 0.6, 0.6);
        assert(segs[2].layers[0] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.7, 0.7);
        assert(segs[2].layers[1] == temp);
        temp = std::make_tuple<int, double, double>(31, 0.8, 0.8);
        assert(segs[2].layers[2] == temp);
        temp = std::make_tuple<int, double, double>(25, 0.9, 0.9);
        assert(segs[2].layers[3] == temp);
        temp = std::make_tuple<int, double, double>(21, 1.0, 1.0);
        assert(segs[2].layers[4] == temp);
      }
    }
  }

  // Validate assemblies
  const auto& AssyGroups = modelRes->findEntitiesByProperty("rggType", Assembly::typeDescription);
  assert(AssyGroups.size() == 2);
  for (const auto& AssyG: AssyGroups)
  {
    Assembly assy = nlohmann::json::parse(AssyG.stringProperty(Assembly::propDescription)[0]);
    if (assy.name() == "PC_Duct")
    {
      assert(assy.label() == "PC");
      auto targetPitch = std::make_pair<double, double>(2,2);
      assert(assy.pitch() == targetPitch);
      assert(assy.rotate() == 0.0);
      auto targetLatticeSize = std::make_pair<int, int>(2,2);
      assert(assy.latticeSize() == targetLatticeSize);
      assert(assy.exportParams().Geometry == "Volume");
      assert(assy.exportParams().AxialMeshSize == 2.0);
      assert(assy.exportParams().NeumannSet_StartId == 500);
      assert(assy.exportParams().MaterialSet_StartId == 500);
      assert(assy.exportParams().EdgeInterval == 12.0);
      auto associatedDuctAux = smtk::model::EntityRef(modelRes, assy.associatedDuct());
      assert (associatedDuctAux.name() == "PC_Duct");
      auto layout = assy.layout();
      for (auto& iter: layout)
      {
        smtk::model::EntityRef pin(modelRes, iter.first);
        if (pin.stringProperty("label")[0] == "CA")
        {
          std::sort(iter.second.begin(), iter.second.end());
          std::vector<std::pair<int, int>> targetSchema = {{0,0}};
          assert(iter.second == targetSchema);
        }
        else if (pin.stringProperty("label")[0] == "FC")
        {
          std::sort(iter.second.begin(), iter.second.end());
          std::vector<std::pair<int, int>> targetSchema = {{1,0}, {1,2}, {1,4}};
          assert(iter.second == targetSchema);
        }
        else if (pin.stringProperty("label")[0] == "BP")
        {
          std::sort(iter.second.begin(), iter.second.end());
          std::vector<std::pair<int, int>> targetSchema = {{1,1}, {1,3}, {1,5}};
          assert(iter.second == targetSchema);
        }
      }
    }
    else if ( assy.name() == "IC_Duct")
    {
      assert(assy.label() == "IC");
      auto targetPitch = std::make_pair<double, double>(2,2);
      assert(assy.pitch() == targetPitch);
      assert(assy.rotate() == 0.0);
      auto targetLatticeSize = std::make_pair<int, int>(2,2);
      assert(assy.latticeSize() == targetLatticeSize);
      assert(assy.exportParams().Geometry == "Volume");
      assert(assy.exportParams().AxialMeshSize == 2.0);
      assert(assy.exportParams().NeumannSet_StartId == 100);
      assert(assy.exportParams().MaterialSet_StartId == 100);
      assert(assy.exportParams().EdgeInterval == 12.0);
      auto associatedDuctAux = smtk::model::EntityRef(modelRes, assy.associatedDuct());
      assert (associatedDuctAux.name() == "IC_Duct");
      auto layout = assy.layout();
      for (auto& iter: layout)
      {
        smtk::model::EntityRef pin(modelRes, iter.first);
        if (pin.stringProperty("label")[0] == "CA")
        {
          std::sort(iter.second.begin(), iter.second.end());
          std::vector<std::pair<int, int>> targetSchema = {{0,0}};
          assert(iter.second == targetSchema);
        }
        else if (pin.stringProperty("label")[0] == "SC")
        {
          std::sort(iter.second.begin(), iter.second.end());
          std::vector<std::pair<int, int>> targetSchema = {{1,0}, {1,1}, {1,2}};
          assert(iter.second == targetSchema);
        }
        else if (pin.stringProperty("label")[0] == "CC")
        {
          std::sort(iter.second.begin(), iter.second.end());
          std::vector<std::pair<int, int>> targetSchema = {{1,3}, {1,4}, {1,5}};
          assert(iter.second == targetSchema);
        }
      }
    }
  }

  // Valid core
  const auto& coreGroups = modelRes->findEntitiesByProperty("rggType", Core::typeDescription);
  assert(coreGroups.size() == 1);
  Core core = nlohmann::json::parse(coreGroups[0].stringProperty(Core::propDescription)[0]);
  assert(core.zOrigin() - 38.0 < epison);
  assert(core.height() - 320.0 < epison);
  assert(core.ductThickness().first - 14.598 < epison);
  assert(core.ductThickness().second - 14.598 < epison);
  assert(core.exportParams().OutputFileName == "abtr_core.h5m");
  assert(core.geomType() == Core::GeomType::Hex);
  assert(core.latticeSize().first == 2);
  assert(core.latticeSize().second == 2);
  auto layout = core.layout();
  for (auto& iter : layout)
  {
    smtk::model::Group assyG(modelRes, iter.first);
    if (assyG.stringProperty("label")[0] == "PC")
    {
      std::sort(iter.second.begin(), iter.second.end());
      std::vector<std::pair<int, int>> targetSchema = {{0,0}, {1,0}, {1,2}, {1,3}};
      assert(iter.second == targetSchema);
    }
    else if (assyG.stringProperty("label")[0] == "IC")
    {
      std::sort(iter.second.begin(), iter.second.end());
      std::vector<std::pair<int, int>> targetSchema = {{1,1}, {1,4}, {1,5}};
      assert(iter.second == targetSchema);
    }
  }

  return 0;
}
