//
// ********************************************************************
// * License and Disclaimer                                           *
// *                                                                  *
// * The  Geant4 software  is  copyright of the Copyright Holders  of *
// * the Geant4 Collaboration.  It is provided  under  the terms  and *
// * conditions of the Geant4 Software License,  included in the file *
// * LICENSE and available at  http://cern.ch/geant4/license .  These *
// * include a list of copyright holders.                             *
// *                                                                  *
// * Neither the authors of this software system, nor their employing *
// * institutes,nor the agencies providing financial support for this *
// * work  make  any representation or  warranty, express or implied, *
// * regarding  this  software system or assume any liability for its *
// * use.  Please see the license in the file  LICENSE  and URL above *
// * for the full disclaimer and the limitation of liability.         *
// *                                                                  *
// * This  code  implementation is the result of  the  scientific and *
// * technical work of the GEANT4 collaboration.                      *
// * By using,  copying,  modifying or  distributing the software (or *
// * any work based  on the software)  you  agree  to acknowledge its *
// * use  in  resulting  scientific  publications,  and indicate your *
// * acceptance of all terms of the Geant4 Software license.          *
// ********************************************************************
//
// G4GDMLRead implementation
//
// Author: Zoltan Torzsok, November 2007
// --------------------------------------------------------------------

#include "globals.hh"

#include "G4GDMLRead.hh"

#include "G4UnitsTable.hh"
#include "G4Element.hh"
#include "G4Material.hh"
#include "G4SolidStore.hh"
#include "G4LogicalVolumeStore.hh"
#include "G4PhysicalVolumeStore.hh"
#include "G4EnvironmentUtils.hh"
#include "G4Exception.hh"

// --------------------------------------------------------------------
G4GDMLRead::G4GDMLRead()
{
  // Make sure units are defined.
  G4UnitDefinition::GetUnitsTable();
}

// --------------------------------------------------------------------
G4GDMLRead::~G4GDMLRead()
{
}

// --------------------------------------------------------------------
G4String G4GDMLRead::Transcode(const XMLCh* const toTranscode)
{
  char* char_str = xercesc::XMLString::transcode(toTranscode);
  G4String my_str(char_str);
  xercesc::XMLString::release(&char_str);
  return my_str;
}

// --------------------------------------------------------------------
void G4GDMLRead::OverlapCheck(G4bool flag)
{
  check = flag;
}

// --------------------------------------------------------------------
G4String G4GDMLRead::GenerateName(const G4String& nameIn, G4bool strip)
{
  G4String nameOut(nameIn);

  if(inLoop > 0)
  {
    nameOut = eval.SolveBrackets(nameOut);
  }
  if(strip)
  {
    StripName(nameOut);
  }

  return nameOut;
}

// --------------------------------------------------------------------
void G4GDMLRead::GeneratePhysvolName(const G4String& nameIn,
                                     G4VPhysicalVolume* physvol)
{
  G4String nameOut(nameIn);

  if(nameIn.empty())
  {
    std::stringstream stream;
    stream << physvol->GetLogicalVolume()->GetName() << "_PV";
    nameOut = stream.str();
  }
  nameOut = eval.SolveBrackets(nameOut);

  physvol->SetName(nameOut);
}

// --------------------------------------------------------------------
G4String G4GDMLRead::Strip(const G4String& name) const
{
  G4String sname(name);
  StripName(sname);
  return sname;
}

// --------------------------------------------------------------------
void G4GDMLRead::StripName(G4String& name) const
{
  auto idx = name.find("0x");
  if(idx != G4String::npos)
  {
    name.erase(idx);
  }
}

