//
// ********************************************************************
// * 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.          *
// ********************************************************************
//
//
// Global environment utility functions:
//
// G4GetEnv<T>
//      Simplifies getting environment variables
//      Automatic conversion to non-string types
//      Records the values used from the environment
// G4GetDataEnv
//      For data library paths
//      Will issue a G4Exception if not set
// G4PrintEnv
//      Provide a way for users to determine (and log) the environment
//      variables were used as settings in simulation
//
// ---------------------------------------------------------------------------
#ifndef G4ENVIRONMENTUTILS_HH_
#define G4ENVIRONMENTUTILS_HH_

#include <cstdlib>
#include <string>
#include <sstream>
#include <map>
#include <iostream>
#include <iomanip>
#include <mutex>

#include "G4ios.hh"
#include "G4String.hh"
#include "G4Exception.hh"
#include "G4ExceptionSeverity.hh"

// ---------------------------------------------------------------------------

class G4EnvSettings
{
  // Static singleton class storing environment variables and
  // their values that were used by Geant4 in the simulation

  public:
    typedef std::string                         string_t;
    typedef std::map<string_t, string_t>        env_map_t;
    typedef std::pair<string_t, string_t>       env_pair_t;

  public:
    static G4EnvSettings* GetInstance()
    {
        static G4EnvSettings* _instance = new G4EnvSettings();
        return _instance;
    }

  public:
    template <typename _Tp>
    void insert(const std::string& env_id, _Tp val)
    {
      std::stringstream ss;
      ss << val;
      // lock for MT mode, use C++ type not Geant4 because this file
      // is included by the those headers
      static std::mutex _mutex;
       _mutex.lock();
       m_env.insert(env_pair_t(env_id, ss.str()));
       _mutex.unlock();
    }

    const env_map_t& get() const { return m_env; }

    friend std::ostream& operator<<(std::ostream& os, const G4EnvSettings& env)
    {
      std::stringstream filler;
      filler.fill('#');
      filler << std::setw(90) << "";
      std::stringstream ss;
      ss << filler.str() << "\n# Environment settings:\n";
      for(const auto& itr : env.get())
      {
        ss << "# " << std::setw(35) << std::right << itr.first
           << "\t = \t" << std::left << itr.second << "\n";
      }
      ss << filler.str();
      os << ss.str() << std::endl;
      return os;
    }

  private:
    env_map_t m_env;
};

// ---------------------------------------------------------------------------
//  Use this function to get an environment variable setting +
//  a default if not defined, e.g.
//      int num_threads =
//          G4GetEnv<int>("G4FORCENUMBEROFTHREADS",
//                        std::thread::hardware_concurrency());
template <typename _Tp>
_Tp G4GetEnv(const std::string& env_id, _Tp _default = _Tp())
{
  char* env_var = std::getenv(env_id.c_str());
  if(env_var)
  {
    std::string str_var = std::string(env_var);
    std::istringstream iss(str_var);
    _Tp var = _Tp();
    iss >> var;
    // record value defined by environment
    G4EnvSettings::GetInstance()->insert<_Tp>(env_id, var);
    return var;
  }
  // record default value
  G4EnvSettings::GetInstance()->insert<_Tp>(env_id, _default);

  // return default if not specified in environment
  return _default;
}

// ---------------------------------------------------------------------------
//  Use this function to get an environment variable setting +
//  a default if not defined, e.g.
//      int num_threads =
//          GetEnv<int>("FORCENUMBEROFTHREADS",
//                      std::thread::hardware_concurrency());
template <> inline
G4bool G4GetEnv(const std::string& env_id, bool _default)
{
  char* env_var = std::getenv(env_id.c_str());
  if(env_var)
  {
    // record value defined by environment
    G4EnvSettings::GetInstance()->insert<bool>(env_id, true);
    return true;
  }
  // record default value
  G4EnvSettings::GetInstance()->insert<bool>(env_id, false);

  // return default if not specified in environment
  return _default;
}

// ---------------------------------------------------------------------------
//  Use this function to get an environment variable setting +
//  a default if not defined and a message about the setting, e.g.
//      int num_threads =
//          G4GetEnv<int>("G4FORCENUMBEROFTHREADS",
//                        std::thread::hardware_concurrency(),
//                        "Forcing number of threads");
template <typename _Tp>
_Tp G4GetEnv(const std::string& env_id, _Tp _default, const std::string& msg)
{
  char* env_var = std::getenv(env_id.c_str());
  if(env_var)
  {
    std::string str_var = std::string(env_var);
    std::istringstream iss(str_var);
    _Tp var = _Tp();
    iss >> var;
    G4cout << "Environment variable \"" << env_id << "\" enabled with "
           << "value == " << var << ". " << msg << G4endl;
    // record value defined by environment
    G4EnvSettings::GetInstance()->insert<_Tp>(env_id, var);
    return var;
  }
  // record default value
  G4EnvSettings::GetInstance()->insert<_Tp>(env_id, _default);

  // return default if not specified in environment
  return _default;
}

// ---------------------------------------------------------------------------
//  Use this function to get a data directory environment variable setting +
//  and raise a G4Exception if the value is not set, e.g.
//
//      G4String filename = G4GetDataEnv("G4ENSDFSTATEDATA",
//                                       "G4NuclideTable", "PART70000",
//                                       FatalException,
//                                       "G4ENSDFSTATEDATA environment variable"
//                                       " must be set");
inline G4String
G4GetDataEnv(const std::string& env_id,
             const char* originOfException,
             const char* exceptionCode,
             G4ExceptionSeverity severity,
             const char* description)
{
    char* env_var = std::getenv(env_id.c_str());
    if(env_var)
    {
        std::string str_var = std::string(env_var);
        std::istringstream iss(str_var);
        G4String var = "";
        iss >> var;
        // record value defined by environment
        G4EnvSettings::GetInstance()->insert<G4String>(env_id, var);
        return var;
    }

    // issue an exception
    G4Exception(originOfException, exceptionCode, severity, description);

    // return default initialized
    return "";
}

// ---------------------------------------------------------------------------
// Use this function to print the environment
//
inline void G4PrintEnv(std::ostream& os = G4cout)
{
    os << (*G4EnvSettings::GetInstance());
}

//----------------------------------------------------------------------------//

#endif /* G4ENVIRONMENTUTILS_HH_ */
