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

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

#include "smtk/io/Logger.h"

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

#include "smtk/extension/vtk/source/vtkCmbLayeredConeSource.h"

#include "smtk/session/rgg/Assembly.h"
#include "smtk/session/rgg/Core.h"
#include "smtk/session/rgg/Duct.h"
#include "smtk/session/rgg/Material.h"
#include "smtk/session/rgg/Pin.h"

#include "smtk/session/rgg/operators/CreateModel.h"
#include "smtk/session/rgg/operators/EditAssembly.h"
#include "smtk/session/rgg/operators/EditPin.h"
#include "smtk/session/rgg/operators/EditDuct.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/Instance.h"
#include "smtk/model/Model.h"
#include "smtk/model/Resource.h"

#include <limits>

using rggAssembly = smtk::session::rgg::Assembly;
using rggCore = smtk::session::rgg::Core;
using rggDuct = smtk::session::rgg::Duct;
using rggPin = smtk::session::rgg::Pin;
using json = nlohmann::json;

static const double EPISON = 1e-6;
static const double cos30 = 0.86602540378443864676372317075294;
static const double cos60 = 0.5;
// 0', 60', 120, 180', 240', 300'
static const double cosSinAngles[6][2] = { { 1.0, 0.0 }, { cos60, -cos30 }, { -cos60, -cos30 },
  { -1.0, 0.0 }, { -cos60, cos30 }, { cos60, cos30 } };

namespace
{
// Calculate the x,y coordinates of the current pin in the hex grid
std::tuple<double, double, double> calculateHexPinCoordinate(
  const double& spacing, const std::pair<int, int>& ringAndLayer)
{
  // The index order of layer is clockwise, starting from upper left corner of the hex.
  // It's RGG's order and not ideal...
  double x{0}, y{0}, z{0};
  int ring = ringAndLayer.first;
  int layer = ringAndLayer.second;
  if (ring == 0)
  {
    x = y = 0;
  }
  else
  {
    int remainder = layer % ring;
    int modulus = layer / ring;

    double eL = ring * spacing;                       // edge length
    double xBT = -eL * cos60 + eL / (ring)*remainder; // x before transform
    double yBT = eL * cos30;                          // y befor transform
    // Apply rotation if needed. In order to avoid sin/cos calculation, we use
    // predefined values;
    double cosValue = cosSinAngles[modulus][0];
    double sinValue = cosSinAngles[modulus][1];

    x = xBT * cosValue - yBT * sinValue;
    y = yBT * cosValue + xBT * sinValue;
  }
  return std::make_tuple(x, y, z);
}

// FIXME: This function is duplicated in smtkRGGEditCoreView
// Calculate the x,y coordinates of the current assy in the hex grid
std::pair<double, double> calculateHexAssyCoordinate(const double& spacing, const int& ring, const int& layer)
{
  double x, y;
  // The index order of layer is clockwise, starting from upper left corner of the hex.
  // It's RGG's order and not ideal...
  if (ring == 0)
  {
    x = y = 0;
  }
  else
  {
    int remainder = layer % ring;
    int modulus = layer / ring;

    double eL = ring * spacing;                       // edge length
    double xBT = -eL * cos60 + eL / (ring)*remainder; // x before transform
    double yBT = eL * cos30;                          // y befor transform
    // Apply rotation if needed. In order to avoid sin/cos calculation, we use
    // predefined values;
    double cosValue = cosSinAngles[modulus][0];
    double sinValue = cosSinAngles[modulus][1];

    double x0 = xBT * cosValue - yBT * sinValue;
    double y0 = yBT * cosValue + xBT * sinValue;

    // Rotate 330 degree due to the fact that the orientations do not match in
    // the render view and schema planner
    // sin330 = -cos60 and cos330 = cos30;
    x = x0 * cos30 - y0 * (-cos60);
    y = y0 * cos30 + x0 * (-cos60);
  }
  return std::make_pair(x, y);
}

// A key to uniquely indentify unit prototypes for glyphing
struct GlyphProtoKey
{
  GlyphProtoKey()
  {
    type = Jacket;
    resolution = -1;
    boundaryLayer = false;
    for(int r = 0; r < 8; ++r)
    {
      radius[r] = -1;
    }
  }

  GlyphProtoKey( int s, double rTop, double rBottom)
  {
    type = Frustum;
    resolution = s;
    radius[0] = rTop;
    radius[1] = rBottom;
    boundaryLayer = false;
    for(int r = 2; r < 8; ++r)
    {
      radius[r] = -1;
    }
  }

  GlyphProtoKey( int s )
  {
    type = Cylinder;
    resolution = s;
    boundaryLayer = false;
    for(int r = 0; r < 8; ++r) radius[r] = -1;
  }

