/*
  Copyright (C) 2002-2023 CERN for the benefit of the ATLAS collaboration
*/

//
//   Processor for logvol elements
//
//   Add name to list.
//   Create a physvol using my logvol; add it to list.
//   Process children and get list of things to be added to the physvol.
//   Add them to the physvol.
//
//
// Updates:
// - 2022/02, Riccardo Maria BIANCHI <riccardo.maria.bianchi@cern.ch>
//            * Removed the automatic cration of a GeoNameTag for each volume, to save memory
//            * Added support for the GeoNameTag node
//


#include "GeoModelXml/LogvolProcessor.h"
#include "OutputDirector.h"

#include <map>

#include <xercesc/dom/DOM.hpp>
#include "GeoModelKernel/GeoNameTag.h"
#include "GeoModelKernel/GeoIdentifierTag.h"
#include "GeoModelKernel/GeoLogVol.h"
#include "GeoModelKernel/GeoPhysVol.h"
#include "GeoModelKernel/GeoFullPhysVol.h"
#include "GeoModelKernel/GeoVFullPhysVol.h"
#include "GeoModelKernel/GeoVolumeTagCatalog.h"
#include "GeoModelXml/GmxUtil.h"
#include "GeoModelXml/GeoNodeList.h"
#include "xercesc/util/XMLString.hpp"

#include "GeoModelKernel/throwExcept.h"
// using namespace CLHEP;

using namespace std;
using namespace xercesc;

