//==========================================================================
//  AIDA Detector description implementation 
//--------------------------------------------------------------------------
// Copyright (C) Organisation europeenne pour la Recherche nucleaire (CERN)
// All rights reserved.
//
// For the licensing terms see $DD4hepINSTALL/LICENSE.
// For the list of contributors see $DD4hepINSTALL/doc/CREDITS.
//
// Author     : M.Frank
//
//==========================================================================
#ifndef DD4HEP_CONDITIONS_H
#define DD4HEP_CONDITIONS_H

// Framework include files
#include <DD4hep/IOV.h>
#include <DD4hep/Handle.h>
#include <DD4hep/OpaqueData.h>

// C/C++ include files
#include <vector>

/// Namespace for the AIDA detector description toolkit
namespace dd4hep {

  // Forward declarations
  class BasicGrammar;
  class DetElement;
  class Detector;

  /// Conditions internal namespace
  namespace detail  {
    class ConditionObject;
  }

  /// Main condition object handle.
  /**
   *  This objects allows access to the data block and
   *  the interval of validity for a single condition.
   *
   *  Note:
   *  Conditions may be shared between several DetElement objects.
   *  Hence, the back-link to the DetElement structure cannot be
   *  set - it would be ambiguous.
   *
   *  \author  M.Frank
   *  \version 1.0
   *  \ingroup DD4HEP_CONDITIONS
   */
  class Condition: public Handle<detail::ConditionObject> {
  public:
    /// Forward definition of the key type
    typedef unsigned long long int key_type;
    /// High part of the key identifies the detector element
    typedef unsigned int           detkey_type;
    /// Low part of the key identifies the item identifier
    typedef unsigned int           itemkey_type;
    /// Forward definition of the object properties
    typedef unsigned int           mask_type;

  public:
    /// Flags to steer the conditions conversion to string
    enum StringFlags  {
      WITH_IOV            =  1<<0,
      WITH_ADDRESS        =  1<<1,
      WITH_TYPE           =  1<<2,
      WITH_COMMENT        =  1<<4,
      WITH_DATATYPE       =  1<<5,
      WITH_DATA           =  1<<6,
      NO_NAME             =  1<<20,
      NONE
    };
    /// Flags to indicate the conditions type ans state
    enum ConditionState {
      INACTIVE            =  0,
      ACTIVE              =  1<<0,
      CHECKED             =  1<<2,
      DERIVED             =  1<<3,
      ONSTACK             =  1<<4,
      // Flags for specific conditions
      TEMPERATURE         =  1<<5,
      TEMPERATURE_DERIVED =  1<<6|DERIVED,
      PRESSURE            =  1<<7,
      PRESSURE_DERIVED    =  1<<8|DERIVED,
      ALIGNMENT_DELTA     =  1<<9,
      ALIGNMENT_DERIVED   =  1<<10|DERIVED,
      // Keep bit 10-15 for other generic types
      // Bit 16-31 is reserved for user classifications
      USER_FLAGS_FIRST    =  1<<16,
      USER_FLAGS_LAST     =  1<<31
    };
    /// Flags to indicate conditions item ranges (low word of the conditions key)
    enum ConditionItemRangeKeys {
      FIRST_ITEM_KEY      =  0x0U,
      LAST_ITEM_KEY       = ~0x0U
    };
    /// Flags to indicate conditions detector ranges (high word of the conditions key)
    enum ConditionDetectorRangeKeys {
      FIRST_DET_KEY       =  0x0U,
      LAST_DET_KEY        = ~0x0U
    };
    /// Flags to indicate global conditions ranges
    static constexpr unsigned long long int FIRST_KEY =  0x0ULL, LAST_KEY  = ~0x0ULL;