// --------------------------------------------------------------------
void G4GDMLRead::StripNames() const
{
  // Strips off names of volumes, solids elements and materials from possible
  // reference pointers or IDs attached to their original identifiers.

  G4PhysicalVolumeStore* pvols     = G4PhysicalVolumeStore::GetInstance();
  G4LogicalVolumeStore* lvols      = G4LogicalVolumeStore::GetInstance();
  G4SolidStore* solids             = G4SolidStore::GetInstance();
  const G4ElementTable* elements   = G4Element::GetElementTable();
  const G4MaterialTable* materials = G4Material::GetMaterialTable();

  G4cout << "Stripping off GDML names of materials, solids and volumes ..."
         << G4endl;

  G4String sname;
  std::size_t i;

  // Solids...
  //
  for(i = 0; i < solids->size(); ++i)
  {
    G4VSolid* psol = (*solids)[i];
    sname          = psol->GetName();
    StripName(sname);
    psol->SetName(sname);
  }
  solids->UpdateMap();

  // Logical volumes...
  //
  for(i = 0; i < lvols->size(); ++i)
  {
    G4LogicalVolume* lvol = (*lvols)[i];
    sname                 = lvol->GetName();
    StripName(sname);
    lvol->SetName(sname);
  }
  lvols->UpdateMap();

  // Physical volumes...
  //
  for(i = 0; i < pvols->size(); ++i)
  {
    G4VPhysicalVolume* pvol = (*pvols)[i];
    sname                   = pvol->GetName();
    StripName(sname);
    pvol->SetName(sname);
  }
  pvols->UpdateMap();

  // Materials...
  //
  for(i = 0; i < materials->size(); ++i)
  {
    G4Material* pmat = (*materials)[i];
    sname            = pmat->GetName();
    StripName(sname);
    pmat->SetName(sname);
  }

  // Elements...
  //
  for(i = 0; i < elements->size(); ++i)
  {
    G4Element* pelm = (*elements)[i];
    sname           = pelm->GetName();
    StripName(sname);
    pelm->SetName(sname);
  }
}

// --------------------------------------------------------------------
void G4GDMLRead::LoopRead( const xercesc::DOMElement* const element,
  void (G4GDMLRead::*func)(const xercesc::DOMElement* const))
{
  G4String var;
  G4String from;
  G4String to;
  G4String step;

  const xercesc::DOMNamedNodeMap* const attributes = element->getAttributes();
  XMLSize_t attributeCount                         = attributes->getLength();

  for(XMLSize_t attribute_index = 0; attribute_index < attributeCount;
      ++attribute_index)
  {
    xercesc::DOMNode* attribute_node = attributes->item(attribute_index);

    if(attribute_node->getNodeType() != xercesc::DOMNode::ATTRIBUTE_NODE)
    {
      continue;
    }

    const xercesc::DOMAttr* const attribute =
      dynamic_cast<xercesc::DOMAttr*>(attribute_node);
    if(!attribute)
    {
      G4Exception("G4GDMLRead::LoopRead()", "InvalidRead", FatalException,
                  "No attribute found!");
      return;
    }
    const G4String attribute_name  = Transcode(attribute->getName());
    const G4String attribute_value = Transcode(attribute->getValue());

    if(attribute_name == "for")
    {
      var = attribute_value;
    }
    else if(attribute_name == "from")
    {
      from = attribute_value;
    }
    else if(attribute_name == "to")
    {
      to = attribute_value;
    }
    else if(attribute_name == "step")
    {
      step = attribute_value;
    }
  }

  if(var.empty())
  {
    G4Exception("G4GDMLRead::loopRead()", "InvalidRead", FatalException,
                "No variable is determined for loop!");
  }

  if(!eval.IsVariable(var))
  {
    G4Exception("G4GDMLRead::loopRead()", "InvalidRead", FatalException,
                "Variable is not defined in loop!");
  }

  G4int _var  = eval.EvaluateInteger(var);
  G4int _from = eval.EvaluateInteger(from);
  G4int _to   = eval.EvaluateInteger(to);
  G4int _step = eval.EvaluateInteger(step);

  if(!from.empty())
  {
    _var = _from;
  }

  if((_from < _to) && (_step <= 0))
  {
    G4Exception("G4GDMLRead::loopRead()", "InvalidRead", FatalException,
                "Infinite loop!");
  }
  if((_from > _to) && (_step >= 0))
  {
    G4Exception("G4GDMLRead::loopRead()", "InvalidRead", FatalException,
                "Infinite loop!");
  }

  ++inLoop;

  while(_var <= _to)
  {
    eval.SetVariable(var, _var);
    (this->*func)(element);
    _var += _step;
    ++loopCount;
  }

  --inLoop;
  if(!inLoop)
  {
    loopCount = 0;
  }
}