std::string getNodeType(const GeoGraphNode* node) {
    if ( dynamic_cast<const GeoPhysVol*>(node) )
        return "GeoPhysVol";
    if ( dynamic_cast<const GeoFullPhysVol*>(node) )
        return "GeoFullPhysVol";
    if ( dynamic_cast<const GeoIdentifierTag*>(node) )
        return "GeoIdentifierTag";
    if ( dynamic_cast<const GeoNameTag*>(node) )
        return "GeoNameTag";
    if ( dynamic_cast<const GeoLogVol*>(node) )
        return "GeoLogVol";
    if ( dynamic_cast<const GeoShape*>(node) )
        return "GeoShape";
    if ( dynamic_cast<const GeoMaterial*>(node) )
        return "GeoMaterial";
    if ( dynamic_cast<const GeoTransform*>(node) )
        return "GeoTransform";
    return "UnidentifiedNode";
}
void LogvolProcessor::process(const DOMElement *element, GmxUtil &gmxUtil, GeoNodeList &toAdd) {
  GeoLogVolPtr lv{};
  GeoIntrusivePtr<GeoNameTag> nameTag_physChildVolName{};//USed for "sensitive" PhysVols 
  GeoIntrusivePtr<GeoNameTag> nameTag_physVolName{};//Actually the logVol name, which gets used for the PhysVols if they have "named" attribute (and aren't sensitive)

  gmxUtil.positionIndex.incrementLevel();

  // get the name of the LogVol
  XMLCh * name_tmp = XMLString::transcode("name");
  char *name2release = XMLString::transcode(element->getAttribute(name_tmp));
  string name(name2release);
  gmxUtil.positionIndex.addToLevelMap(name,gmxUtil.positionIndex.level());
  XMLString::release(&name2release);
  XMLString::release(&name_tmp);

  // get the value for the "named" option;
  // if "true", add a GeoNameTag to the GeoModel tree
  XMLCh * named_tmp = XMLString::transcode("named");
  char *toRelease2 = XMLString::transcode(element->getAttribute(named_tmp));
  string named(toRelease2);
  XMLString::release(&toRelease2);
  XMLString::release(&named_tmp);
  bool isNamed = bool(named.compare(string("true")) == 0);
  
  // get the value for the "identifier" option;
  // if "true", add a GeoIdentifierTag to the GeoModel tree
  XMLCh * id_tmp = XMLString::transcode("identifier");
  char *toRelease3 = XMLString::transcode(element->getAttribute(id_tmp));
  string idStr(toRelease3);
  XMLString::release(&toRelease3);
  XMLString::release(&id_tmp);
  bool hasIdentifier = bool(idStr.compare(string("true")) == 0);

  //

  XMLCh * envelope_tmp = XMLString::transcode("envelope");
  char *env = XMLString::transcode(element->getAttribute(envelope_tmp));
  string envelope(env);
  bool is_envelope=(envelope.compare(string("true"))==0);
  XMLString::release(&env);
  
  XMLCh * sensitive_tmp = XMLString::transcode("sensitive");
  bool sensitive = element->hasAttribute(sensitive_tmp);
  XMLString::release(&sensitive_tmp);

//
//    Look for the logvol in the map; if not yet there, add it
//
  map<string, LogVolStore>::iterator entry;
  if ((entry = m_map.find(name)) == m_map.end()) { // Not in registry; make a new item
    //
    //    Name
    //   
    LogVolStore* store{&m_map[name]};
    if(isNamed) {
        nameTag_physVolName = nameTag(name);
        store->name = nameTag_physVolName;
    }   
    //
    //    Get the shape.
    //
    DOMDocument *doc = element->getOwnerDocument();
    XMLCh * shape_tmp = XMLString::transcode("shape");
    const XMLCh *shape = element->getAttribute(shape_tmp);
    DOMElement *refShape = doc->getElementById(shape);
    // Check it is a shape... its parent should be a <shapes>. DTD cannot do this for us.
    DOMNode *parent = refShape->getParentNode();
    if (XMLString::compareIString(parent->getNodeName(), XMLString::transcode("shapes")) != 0) {
        char* shape_s = XMLString::transcode (shape);
        THROW_EXCEPTION("Processing logvol " << name <<
            ". Error in gmx file. An IDREF for a logvol shape did not refer to a shape.\n" <<
            "Shape ref was " << shape_s << "; exiting");
        XMLString::release (&shape_s);
    }
    //
    //    What sort of shape?
    //
    name2release = XMLString::transcode(refShape->getNodeName());
    string shapeType(name2release);
    XMLString::release(&name2release);
    XMLString::release(&shape_tmp);

    GeoIntrusivePtr<const GeoShape> shGeo = dynamic_pointer_cast<const GeoShape>(gmxUtil.geoItemRegistry.find(shapeType)->process(refShape, gmxUtil));
    //
    //    Get the material
    //
    XMLCh * material_tmp = XMLString::transcode("material");
    const XMLCh *material = element->getAttribute(material_tmp);
    DOMElement *refMaterial = doc->getElementById(material);
    // Check it is a material... its parent should be a <materials>. DTD cannot do this for us.
    parent = refMaterial->getParentNode();
    XMLCh * materials_tmp = XMLString::transcode("materials");
    if (XMLString::compareIString(parent->getNodeName(), materials_tmp) != 0) {
        char* material_s = XMLString::transcode (material);
        std::string materialName{material_s};
        XMLString::release (&material_s);
        THROW_EXCEPTION("Processing logvol " << name <<
            ". Error in gmx file. An IDREF for a logvol material did not refer to a material.\n" <<
            "Material ref was " << materialName << "; exiting");
    }
    std::string nam_mat= XMLString::transcode(material);

    GeoIntrusivePtr<const GeoMaterial> matGeo{};

    if (gmxUtil.matManager) {
        if (!gmxUtil.matManager->isMaterialDefined(nam_mat)) {
            GeoIntrusivePtr<GeoMaterial> tempMat=dynamic_pointer_cast<GeoMaterial>(gmxUtil.tagHandler.material.process(refMaterial, gmxUtil));
            // we let GMX create the material and store it in the MM
            gmxUtil.matManager->addMaterial(tempMat);
        } matGeo = gmxUtil.matManager->getMaterial(nam_mat);
    } else {
        matGeo = dynamic_pointer_cast<const GeoMaterial>(gmxUtil.tagHandler.material.process(refMaterial, gmxUtil));
    }

    if (!matGeo) {
        THROW_EXCEPTION("Cannot process "<<nam_mat);
    }    
    //
    //    Make the LogVol and add it to the map ready for next time
    //
    lv = make_intrusive<GeoLogVol>(name, shGeo, matGeo);
    store->logVol = lv;

    XMLString::release(&material_tmp);
    XMLString::release(&materials_tmp);
  }
  else { // Already in the registry; use it.
    //msglog << MSG::DEBUG << "LogVol w/ name '" << name << "' already present, picking it from cache..." << endmsg;
    if(isNamed) {
        nameTag_physVolName = entry->second.name;
    }
    lv = entry->second.logVol;
  }

  //
  //    Process the logvol children (side effect: sets formulae for indexes before calculating them)
  //
  // RMB: Note -- here the code looks for "children of the LogVol" but this is not true: they are PhysVol children. In fact, they are later added to the newly created PhysVol... needs to be clearified/updated
  GeoNodeList childrenAdd;
  for (DOMNode *child = element->getFirstChild(); child != 0; child = child->getNextSibling()) {
      if (child->getNodeType() == DOMNode::ELEMENT_NODE) {
        DOMElement *el = dynamic_cast<DOMElement *> (child);
        name2release = XMLString::transcode(el->getNodeName());
        string name(name2release);
        XMLString::release(&name2release);
        gmxUtil.processorRegistry.find(name)->process(el, gmxUtil, childrenAdd);
      }
  }
  //
  //   Make a list of things to be added
  //
  if(isNamed) {
      if(!sensitive) toAdd.push_back(nameTag_physVolName);//If sensitive, it gets a different name in a moment...
  }

  int sensId = 0;
  map<string, int> index;
  if (sensitive) {
    gmxUtil.positionIndex.setCopyNo(m_map[name].id++);
    gmxUtil.positionIndex.indices(index, gmxUtil.eval);
    sensId = gmxUtil.gmxInterface().sensorId(index);
    std::string newName = name;
    for(auto index_i:index){
            newName.append("_");
            newName.append(index_i.first);
            newName.append("_");
            newName.append(std::to_string(index_i.second));
    }
    nameTag_physChildVolName = nameTag(newName);//Make sensitive always have a name, to extra Id information from
    toAdd.push_back(nameTag_physChildVolName);
    if(hasIdentifier) { //TODO: check if all "sensitive" volumes must have an identifier. If that's the case, then we can remove this "if" here
        toAdd.push_back(geoId(sensId));
    }
  }
  else {
      if(hasIdentifier) {
          toAdd.push_back(geoId(m_map[name].id)); // Normal copy number
          gmxUtil.positionIndex.setCopyNo(m_map[name].id++);
      }
  }

  //
  //    Make a new PhysVol/FullPhysVol and add everything to it, then add it to the list of things for my caller to add
  //
  
  // get the value for the "alignable" option
  XMLCh * alignable_tmp = XMLString::transcode("alignable");
  char *toRelease = XMLString::transcode(element->getAttribute(alignable_tmp));
  string alignable(toRelease);
  XMLString::release(&toRelease);
  XMLString::release(&alignable_tmp);
  

  if (sensitive || (alignable.compare(string("true")) == 0)) {
    //msglog << MSG::DEBUG << "Handling a FullPhysVol (i.e., an 'alignable' or 'sensitive' volume) ..." << endmsg;
    GeoIntrusivePtr<GeoFullPhysVol> pv = make_intrusive<GeoFullPhysVol>(cacheVolume(lv));
    if (is_envelope) GeoVolumeTagCatalog::VolumeTagCatalog()->addTaggedVolume("Envelope",name,pv);
    for (const auto& node : childrenAdd) {
	     pv->add(node);
    }
    toAdd.push_back(pv); // NB: the *PV is third item added, so reference as toAdd[2].
    //
    //    Add sensitive volumes to detector manager via GmxInterface
    //
    if (sensitive) {
      XMLCh * sensitive_tmp = XMLString::transcode("sensitive");
      name2release = XMLString::transcode(element->getAttribute(sensitive_tmp));
      string sensitiveName(name2release);
      XMLString::release(&name2release);
      XMLString::release(&sensitive_tmp);
	    //splitting sensors where we would like multiple DetectorElements per GeoVFullPhysVol (e.g.ITk Strips)
      XMLCh * splitLevel_tmp = XMLString::transcode("splitLevel");
      bool split = element->hasAttribute(splitLevel_tmp);
      char* splitString;
	    int splitLevel = 1;
	    if (split) {
        splitString = XMLString::transcode(element->getAttribute(splitLevel_tmp));
        splitLevel = gmxUtil.evaluate(splitString);
        XMLString::release(&splitString);
        for(int i=0;i<splitLevel;i++){
          std::string field = "eta_module";//eventually specify in Xml the field to split in?
          std::pair<std::string,int> extraIndex(field,i);
          gmxUtil.gmxInterface().addSplitSensor(sensitiveName, index,extraIndex, sensId, 
                                                dynamic_pointer_cast<GeoVFullPhysVol> (pv),splitLevel);
        }
	    }
	    else gmxUtil.gmxInterface().addSensor(sensitiveName, index, sensId, 
                                             dynamic_pointer_cast<GeoVFullPhysVol>(pv));
        XMLString::release(&splitLevel_tmp);
    }
  }
  else {
      //msglog << MSG::DEBUG << "Handling a standard PhysVol..." << endmsg;
      GeoIntrusivePtr<GeoPhysVol> pv = make_intrusive<GeoPhysVol>(cacheVolume(lv));
      if (is_envelope) GeoVolumeTagCatalog::VolumeTagCatalog()->addTaggedVolume("Envelope",name,pv);
      //msglog << MSG::DEBUG << "Now, looping over all the children of the LogVol (in the GMX meaning)..." << endmsg; 
      for (const auto & node : childrenAdd) {
        pv->add(node);
        //msglog << MSG::DEBUG << "LVProc, PV child: " << *node << " -- " << getNodeType(*node) << endmsg;
      }
      //msglog << MSG::DEBUG << "End of loop over children." << endmsg;
      toAdd.push_back(cacheVolume(pv));
  }
  gmxUtil.positionIndex.decrementLevel();
  if(gmxUtil.positionIndex.level()==-1){ //should mean that we are at the end of processing the geometry
    gmxUtil.positionIndex.printLevelMap();
  }
  return;
}

void LogvolProcessor::zeroId(const xercesc::DOMElement *element) {

  XMLCh * name_tmp = XMLString::transcode("name");
  char *name2release = XMLString::transcode(element->getAttribute(name_tmp));
  string name(name2release);
  XMLString::release(&name2release);
  XMLString::release(&name_tmp);
  //
  //    Look for the logvol in the map; if not yet there, add it
  //
  map<string, LogVolStore>::iterator entry;
  if ((entry = m_map.find(name)) != m_map.end()) {
    entry->second.id = 0;
  }
  /* else: Not an error: it is usually just about to be made with id = 0; no action needed. */
}