    /// Abstract base for processing callbacks to conditions objects
    /**
     *  \author  M.Frank
     *  \version 1.0
     *  \ingroup DD4HEP_CONDITIONS
     */
    class Processor {
    protected:
      /// Move constructor
      Processor(Processor&& copy) = default;
      /// Copy constructor
      Processor(const Processor& copy) = default;
      /// Assignment operator
      Processor& operator=(const Processor& copy) = delete;
    public:
      /// Default constructor
      Processor() = default;
      /// Default destructor
      virtual ~Processor() = default;
      /// Processing callback
      virtual int process(Condition c)  const = 0;
      /// Conditions callback for object processing
      virtual int operator()(Condition c)  const
      {  return this->process(c);                   }
      /// Conditions callback for object processing in maps
      virtual int operator()(const std::pair<Condition::key_type,Condition>& e)  const
      {  return this->process(e.second);            }
    };

    /// Default constructor
    Condition() = default;
    /// Move constructor
    Condition(Condition&& c) = default;
    /// Copy constructor
    Condition(const Condition& c) = default;
    /// Initializing constructor
    Condition(Object* p);
    /// Constructor to be used when reading the already parsed object
    template <typename Q> Condition(const Handle<Q>& e);
    /// Initializing constructor for a pure, undecorated conditions object
    Condition(key_type hash_key);
    /// Initializing constructor for a pure, undecorated conditions object
    Condition(const std::string& name, const std::string& type);
    /// Initializing constructor for a pure, undecorated conditions object with payload buffer
    Condition(const std::string& name, const std::string& type, size_t memory);
    /// Assignment move operator
    Condition& operator=(Condition&& c) = default;
    /// Assignment copy operator
    Condition& operator=(const Condition& c) = default;

    /// Output method
    std::string str(int with_data=WITH_IOV|WITH_ADDRESS|WITH_DATATYPE)  const;

    /** Data block (bound type)         */
    /// Access the data type
    int dataType()  const;

    /** Interval of validity            */
    /// Access the IOV type
    const IOVType& iovType()  const;
    /// Access the IOV block
    const IOV& iov()  const;

    /** Conditions identification using integer keys.   */
    /// Hash identifier
    key_type key()  const;
    /// DetElement part of the identifier
    detkey_type  detector_key()  const;
    /// Item part of the identifier
    itemkey_type item_key()  const;

    /** Direct data items in string form */
#if defined(DD4HEP_CONDITIONS_DEBUG) || !defined(DD4HEP_MINIMAL_CONDITIONS)
    /// Access the type field of the condition
    const std::string& type()  const;
    /// Access the value field of the condition as a string
    const std::string& value()  const;
    /// Access the comment field of the condition
    const std::string& comment()  const;
    /// Access the address string [e.g. database identifier]
    const std::string& address()  const;
#endif
    /// Flag operations: Get condition flags
    mask_type flags()  const;
    /// Flag operations: Set a conditions flag
    void setFlag(mask_type option);
    /// Flag operations: UN-Set a conditions flag
    void unFlag(mask_type option);
    /// Flag operations: Test for a given a conditions flag
    bool testFlag(mask_type option) const;

    /** Conditions meta-data and handling of the data binding  */
    /// Access the opaque data block
    OpaqueDataBlock& data()  const;
    /// Access to the type information
    const std::type_info& typeInfo() const;
    /// Access to the grammar type
    const BasicGrammar& descriptor() const;
    /// Check if object is already bound....
    bool is_bound()  const  {  return isValid() ? data().is_bound() : false;  }
    /** Construct conditions object and bind the data
     *
     *  Note: The type definition is possible exactly once.
     *  Any further rebindings MUST match the identical type.
     */
    template <typename T, typename... Args> T& construct(Args... args);
    /** Bind the data of the conditions object to a given format.
     *
     *  Note: The type definition is possible exactly once.
     *  Any further rebindings MUST match the identical type.
     */
    template <typename T> T& bind();
    /** Set and bind the data of the conditions object to a given format.
     *
     *  Note: The type definition is possible exactly once.
     *  Any further rebindings MUST match the identical type.
     */
    template <typename T> T& bind(const std::string& val);
    /// Generic getter. Specify the exact type, not a polymorph type
    template <typename T> T& get();
    /// Generic getter (const version). Specify the exact type, not a polymorph type
    template <typename T> const T& get() const;
    /// Generic getter. Resolves polymorph types. It is mandatory that the datatype is polymorph!
    template <typename T> T& as();
    /// Generic getter (const version). Resolves polymorph types. It is mandatory that the datatype is polymorph!
    template <typename T> const T& as() const;