  GlyphProtoKey( int s,
       double rTop1, double rTop2, double rTop3, double rTop4,
       double rBottom1, double rBottom2, double rBottom3, double rBottom4, bool bl = false)
  {
    type = Annulus;
    resolution = s;
    radius[0] = rTop1;
    radius[1] = rBottom1;
    radius[2] = rTop2;
    radius[3] = rBottom2;
    radius[4] = rTop3;
    radius[5] = rBottom3;
    radius[6] = rTop4;
    radius[7] = rBottom4;
    // Boundary layer to generate the shell for the core. Not implemented yet
    boundaryLayer = bl;
  }

  bool operator<(GlyphProtoKey const& other) const
  {
    if(other.boundaryLayer != boundaryLayer)
    {
      return boundaryLayer;
    }
    if(type != other.type) return type < other.type;
    switch(type)
    {
      case Annulus:
      case Frustum:
        if(resolution == other.resolution)
        {
          for(unsigned int i = 0; i < 8; ++i)
          {
            if(fabs(radius[i] - other.radius[i]) > EPISON)
            {
              return radius[i] < other.radius[i];
            }
          }
        }
      case Cylinder:
      case Sectioned:
        return resolution < other.resolution;
      case Jacket:
        return false;
    }
    return false;
  }
private:
  enum {Cylinder, Frustum, Annulus, Jacket, Sectioned} type;
  int resolution;
  double radius[8];
  bool boundaryLayer;
};

struct InstanceInfo
{
  std::vector<double> coords;
  std::vector<double> rotations;
  std::vector<double> scales;
  std::vector<double> colors;
};
}