// --------------------------------------------------------------------
G4GDMLAuxStructType G4GDMLRead::AuxiliaryRead(
  const xercesc::DOMElement* const auxiliaryElement)
{
  G4GDMLAuxStructType auxstruct = { "", "", "", 0 };
  G4GDMLAuxListType* auxList    = nullptr;

  const xercesc::DOMNamedNodeMap* const attributes =
    auxiliaryElement->getAttributes();
  XMLSize_t attributeCount = attributes->getLength();

  for(XMLSize_t attribute_index = 0; attribute_index < attributeCount;
      ++attribute_index)
  {
    xercesc::DOMNode* attribute_node = attributes->item(attribute_index);

    if(attribute_node->getNodeType() != xercesc::DOMNode::ATTRIBUTE_NODE)
    {
      continue;
    }

    const xercesc::DOMAttr* const attribute =
      dynamic_cast<xercesc::DOMAttr*>(attribute_node);
    if(!attribute)
    {
      G4Exception("G4GDMLRead::AuxiliaryRead()", "InvalidRead", FatalException,
                  "No attribute found!");
      return auxstruct;
    }
    const G4String attName  = Transcode(attribute->getName());
    const G4String attValue = Transcode(attribute->getValue());

    if(attName == "auxtype")
    {
      auxstruct.type = attValue;
    }
    else if(attName == "auxvalue")
    {
      auxstruct.value = attValue;
    }
    else if(attName == "auxunit")
    {
      auxstruct.unit = attValue;
    }
  }

  for(xercesc::DOMNode* iter = auxiliaryElement->getFirstChild();
                        iter != nullptr; iter = iter->getNextSibling())
  {
    if(iter->getNodeType() != xercesc::DOMNode::ELEMENT_NODE)
    {
      continue;
    }

    const xercesc::DOMElement* const child =
      dynamic_cast<xercesc::DOMElement*>(iter);
    if(!child)
    {
      G4Exception("G4GDMLRead::AuxiliaryRead()", "InvalidRead", FatalException,
                  "No child found!");
      break;
    }
    const G4String tag = Transcode(child->getTagName());

    if(tag == "auxiliary")
    {
      if(!auxList)
      {
        auxList = new G4GDMLAuxListType;
      }
      auxList->push_back(AuxiliaryRead(child));
    }
  }

  if(auxList)
  {
    auxstruct.auxList = auxList;
  }

  return auxstruct;
}

// --------------------------------------------------------------------
void G4GDMLRead::UserinfoRead(const xercesc::DOMElement* const userinfoElement)
{
#ifdef G4VERBOSE
  G4cout << "G4GDML: Reading userinfo..." << G4endl;
#endif
  for(xercesc::DOMNode* iter = userinfoElement->getFirstChild();
                        iter != nullptr; iter = iter->getNextSibling())
  {
    if(iter->getNodeType() != xercesc::DOMNode::ELEMENT_NODE)
    {
      continue;
    }

    const xercesc::DOMElement* const child =
      dynamic_cast<xercesc::DOMElement*>(iter);
    if(!child)
    {
      G4Exception("G4GDMLRead::UserinfoRead()", "InvalidRead", FatalException,
                  "No child found!");
      return;
    }
    const G4String tag = Transcode(child->getTagName());

    if(tag == "auxiliary")
    {
      auxGlobalList.push_back(AuxiliaryRead(child));
    }
    else
    {
      G4String error_msg = "Unknown tag in structure: " + tag;
      G4Exception("G4GDMLRead::UserinfoRead()", "ReadError", FatalException,
                  error_msg);
    }
  }
}

// --------------------------------------------------------------------
void G4GDMLRead::ExtensionRead(const xercesc::DOMElement* const)
{
  G4String error_msg = "No handle to user-code for parsing extensions!";
  G4Exception("G4GDMLRead::ExtensionRead()", "NotImplemented", JustWarning,
              error_msg);
}

// --------------------------------------------------------------------
const G4String& G4GDMLRead::GetSchemaFile() const
{
  return schema;
}

// --------------------------------------------------------------------
void G4GDMLRead::SetSchemaFile(const G4String& schemaFile)
{
  schema = schemaFile;
}