    /// Allow to trace condition names from keys for debugging
    static int haveInventory(int value = -1);
  };

  /// Initializing constructor
  inline Condition::Condition(Condition::Object* p)
    : Handle<Condition::Object>(p)  {}

  /// Constructor to be used when reading the already parsed object
  template <typename Q> inline Condition::Condition(const Handle<Q>& e)
    : Handle<Condition::Object>(e) {}

  /// Construct conditions object and bind the data
  template <typename T, typename... Args> T& Condition::construct(Args... args)   {
    return data().construct<T,Args...>(args...);
  }
  /// Bind the data of the conditions object to a given format.
  template <typename T> inline T& Condition::bind()   {
    return data().bind<T>();
  }
  /// Bind the data of the conditions object to a given format and fill data from string representation.
  template <typename T> inline T& Condition::bind(const std::string& val)   {
    return data().bind<T>(val);
  }
  /// Generic getter. Specify the exact type, not a polymorph type
  template <typename T> inline T& Condition::get() {
    return data().get<T>();
  }
  /// Generic getter (const version). Specify the exact type, not a polymorph type
  template <typename T> inline const T& Condition::get() const {
    return data().get<T>();
  }    
  /// Generic getter. Specify the exact type, not a polymorph type
  template <typename T> inline T& Condition::as() {
    return data().as<T>();
  }
  /// Generic getter (const version). Specify the exact type, not a polymorph type
  template <typename T> inline const T& Condition::as() const {
    return data().as<T>();
  }
    
  /// Key definition to optimize ans simplyfy the access to conditions entities
  /**
   *  \author  M.Frank
   *  \version 1.0
   *  \ingroup DD4HEP_CONDITIONS
   */
  class ConditionKey  {
  public:
#if defined(DD4HEP_CONDITIONS_HAVE_NAME)
    /// Optional string identifier. Helps debugging a lot!
    std::string  name;
#endif
    /// Hashed key representation
    Condition::key_type     hash = 0;

    /// Helper union to interprete conditions keys
    /**
     *  \author  M.Frank
     *  \version 1.0
     *  \ingroup DD4HEP_CONDITIONS
     */
    union KeyMaker  {
      Condition::key_type  hash;
      /** Note: The memory layout is important here to have properly
       *        ordered maps. The detector key MUST be on the high end 
       *        of the resulting int64 'hash'.
       */
      struct {
        Condition::itemkey_type item_key;
        Condition::detkey_type  det_key;
      } values;
      KeyMaker()  {
        this->hash = 0;
      }
      KeyMaker(Condition::key_type k)  {
        this->hash = k;
      }
      KeyMaker(Condition::detkey_type det, Condition::itemkey_type item)  {
        this->values.det_key  = det;
        this->values.item_key = item;
      }
      /// Constructor from string
      KeyMaker(Condition::detkey_type det, const std::string& value);
      /// Constructor from string
      KeyMaker(DetElement detector, const std::string& value);
      /// Constructor from detector element and item sub-key
      KeyMaker(DetElement detector, Condition::itemkey_type item_key);
    };
    