namespace smtk
{
namespace session
{
namespace rgg
{

size_t materialNameToIndex(std::string materialN, const std::vector<std::string>& materialList)
{
  auto iter = std::find(materialList.begin(), materialList.end(), materialN);
  if (iter != materialList.end())
  {
    size_t pos = std::distance(materialList.begin(), iter);
    return pos;
  }
  if (!materialN.empty())
  {
    smtkErrorMacro(smtk::io::Logger::instance().instance(), "Cannot find index for"
                                                            " material '"
        << materialN << "'. Set it to be the first defined material");
  }
  return 0;
}

bool OperationHelper::calculateEntityCoordsInCore(Core& core, smtk::model::ResourcePtr resource)
{
  // Pins and ducts
  // Calculate the starting point first
  bool isHex = (core.geomType() == Core::GeomType::Hex);
  std::pair<int, int> latticeSize = core.latticeSize();
  std::pair<double, double> spacing = core.ductThickness();
  auto& assyToLayout = core.layout();
  double baseX, baseY;
  if (!isHex)
  { // Use the cartesian coordinate where the starting point is located
    // at left bottom
    baseX = -1 * spacing.first * (static_cast<double>(latticeSize.first) / 2 - 0.5);
    baseY = -1 * spacing.second * (static_cast<double>(latticeSize.second) / 2 - 0.5);
  }
  else
  { // Spacing is the allowable max distance between two adjacent assembly centers
    // Use the cartesian coordinate where the starting point is located at
    // the origin point.
    baseX = baseY = 0.0; // Ignored by calculateHexAssyCoordinate for now
  }

  // Map pins&ducts to their placements
  Core::UuidToCoordinates pdToCoords, assyToCoords;
  // Loop through each assembly, calculate the new global coords for its duct and pins
  for (auto iter = assyToLayout.begin(); iter != assyToLayout.end(); iter++)
  { // For each assembly, retrieve its pins&duct info, apply the right transformation
    // then add it into pinDuctToLayout map

    smtk::model::EntityRef assyEntity = smtk::model::EntityRef(resource, iter->first);
    if (!assyEntity.hasStringProperty(Assembly::propDescription))
    {
      smtkErrorMacro(smtk::io::Logger::instance(), "An invalid assembly is provided"
                                                   " in the core layout");
    }
    const std::vector<std::pair<int, int>>& assyLayout = iter->second;
    Assembly assy = json::parse(assyEntity.stringProperty(Assembly::propDescription)[0]);
    smtk::common::UUID ductUUID = assy.associatedDuct();

    auto pinsToLocalCoords = assy.entityToCoordinates();

    std::vector<std::tuple<double, double, double>> assyCoordinates;
    for (size_t index = 0; index < assyLayout.size(); index++)
    {
      double x, y;
      if (isHex)
      {
        auto xAndy = calculateHexAssyCoordinate(spacing.first, assyLayout[index].first,
                                                assyLayout[index].second);
        x = xAndy.first;
        y = xAndy.second;
      }
      else
      {
        // In schema planner, x and y axis are following Qt's pattern.
        // Here we just follow the traditional coordinate convension
        x = baseX + spacing.first * assyLayout[index].first;
        y = baseY + spacing.second * assyLayout[index].second;
      }
      assyCoordinates.emplace_back(std::make_tuple(x, y, 0));
      // For each (x,y) pair, add it to every pin and duct in the current assy
      auto transformCoordsAndAddToMap = [&pdToCoords, &x, &y](
        const smtk::common::UUID& ent, std::vector<std::tuple<double, double, double>>& coords) {
        // Apply transformation
        for (size_t i = 0; i < coords.size(); i++)
        {
          // X
          std::get<0>(coords[i]) += x;
          // Y
          std::get<1>(coords[i]) += y;
        }

        if (pdToCoords.find(ent) != pdToCoords.end())
        { // TODO: Possible performance bottleneck
          pdToCoords[ent].insert(pdToCoords[ent].end(), coords.begin(), coords.end());
        }
        else
        {
          pdToCoords[ent] = coords;
        }
      };
      // Duct
      std::vector<std::tuple<double, double, double>> ductCoords = {
        std::make_tuple(0, 0, 0)};
      transformCoordsAndAddToMap(ductUUID, ductCoords);
      // Pins
      for (const auto& iter : pinsToLocalCoords)
      { // Make an explict copy
        std::vector<std::tuple<double, double, double>> coords = iter.second;
        transformCoordsAndAddToMap(iter.first, coords);
      }
    }
    assyToCoords.emplace(iter->first, assyCoordinates);
  }
  core.setEntityToCoordinates(assyToCoords);
  core.setPinsAndDuctsToCoordinates(pdToCoords);
  return true;
}

bool OperationHelper::calculateEntityCoordsInAssembly(Assembly& assembly,
                                                      bool isHex,
                                                      smtk::model::ResourcePtr resource)
{
  // Calculate UuidToCoordinates
  // Copy the logic from smtkRGGEditAssemblyView::apply function
  const std::pair<int,int>& latticeSize= assembly.latticeSize();
  const std::pair<double,double>& pitches = assembly.pitch();
  const auto& pinUuidToSchema = assembly.layout();
  smtk::model::EntityRef ductAux(resource, assembly.associatedDuct());
  auto thicknesses = assembly.pitch();
  std::vector<double> spacing = { 0, 0 };
  double baseX{0}, baseY{0};
  if (!isHex)
  { // Use the cartesian coordinate where the starting point is located
    // at left bottom
    // TODO: Use the pitch defined in the file?
    spacing[0] = thicknesses.first / static_cast<double>(latticeSize.first);
    spacing[1] = thicknesses.second / static_cast<double>(latticeSize.second);
    baseX = -1 * thicknesses.first / 2 + spacing[0] / 2;
    baseY = -1 * thicknesses.second / 2 + spacing[0] / 2;
  }
  else
  { // Spacing is the allowable max distance between two adjacent pin centers
    // Use the cartesian coordinate where the starting point is located at
    // the origin point.
    spacing[0] = pitches.first;
    spacing[1] = pitches.second;
    baseX = baseY = 0.0; // Ignored by calculateHexPinCoordinate for now
  }

  Assembly::UuidToCoordinates entityToCoordinates;
  for (auto iter = pinUuidToSchema.begin(); iter != pinUuidToSchema.end(); iter++)
  {
    const auto& layout = iter->second;
    std::vector<std::tuple<double, double, double>> coordinates;
    coordinates.reserve(layout.size());
    for (size_t index = 0; index < layout.size(); index++)
    {
      double x, y;
      if (isHex)
      {
        coordinates.emplace_back(calculateHexPinCoordinate(spacing[0], layout[index]));
      }
      else
      { // Question
        // In schema planner, x and y axis are exchanged. Here we just follow the traditional coordinate convension
        x = baseX + spacing[0] * layout[index].first;
        y = baseY + spacing[1] * layout[index].second;
        coordinates.emplace_back(std::make_tuple(x,y,0));
      }
    }
    // Set it in the edit assembly
    //cIAtt->findModelEntity("snap to entity")->setIsEnabled(false);
    entityToCoordinates.emplace(iter->first, std::move(coordinates));
  }
  assembly.setEntityToCoordinates(entityToCoordinates);
  return true;
}

// TODO: Simplify the logic and refactor the code since it share much with rggAuxiliaryGeometryExtension
void OperationHelper::glyphDucts(smtk::model::ResourcePtr& resource,
                                           smtk::attribute::ComponentItemPtr& createdItem,
                                           smtk::attribute::ComponentItemPtr& modifiedItem,
                                           smtk::attribute::ComponentItemPtr& tessChangedItem,
                                           smtk::model::Group& group,
                                           const Assembly::UuidToCoordinates& uuidToCoord,
                                           const std::string& label)
{
  using unitProtoType = std::pair<smtk::model::AuxiliaryGeometry, InstanceInfo>;
  std::map<GlyphProtoKey, unitProtoType> keyToUnitProtoInfo;

  bool fetchCoreInfo(false);
  smtk::model::Model model;
  bool isHex{false};
  std::pair<double, double> ductThickness;

  // Find the all unit ducts
  for (const auto& iter: uuidToCoord)
  {
    smtk::model::EntityRef ductEntity(resource, iter.first);
    if (!fetchCoreInfo)
    {
      fetchCoreInfo = true;
      model = ductEntity.owningModel();
      // Since duct properties are defined at core level, find the core from resource
      // then use its info to populate the panel.
      EntityRef coreGroup =
          model.resource()->findEntitiesByProperty("rggType", Core::typeDescription)[0];
      Core core = json::parse(coreGroup.stringProperty(Core::propDescription)[0]);

      isHex = core.geomType() == Core::GeomType::Hex;
      ductThickness = core.ductThickness();
    }
    if (!ductEntity.hasStringProperty(rggDuct::propDescription))
    {
      continue;
    }
    // Find its unit and annulus geometries
    rggDuct duct = json::parse(ductEntity.stringProperty(rggDuct::propDescription)[0]);
    const auto& segments = duct.segments();

    size_t numSegs = segments.size();
    for (size_t segIndex = 0; segIndex < numSegs; segIndex++)
    {
      // Create layer manager
      // TODO: For now I just blindly follow the generation logic in the old RGG appliation. It's not
      // straightforward and if we have time, we should simplify it.
      const auto& currentSeg = segments[segIndex];
      double z1 =  currentSeg.baseZ;
      double height = currentSeg.height;
      // Magic number used in rgg appliation code. My understanding is that it's
      // a warkaround for z fighting issue.
      double magicRatio = 0.0005;
      double deltaZ = height * magicRatio;
      if (segIndex == 0)
      {
        z1 = z1 + deltaZ;
        // if more than one duct, first duct height need to be reduced by deltaZ
        height = (numSegs > 1) ? height - deltaZ : height - 2 * deltaZ;
      }
      else if (segIndex == (numSegs - 1)) //last duct
      {
        height -= 2 * deltaZ;
      }
      else
      {
        z1 += deltaZ;
      }
      size_t numLayers = currentSeg.layers.size();
      vtkSmartPointer<vtkCmbLayeredConeSource> layerMgr =
        vtkSmartPointer<vtkCmbLayeredConeSource>::New();
      layerMgr->SetNumberOfLayers(static_cast<int>(numLayers));
      layerMgr->SetBaseCenter(0, 0, z1);
      double direction[] = { 0, 0, 1 };
      layerMgr->SetDirection(direction);
      layerMgr->SetHeight(height);

      int res = 4;
      double mult = 0.5;

      if (isHex)
      {
        res = 6;
        mult = 0.5 / cos30;
      }

      for (size_t k = 0; k < numLayers; k++)
      { // For each layer based on is hex or not,
        // it might have two different thicknesses
        const auto& layer = currentSeg.layers[k];
        double tx = std::get<1>(layer) * ductThickness.first *(1- magicRatio);
        double ty = std::get<2>(layer) * ductThickness.second  * (1 - magicRatio);
        layerMgr->SetBaseRadius(static_cast<int>(k), tx * mult, ty * mult);
        layerMgr->SetTopRadius(static_cast<int>(k), tx * mult, ty * mult);
        layerMgr->SetResolution(static_cast<int>(k), res);
      }
      // The offset for current unit duct.
      double baseCenter[3];
      layerMgr->GetBaseCenter(baseCenter);

      // Now we have populate the conSource. Calculate the unique key for each subDuct
      for (int layerI = 0; layerI < layerMgr->GetNumberOfLayers(); layerI++)
      {
        // Inner hex/rect(layerI=0) or annulus hex/rect(layerI>0)
        GlyphProtoKey k = (layerI == 0) ? GlyphProtoKey(layerMgr->GetResolution(layerI)) :
                          GlyphProtoKey(layerMgr->GetResolution(layerI),
                                        layerMgr->GetTopRadius(layerI, 0), layerMgr->GetTopRadius(layerI, 1),
                                        layerMgr->GetTopRadius(layerI-1, 0), layerMgr->GetTopRadius(layerI-1, 1),
                                        layerMgr->GetBaseRadius(layerI,0), layerMgr->GetBaseRadius(layerI,1),
                                        layerMgr->GetBaseRadius(layerI-1,0), layerMgr->GetBaseRadius(layerI-1,1));
        std::pair<double, double> scaleXY = (layerI == 0) ?
                                        std::make_pair(layerMgr->GetTopRadius(0, 0), layerMgr->GetTopRadius(0,1)) :
                                        std::make_pair(1.0, 1.0);
        // Get Material color
        int mIndex = std::get<0>(currentSeg.layers[static_cast<size_t>(layerI)]);
        auto mColor = CreateModel::getMaterialColor(static_cast<size_t>(mIndex), model);

        if (keyToUnitProtoInfo.find(k) == keyToUnitProtoInfo.end())
        {
          smtk::model::AuxiliaryGeometry unitGeom = resource->addAuxiliaryGeometry(model, 3);
          // Question: hide them in a dummy group or solve it in the rgg session descriptive phrase generator
          createdItem->appendValue(unitGeom.component());
          modifiedItem->appendValue(model.component());

          std::string name = label + "_unitDuct_Segment_" + std::to_string(segIndex) + "_layer_" + std::to_string(layerI);
          unitGeom.setName(name);
          unitGeom.setVisible(group.visible());
          group.addEntity(unitGeom);
          // The unit geom should be excluded from everything since it's used for glyphing only
          unitGeom.setExclusions(true);
          // FIXME: Fix these hard coded properties
          unitGeom.setStringProperty("rggType",OperationHelper::protoTypeDescription);
          unitGeom.setStringProperty("representation",ductEntity.stringProperty(rggDuct::propDescription)[0]);
          unitGeom.setStringProperty("representation_type", rggDuct::typeDescription);
          unitGeom.setIntegerProperty("index", {static_cast<long>(segIndex), static_cast<long>(layerI)});
          keyToUnitProtoInfo[k] = std::make_pair(unitGeom, InstanceInfo());
        }
        InstanceInfo& info = keyToUnitProtoInfo[k].second;
        for (const auto& coord : iter.second)
        {
          info.coords.push_back(std::get<0>(coord) + baseCenter[0]);
          info.coords.push_back(std::get<1>(coord) + baseCenter[1]);
          info.coords.push_back(std::get<2>(coord) + baseCenter[2]);
          info.scales.push_back(scaleXY.first);
          info.scales.push_back(scaleXY.second);
          info.scales.push_back(layerMgr->GetHeight());
          info.colors.push_back(mColor[0] *255);
          info.colors.push_back(mColor[1] *255);
          info.colors.push_back(mColor[2] *255);
          info.colors.push_back(mColor[3] *255);
        }
      }
    }
  }
  // Glyph all the unit ducts we've found
  for (auto iter: keyToUnitProtoInfo)
  {
    auto& protoType = iter.second.first;
    InstanceInfo& instanceInfo = iter.second.second;

    Instance instance = resource->addInstance(protoType);
    instance.setRule("tabular");
    instance.setVisible(group.visible());
    instance.setName(protoType.name()+"-instance");
    instance.setFloatProperty(Instance::placements, instanceInfo.coords);
    instance.setFloatProperty(Instance::orientations, instanceInfo.rotations);
    instance.setFloatProperty(Instance::scales, instanceInfo.scales);
    instance.setFloatProperty(Instance::colors, instanceInfo.colors);

    group.addEntity(instance);
    createdItem->appendValue(instance.component());
    modifiedItem->appendValue(protoType.component());
    tessChangedItem->appendValue(instance.component());

    // Now that the instance is fully specified, generate
    // the placement points.
    instance.generateTessellation();
  }
}

// TODO: Simplify the logic and refactor the code since it shares much with rggAuxiliaryGeometryExtension
void OperationHelper::glyphPins(smtk::model::ResourcePtr& resource,
                                           smtk::attribute::ComponentItemPtr& createdItem,
                                           smtk::attribute::ComponentItemPtr& modifiedItem,
                                           smtk::attribute::ComponentItemPtr& tessChangedItem,
                                           smtk::model::Group& group,
                                           const Assembly::UuidToCoordinates& uuidToCoord,
                                           const std::string& label)
{
  using unitProtoType = std::pair<smtk::model::AuxiliaryGeometry, InstanceInfo>;
  std::map<GlyphProtoKey, unitProtoType> keyToUnitProtoInfo;
  smtk::model::Model model = group.owningModel();

  for (auto iter : uuidToCoord)
  {
    smtk::model::EntityRef pinAux(resource, iter.first);

    if (!pinAux.hasStringProperty(Pin::propDescription))
    {
      continue;
    }
    rggPin pin = json::parse(pinAux.stringProperty(rggPin::propDescription)[0]);
    // Extract info from pin
    int materialIndex = pin.cellMaterialIndex();
    bool isMaterialSet = materialIndex > 0 ? true : false;

    double zOrigin = pin.zOrigin();

    bool isHex = pinAux.owningModel().integerProperty(Core::geomDescription)[0]
                                        == static_cast<int>(Core::GeomType::Hex);

    const std::vector<Pin::Piece>& pieces = pin.pieces();

    const std::vector<Pin::LayerMaterial>& layerMs = pin.layerMaterials();

    // Follow logic in cmbNucRender::createGeo function. L249
    size_t numParts = pieces.size();
    size_t numLayers = layerMs.size();
    double baseCenter(zOrigin);

    constexpr int PinCellResolution = 20;

    for (size_t partI = 0; partI < numParts; ++partI)
    {
      // Create a vtkCmbLayeredConeSource for current part
      vtkSmartPointer<vtkCmbLayeredConeSource> layerMgr =
        vtkSmartPointer<vtkCmbLayeredConeSource>::New();
      //  Add a material layer if needed
      layerMgr->SetNumberOfLayers(static_cast<int>(numLayers + isMaterialSet));
      double height(pieces[partI].length);
      double baseR(pieces[partI].baseRadius), topR(pieces[partI].topRadius);
      // baseCenter would be updated at the end of the loop
      layerMgr->SetBaseCenter(0, 0, baseCenter);
      layerMgr->SetHeight(height);
      double largestRadius = 0;

      for (size_t layerI = 0; layerI < numLayers; layerI++)
      {
        // Calculate the baseR and topR at current layer
        double baseRL = baseR * layerMs[layerI].normRadius;
        double topRL = topR * layerMs[layerI].normRadius;
        layerMgr->SetBaseRadius(static_cast<int>(layerI), baseRL);
        layerMgr->SetTopRadius(static_cast<int>(layerI), topRL);
        layerMgr->SetResolution(static_cast<int>(layerI), PinCellResolution);
        // Update largest radius for cell material visualization
        if (largestRadius < baseRL)
        {
          largestRadius = baseRL;
        }
        if (largestRadius < topRL)
        {
          largestRadius = topRL;
        }
      }
      if (isMaterialSet) // We have a valid material assigned( 0 means no material)
      {
        largestRadius *= 2.50;
        double r[] = { largestRadius * 0.5, largestRadius * 0.5 };
        int res = 4;
        if (isHex)
        {
          res = 6;
          r[0] = r[1] = r[0] / cos30;
        }
        layerMgr->SetBaseRadius(static_cast<int>(numLayers), r[0], r[1]);
        layerMgr->SetTopRadius(static_cast<int>(numLayers), r[0], r[1]);
        layerMgr->SetResolution(static_cast<int>(numLayers), res);
      }
      double direction[] = { 0, 0, 1 };
      layerMgr->SetDirection(direction);

      // Now we have the layer manager. Create keys for subducts and calculate the related glyphing info.
      double curBaseCenter[3];
      layerMgr->GetBaseCenter(curBaseCenter);

      std::vector<double> rotation{0.0, 0.0, (isHex) ? 30.0: 0.0};
      bool addCellMat = pin.cellMaterialIndex() > 0;
      for (int layerI = layerMgr->GetNumberOfLayers()-1; layerI >= 0; layerI--)
      {
        smtk::model::FloatList mColor;
        if (addCellMat)
        {
          addCellMat = false;
          mColor = CreateModel::getMaterialColor(static_cast<size_t>(pin.cellMaterialIndex()),
                                                 pinAux.owningModel());
        }
        else
        {
          mColor = CreateModel::getMaterialColor(static_cast<size_t>(layerMs[layerI].subMaterialIndex),
                                                 pinAux.owningModel());
        }
        GlyphProtoKey key;
        std::pair<double, double> thickness{1.0, 1.0};
        // Inner cylinder/frustum
        if (layerI == 0)
        { // Assume it's a cylinder
          key = GlyphProtoKey(layerMgr->GetResolution(0));
          thickness = std::pair<double, double>{layerMgr->GetTopRadius(0), layerMgr->GetBaseRadius(0)};
          // frustum
          if (thickness.first - thickness.second > EPISON)
          {
            key = GlyphProtoKey(layerMgr->GetResolution(0), thickness.first, thickness.second);
            // Reset the scale for x and y
            thickness = std::make_pair(1.0, 1.0);
          }
        }
        else
        {
          key = GlyphProtoKey(layerMgr->GetResolution(layerI),
                          layerMgr->GetTopRadius(layerI, 0), layerMgr->GetTopRadius(layerI, 1),
                          layerMgr->GetTopRadius(layerI-1, 0), layerMgr->GetTopRadius(layerI-1, 1),
                          layerMgr->GetBaseRadius(layerI, 0), layerMgr->GetBaseRadius(layerI, 1),
                          layerMgr->GetBaseRadius(layerI-1, 0), layerMgr->GetBaseRadius(layerI-1, 1));

        }

        if (keyToUnitProtoInfo.find(key) == keyToUnitProtoInfo.end())
        {
          smtk::model::AuxiliaryGeometry unitGeom = resource->addAuxiliaryGeometry(model, 3);
          // Question: hide them in a dummy group or solve it in the rgg session descriptive phrase generator
          createdItem->appendValue(unitGeom.component());

          std::string name = label + "_unitPin_pinPart_" + std::to_string(partI) + "_layer_" + std::to_string(layerI);
          unitGeom.setName(name);
          unitGeom.setVisible(group.visible());
          group.addEntity(unitGeom);
          // The unit geom should be excluded from everything since it's used for glyphing only
          unitGeom.setExclusions(true);

          // FIXME: Fix these hard coded properties
          unitGeom.setStringProperty("rggType",OperationHelper::protoTypeDescription);
          unitGeom.setStringProperty("representation",pinAux.stringProperty(rggPin::propDescription)[0]);
          unitGeom.setStringProperty("representation_type", rggPin::typeDescription);
          unitGeom.setIntegerProperty("index", {static_cast<long>(partI), layerI});
          modifiedItem->appendValue(model.component());
          keyToUnitProtoInfo[key] = std::make_pair(unitGeom, InstanceInfo());
        }
        InstanceInfo& info = keyToUnitProtoInfo[key].second;
        for (const auto& coord : iter.second)
        {
          info.coords.push_back(std::get<0>(coord) + curBaseCenter[0]);
          info.coords.push_back(std::get<1>(coord) + curBaseCenter[1]);
          info.coords.push_back(std::get<2>(coord) + curBaseCenter[2]);
          info.rotations.push_back(rotation[0]);
          info.rotations.push_back(rotation[1]);
          info.rotations.push_back(rotation[2]);
          info.scales.push_back(thickness.first);
          info.scales.push_back(thickness.second);
          info.scales.push_back(layerMgr->GetHeight());
          info.colors.push_back(mColor[0] *255);
          info.colors.push_back(mColor[1] *255);
          info.colors.push_back(mColor[2] *255);
          info.colors.push_back(mColor[3] *255);
        }
      }
      // Update current baseCenter
      baseCenter += height;
    }
  }
  // Glyph all the unit pins we've found
  for (auto iter: keyToUnitProtoInfo)
  {
    auto& protoType = iter.second.first;
    InstanceInfo& instanceInfo = iter.second.second;

    Instance instance = resource->addInstance(protoType);
    instance.setRule("tabular");
    instance.setVisible(group.visible());
    instance.setName(protoType.name()+"-instance");
    instance.setFloatProperty(Instance::placements, instanceInfo.coords);
    instance.setFloatProperty(Instance::orientations, instanceInfo.rotations);
    instance.setFloatProperty(Instance::scales, instanceInfo.scales);
    instance.setFloatProperty(Instance::colors, instanceInfo.colors);

    group.addEntity(instance);
    createdItem->appendValue(instance.component());
    modifiedItem->appendValue(protoType.component());
    tessChangedItem->appendValue(instance.component());

    // Now that the instance is fully specified, generate
    // the placement points.
    instance.generateTessellation();
  }
}

void OperationHelper::populateChildrenInPinAux(smtk::model::AuxiliaryGeometry& pinAux,
                              std::vector<smtk::model::EntityRef>& subPinAuxs,
                                          const Pin& pin)
{
  auto assignColor = [](size_t index, smtk::model::AuxiliaryGeometry& aux) {
    smtk::model::FloatList rgba = smtk::session::rgg::CreateModel::getMaterialColor(index, aux.owningModel());
    aux.setColor(rgba);
  };
  size_t numParts = pin.pieces().size();
  size_t numLayers = pin.layerMaterials().size();

  // Create auxgeom placeholders for layers and parts
  for (std::size_t i = 0; i < numParts; i++)
  {
    for (std::size_t j = 0; j < numLayers; j++)
    {
      // Create an auxgeom for current each unit part&layer
      AuxiliaryGeometry subLayer = pinAux.resource()->addAuxiliaryGeometry(pinAux, 3);
      std::string subLName = pin.name() + smtk::session::rgg::Pin::subpartDescription +
                             std::to_string(i) +
                             Pin::layerDescription + std::to_string(j);
      subLayer.setName(subLName);
      subLayer.setVisible(pinAux.visible());
      subLayer.setStringProperty("rggType", Pin::typeDescription);
      assignColor(static_cast<size_t>(pin.layerMaterials()[j].subMaterialIndex), subLayer);
      subPinAuxs.push_back(subLayer.as<EntityRef>());
    }
    if (pin.cellMaterialIndex()) // 0 index means no material
    {
      // Append a material layer after the last layer
      AuxiliaryGeometry materialLayer = pinAux.resource()->addAuxiliaryGeometry(pinAux, 3);
      std::string materialName =
        pin.name() + smtk::session::rgg::Pin::subpartDescription +
          std::to_string(i) + Pin::materialDescription;
      materialLayer.setName(materialName);
      materialLayer.setVisible(pinAux.visible());
      materialLayer.setStringProperty("rggType", Pin::typeDescription);
      assignColor(size_t(pin.cellMaterialIndex()), materialLayer);
      subPinAuxs.push_back(materialLayer.as<EntityRef>());
    }
  }
}

void OperationHelper::populateChildrenInDuctAux(
   AuxiliaryGeometry& ductAux, std::vector<EntityRef>& subAuxGeoms,const Duct& duct)
{
  auto assignColor = [](size_t index, smtk::model::AuxiliaryGeometry& aux) {
    smtk::model::FloatList rgba = smtk::session::rgg::CreateModel::getMaterialColor(index, aux.owningModel());
    aux.setColor(rgba);
  };

  // Create auxgeom placeholders for layers and parts
  const std::vector<Duct::Segment>& segments = duct.segments();
  for (std::size_t i = 0; i < segments.size(); i++)
  {
    const auto& seg = segments[i];
    for (std::size_t j = 0; j < static_cast<std::size_t>(seg.layers.size()); j++)
    {
      // Create an auxgeom for every layer in current segment
      AuxiliaryGeometry subLayer = ductAux.resource()->addAuxiliaryGeometry(ductAux, 3);
      std::string subLName = duct.name() + smtk::session::rgg::Duct::segmentDescription + std::to_string(i) +
        smtk::session::rgg::Duct::layerDescription + std::to_string(j);
      subLayer.setName(subLName);
      subLayer.setVisible(ductAux.visible());
      subLayer.setStringProperty("rggType", smtk::session::rgg::Duct::typeDescription);
      assignColor(static_cast<size_t>(std::get<0>(seg.layers[j])), subLayer);
      subAuxGeoms.push_back(subLayer.as<EntityRef>());
    }
  }
}

void OperationHelper::updateRelatedAssyAndCore(smtk::model::AuxiliaryGeometry& aux,
                                           smtk::attribute::ComponentItemPtr& createdItem,
                                           smtk::attribute::ComponentItemPtr& modifiedItem,
                                           smtk::attribute::ComponentItemPtr& expungedItem,
                                           smtk::attribute::ComponentItemPtr& tessChangedItem,
                                           bool debugInfo)
{
  if (!aux.hasStringProperty("rggType") || (aux.stringProperty("rggType")[0] != rggPin::typeDescription
       && aux.stringProperty("rggType")[0] != rggDuct::typeDescription))
  {
    return;
  }

  smtk::model::ResourcePtr resource = aux.resource();
  // Check assemblies first
  EntityRefArray assyEntityRefs = resource->findEntitiesByProperty("rggType",
                                                                 rggAssembly::typeDescription);
  for (auto assyEnt: assyEntityRefs)
  {
    Assembly assy = json::parse(assyEnt.stringProperty(rggAssembly::propDescription)[0]);
    // aux is used in this assy as a pin or duct
    if (assy.associatedDuct() == aux.entity() || assy.entityToCoordinates().count(aux.entity()))
    {
      smtk::model::EntityRefArray expunged, modified, tobeDeleted;

      // TODO: Here the logic can be simplified so that we only regenerated the modified aux's
      // representation. For simplicity I just clear all and regenerate everything.
      // Delete all existing glyphing and regenerate the glyphing.
      smtk::model::Group assyGroup = assyEnt.as<smtk::model::Group>();
      auto oldMembers = assyGroup.members<smtk::model::EntityRefArray>();
      tobeDeleted.insert(tobeDeleted.begin(), oldMembers.begin(), oldMembers.end());
      for (auto& e : tobeDeleted)
      {
        expungedItem->appendValue(e.component());
      }

      // Regenerate everything
      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());
      // 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, debugInfo);
    }
  }

  {
    smtk::model::Group coreGroup = resource->findEntitiesByProperty("rggType",
                              rggCore::typeDescription)[0].as<smtk::model::Group>();
    smtk::model::EntityRefArray expunged, modified, tobeDeleted;
    Core core = json::parse(coreGroup.stringProperty(rggCore::propDescription)[0]);
    // Remove all current entities in the group
    auto groupMembers = coreGroup.members<smtk::model::EntityRefArray>();
    tobeDeleted.insert(tobeDeleted.begin(), groupMembers.begin(), groupMembers.end());
    for (auto& e : tobeDeleted)
    {
      expungedItem->appendValue(e.component());
    }

    // Glyph the used pins and ducts, then add them into the group
    Core::UuidToCoordinates& pDToCoords = core.pinsAndDuctsToCoordinates();
    OperationHelper::glyphDucts(resource,
                                createdItem,
                                modifiedItem, tessChangedItem,
                                coreGroup, pDToCoords, "Core");
    OperationHelper::glyphPins(resource,
                                createdItem,
                                modifiedItem, tessChangedItem,
                                coreGroup, pDToCoords, "Core");

    modifiedItem->appendValue(coreGroup.component());

    // 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, debugInfo);
  }
}

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