// --------------------------------------------------------------------
void G4GDMLRead::Read(const G4String& fileName, G4bool validation,
                      G4bool isModule, G4bool strip)
{
  dostrip = strip;
#ifdef G4VERBOSE
  if(isModule)
  {
    G4cout << "G4GDML: Reading module '" << fileName << "'..." << G4endl;
  }
  else
  {
    G4cout << "G4GDML: Reading '" << fileName << "'..." << G4endl;
  }
#endif
  inLoop   = 0;
  validate = validation;

  xercesc::ErrorHandler* handler   = new G4GDMLErrorHandler(!validate);
  xercesc::XercesDOMParser* parser = new xercesc::XercesDOMParser;

  if(validate)
  {
    parser->setValidationScheme(xercesc::XercesDOMParser::Val_Always);
   
    // Load alternative schema path if specified by user in
    // 1. environment variable, or
    // 2. `schema` data member, or
    // Will fall back to default validation otherwise (`xsi:noNamespaceSchemaLocation` in input file)
    if(auto schemaPath = G4GetEnv<G4String>("G4GDML_SCHEMA_FILE", schema); schemaPath != "")
    {
      // Pre-parse grammar to check it's present/valid
      if(parser->loadGrammar(schemaPath.c_str(), xercesc::Grammar::SchemaGrammarType, true) != nullptr)
      {
        G4cout << "G4GDML: Loaded alternative schema URI: " << schemaPath << G4endl;
      }
      else 
      {
        G4Exception("G4GDMLRead::Read()",
                    "InvalidGDMLSchemaFile",
                    FatalException,
                    G4String("Failed to load/parse schema file '" + schemaPath + "'").c_str());
      }
      parser->useCachedGrammarInParse(true);
      // If the schema has been set manually then we want to ignore path set in input file, if any
      parser->setExternalNoNamespaceSchemaLocation(schemaPath.c_str());
    }
  }
  parser->setValidationSchemaFullChecking(validate);
  parser->setCreateEntityReferenceNodes(false);
  // Entities will be automatically resolved by Xerces

  parser->setDoNamespaces(true);
  parser->setDoSchema(validate);
  parser->setErrorHandler(handler);

  try
  {
    parser->parse(fileName.c_str());
  } catch(const xercesc::XMLException& e)
  {
    G4cout << "G4GDML: " << Transcode(e.getMessage()) << G4endl;
  } catch(const xercesc::DOMException& e)
  {
    G4cout << "G4GDML: " << Transcode(e.getMessage()) << G4endl;
  }

  xercesc::DOMDocument* doc = parser->getDocument();

  if(doc == nullptr)
  {
    G4String error_msg = "Unable to open document: " + fileName;
    G4Exception("G4GDMLRead::Read()", "InvalidRead", FatalException, error_msg);
    return;
  }
  xercesc::DOMElement* element = doc->getDocumentElement();

  if(element == nullptr )
  {
    std::ostringstream message;
    message << "ERROR - Empty document or unable to validate schema!" << G4endl
            << "        Check Internet connection is ON in case of schema"
            << G4endl
            << "        validation enabled and location defined as URL in"
            << G4endl << "        the GDML file - " << fileName
            << " - being imported!" << G4endl
            << "        Otherwise, verify GDML schema server is reachable!";
    G4Exception("G4GDMLRead::Read()", "InvalidRead", FatalException, message);
    return;
  }

  for(xercesc::DOMNode* iter = element->getFirstChild(); iter != nullptr;
      iter                   = iter->getNextSibling())
  {
    if(iter->getNodeType() != xercesc::DOMNode::ELEMENT_NODE)
    {
      continue;
    }

    const xercesc::DOMElement* const child =
      dynamic_cast<xercesc::DOMElement*>(iter);
    if(child == nullptr)
    {
      G4Exception("G4GDMLRead::Read()", "InvalidRead", FatalException,
                  "No child found!");
      return;
    }
    const G4String tag = Transcode(child->getTagName());

    if(tag == "define")
    {
      DefineRead(child);
    }
    else if(tag == "materials")
    {
      MaterialsRead(child);
    }
    else if(tag == "solids")
    {
      SolidsRead(child);
    }
    else if(tag == "setup")
    {
      SetupRead(child);
    }
    else if(tag == "structure")
    {
      StructureRead(child);
    }
    else if(tag == "userinfo")
    {
      UserinfoRead(child);
    }
    else if(tag == "extension")
    {
      ExtensionRead(child);
    }
    else
    {
      G4String error_msg = "Unknown tag in gdml: " + tag;
      G4Exception("G4GDMLRead::Read()", "InvalidRead", FatalException,
                  error_msg);
    }
  }

  delete parser;
  delete handler;

  if(isModule)
  {
#ifdef G4VERBOSE
    G4cout << "G4GDML: Reading module '" << fileName << "' done!" << G4endl;
#endif
  }
  else
  {
    G4cout << "G4GDML: Reading '" << fileName << "' done!" << G4endl;
    if(strip)
    {
      StripNames();
    }
  }
}

// --------------------------------------------------------------------
const G4GDMLAuxListType* G4GDMLRead::GetAuxList() const
{
  return &auxGlobalList;
}