    /// Compare keys by the hash value
    /**
     *  \author  M.Frank
     *  \version 1.0
     *  \ingroup DD4HEP_CONDITIONS
     */
    struct HashCompare {
      Condition::key_type key;
      HashCompare(Condition::key_type k) : key(k) {}
      bool operator==(const ConditionKey& k) const { return key==k.hash; }
    };
  public:
    /// Default constructor
    ConditionKey() = default;
    /// Constructor from fully qualified key
    ConditionKey(Condition::key_type key) : hash(key) {}
    /// Constructor from string
    ConditionKey(DetElement detector, const std::string& identifier);
    /// Constructor from detector element key and item sub-key
    ConditionKey(Condition::detkey_type det_key, const std::string& identifier);
    /// Constructor from detector element and item sub-key
    ConditionKey(DetElement detector, Condition::itemkey_type item_key);
    /// Constructor from detector element key and item sub-key
    ConditionKey(Condition::detkey_type det_key, Condition::itemkey_type item_key);
    /// Copy constructor
    ConditionKey(const ConditionKey& c) = default;

    /// Access the detector element part of the key
    Condition::detkey_type detector_key()  const   {
      return KeyMaker(hash).values.det_key;
    }
    /// Access the detector element part of the key
    Condition::itemkey_type item_key()  const   {
      return KeyMaker(hash).values.item_key;
    }
    /// Hash code generation from input string
    static Condition::key_type hashCode(DetElement detector, const char* value);
    /// Hash code generation from input string
    static Condition::key_type hashCode(DetElement detector, const std::string& value);
    /// 32 bit hashcode of the item
    static Condition::itemkey_type itemCode(const char* value);
    /// 32 bit hashcode of the item
    static Condition::itemkey_type itemCode(const std::string& value);
       
    /// Assignment operator from object copy
    ConditionKey& operator=(const ConditionKey& key) = default;
    /// Equality operator using key object
    bool operator==(const ConditionKey& compare)  const;
    /// Equality operator using hash value
    bool operator==(const Condition::key_type compare)  const;
    /// Equality operator using the string representation
    //bool operator==(const std::string& compare)  const;

    /// Operator less (for map insertions) using key object
    bool operator<(const ConditionKey& compare)  const;
    /// Operator less (for map insertions) using hash value
    bool operator<(const Condition::key_type compare)  const;
    /// Automatic conversion to the hashed representation of the key object
    operator Condition::key_type () const   {  return hash;     }
    /// Conversion to string
    std::string toString()  const;
  };

  /// Constructor from detector element key and item sub-key
  inline ConditionKey::ConditionKey(Condition::detkey_type det_key, Condition::itemkey_type item_key)  {
    hash = KeyMaker(det_key,item_key).hash;
  }
    
  /// Equality operator using key object
  inline bool ConditionKey::operator==(const ConditionKey& compare)  const
  {  return hash == compare.hash;                            }

  /// Equality operator using hash value
  inline bool ConditionKey::operator==(const Condition::key_type compare)  const
  {  return hash == compare;                                 }

  /// Operator less (for map insertions) using key object
  inline bool ConditionKey::operator<(const ConditionKey& compare)  const
  {  return hash < compare.hash;                             }

  /// Operator less (for map insertions) using hash value
  inline bool ConditionKey::operator<(const Condition::key_type compare)  const
  {  return hash < compare;                                  }


  /// Conditions selector functor. Default implementation selects everything evaluated.
  /**
   *  Please note:
   *  This class should never be directly instantiated by the user.
   *  A typical use-case is to do so in a wrapper class, which contains a refernce
   *  to a counter object, which in turn allows to deduce information from the
   *  processed objects.
   *
   *  See class ConditionsSelectWrapper below
   *
   *  \author  M.Frank
   *  \version 1.0
   *  \ingroup DD4HEP_CONDITIONS
   */
  class ConditionsSelect   {
  protected:
    /// Default constructor
    ConditionsSelect() = default;
    /// Copy constructor
    ConditionsSelect(const ConditionsSelect& copy) = default;
    /// Default destructor. 
    virtual ~ConditionsSelect();
    /// Default assignment operator
    ConditionsSelect& operator=(const ConditionsSelect& copy) = default;

  public:
    /// Selection callback: return true if the condition should be selected
    bool operator()(Condition cond)  const  { return (*this)(cond.ptr()); }
    /// Selection callback: return true if the condition should be selected
    bool operator()(std::pair<Condition::key_type,Condition::Object*> cond) const
    /** Arg is 2 longwords. No need to pass by reference.                      */
    { return (*this)(cond.second);            }
    /// Selection callback: return true if the condition should be selected
    /** Arg is 2 longwords. No need to pass by reference.                      */
    bool operator()(std::pair<Condition::key_type,Condition> cond) const
    { return (*this)(cond.second.ptr());      }

    /// Overloadable entry: Return number of conditions selected. Default does nothing....
    virtual size_t size()  const  { return 0; }
    /// Overloadable entry: Selection callback: return true if the condition should be selected
    virtual bool operator()(Condition::Object* cond) const = 0;
  };

  /// Conditions selector functor. Wraps a user defined object by reference
  /**
   *  Example usage for the slow ones:
   *
   *  class MyCounter : public ConditionsSelectWrapper<long> {
   *    MyCounter(long& cnt) : ConditionsSelectWrapper<long>(cnt) {}
   *    virtual bool operator()(Condition::Object* cond) const { if ( cond != 0 ) ++object; }
   *    // Optionally overload: virtual size_t size()  const  { return object; }
   *  };
   *  
   *  long counter = 0;
   *  for_each(std::begin(conditions), std::end(conditions), MyCounter(counter));
   *
   *  \author  M.Frank
   *  \version 1.0
   *  \ingroup DD4HEP_CONDITIONS
   */
  template <typename OBJECT> class ConditionsSelectWrapper : public ConditionsSelect {
  private:
    /// Default constructor
    ConditionsSelectWrapper() = delete;
    /// Default assignment operator
    bool operator==(const ConditionsSelectWrapper& compare) = delete;

  public:
    /// Reference to the infomation collector
    OBJECT& object;

  public:
    /// Default constructor
    ConditionsSelectWrapper(OBJECT& o) : ConditionsSelect(), object(o) {}
    /// Copy constructor
    ConditionsSelectWrapper(const ConditionsSelectWrapper& copy) = default;
    /// Default destructor. 
    virtual ~ConditionsSelectWrapper() = default;
    /// Default assignment operator
    ConditionsSelectWrapper& operator=(const ConditionsSelectWrapper& copy) = default;
  };

  // Utility type definitions
  typedef std::vector<Condition>          RangeConditions;

  /// Conditions internal namespace
  namespace detail  {
    /// Setup conditions item name inventory for debugging
    /** Populate a conditions item name inventory to ease debugging
     *  missing dependencies for derived conditions, which otherwise is
     *  difficult in optimized mode where all string values are suppressed.
     *
     *  Note: the inventory gets populated while creating item dependencies etc.
     *  Enabling afterwards has no effect.
     *
     *  value < 0  Return current value
     *  value = 0  Disable inventory for performance issues
     *  value > 0  Enable inventory population
     *
     *  \author  M.Frank
     *  \version 1.0
     *  \ingroup DD4HEP_CONDITIONS
     */
    int have_condition_item_inventory(int value = -1);

    
    /// Resolve key from conditions item name inventory for debugging
    /** The functionhave_condition_item_inventory must be called
     *  before items get populated to fill the inventory.....
     *
     *  key: conditions item key
     *
     *  \author  M.Frank
     *  \version 1.0
     *  \ingroup DD4HEP_CONDITIONS
     */
    std::string get_condition_item_name(Condition::itemkey_type key);

    /// Resolve key from conditions item name inventory for debugging
    /** The functionhave_condition_item_inventory must be called
     *  before items get populated to fill the inventory.....
     *
     *  key: full condition item hash
     *
     *  \author  M.Frank
     *  \version 1.0
     *  \ingroup DD4HEP_CONDITIONS
     */
    std::string get_condition_item_name(Condition::key_type key);
  }
  
}          /* End namespace dd4hep                   */
#endif // DD4HEP_CONDITIONS_H
