/*
 * Copyright (c) 2002-2005 The Regents of The University of Michigan
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met: redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer;
 * redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the distribution;
 * neither the name of the copyright holders nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * Authors: Steve Reinhardt
 */

#ifndef __SIM_PARAM_HH__
#define __SIM_PARAM_HH__

#include <iostream>
#include <list>
#include <string>
#include <vector>

#include "sim/startup.hh"

// forward decls
class IniFile;
class BaseParam;
class SimObject;

//
// The context of a parameter definition... usually a subclass of
// SimObjectBuilder (which derives from ParamContext), but abstracted
// here to support more global simulator control parameters as well.
//
class ParamContext : protected StartupCallback
{
  private:

    // static list of all ParamContext objects, built as a side effect
    // of the ParamContext constructor
    static std::list<ParamContext *> *ctxList;

  protected:

    // .ini file (database) for parameter lookup... initialized on call
    // to parseParams()
    IniFile *iniFilePtr;

    // .ini file section for parameter lookup
    const std::string iniSection;

    typedef std::vector<BaseParam *> ParamList;

    // list of parameters defined in this context
    ParamList *paramList;

    ParamList *getParamList() {
        if (!paramList)
            paramList = new ParamList;
        return paramList;
    }

  public:

    /// Initialization phases for ParamContext objects.
    enum InitPhase {
        NoAutoInit = -1,	///< Don't initialize at all... params
                                /// will be parsed later (used by
                                /// SimObjectBuilder, which parses
                                /// params in SimObject::create().
        OutputInitPhase = 0,	///< Output stream initialization
        TraceInitPhase = 1,	///< Trace context initialization:
                                /// depends on output streams, but
                                /// needs to come before others so we
                                /// can use tracing in other
                                /// ParamContext init code
        StatsInitPhase = 2,	///< Stats output initialization
        DefaultInitPhase = 3	///< Everything else
    };

    /// Records the initialization phase for this ParamContext.
    InitPhase initPhase;

    /// Constructor.
    /// @param _iniSection Name of .ini section corresponding to this context.
    /// @param _initPhase Initialization phase  (see InitPhase).
    ParamContext(const std::string &_iniSection,
                 InitPhase _initPhase = DefaultInitPhase);

    virtual ~ParamContext() {}

    // add a parameter to the context... called from the parameter
    // object's constructor (see BaseParam::BaseParam())
    void addParam(BaseParam *);

    // call parse() on all params in this context to convert string
    // representations to parameter values
    virtual void parseParams(IniFile &iniFile);

    // Check parameter values for validity & consistency. Default
    // implementation is no-op; derive subclass & override to add
    // actual functionality here
    virtual void checkParams();

    // Clean up at end of execution: close file descriptors, etc.
    // Default implementation is no-op; derive subclass & override to
    // add actual functionality here
    virtual void cleanup();

    // dump parameter descriptions
    void describeParams(std::ostream &);

    // Display the parameters & values used
    void showParams(std::ostream &);

    // print context information for parameter error
    virtual void printErrorProlog(std::ostream &);

    // generate the name for this instance of this context (used as a
    // prefix to create unique names in resolveSimObject()
    virtual const std::string &getInstanceName() { return iniSection; }

    // Parse all parameters registered with all ParamContext objects.
    static void parseAllContexts(IniFile &iniFile);

    // Check all parameters registered with all ParamContext objects.
    // (calls checkParams() on each)
    static void checkAllContexts();

    // Print all parameter values on indicated ostream.
    static void showAllContexts(std::ostream &os);

    // Clean up all registered ParamContext objects.  (calls cleanup()
    // on each)
    static void cleanupAllContexts();

    // print descriptions of all parameters registered with all
    // ParamContext objects
    static void describeAllContexts(std::ostream &os);
};


//
// Base class for all parameter objects
//
class BaseParam
{
  public:

    ParamContext *context;
    std::string name;
    std::string description;	// text description for help message
    bool wasSet;	// true if parameter was set by user
    bool hasDefault;	// true if parameter has default value

    BaseParam(ParamContext *_context, const std::string &_name,
              const std::string &_description, bool _hasDefault)
        : context(_context), name(_name), description(_description),
          wasSet(false), hasDefault(_hasDefault)
    {
        context->addParam(this);
    }

    virtual ~BaseParam() {}

    // a parameter is valid only if its value was set by the user or
    // it has a default value
    bool isValid() const
    {
        return (wasSet || hasDefault);
    }

    // set value by parsing string
    virtual void parse(const std::string &s) = 0;

    // display value to stream
    virtual void showValue(std::ostream &) const = 0;

    // display type to stream
    virtual void showType(std::ostream &) const = 0;

    // signal parse or usage error
    virtual void die(const std::string &err) const;
};

//
// Template classes to specialize parameters to specific types.
//
// Param<T> is for single-valued (scalar) parameters of type T.
// VectorParam<T> is for multi-valued (vector) parameters of type T.
// These are specified in the .ini file as a space-delimited list of
// arguments.
//
template <class T>
class Param : public BaseParam
{
  protected:

    T value;

  public:

    // Param with default value: set value to default
    Param(ParamContext *context,
          const std::string &name, const std::string &description, T dfltValue)
        : BaseParam(context, name, description, true),
          value(dfltValue)
    {
    }

    // Param with no default value: leave value uninitialized
    Param(ParamContext *context,
          const std::string &name, const std::string &description)
        : BaseParam(context, name, description, false)
    {
    }

    virtual ~Param() {}

    operator T&()
    {
        // if we attempt to reference an invalid parameter (i.e., one
        // with no default value that was not set by the user), die.
        if (!isValid())
            die("not found");
        return value;
    }

    // display value to stream
    virtual void showValue(std::ostream &os) const;

    // display type to stream
    virtual void showType(std::ostream &) const;

    // set value by parsing string
    virtual void parse(const std::string &s);
};


//
// Template class for vector-valued parameters (lists)
//
template <class T>
class VectorParam : public BaseParam
{
  protected:

    std::vector<T> value;

  public:

    typedef typename std::vector<T>::size_type size_type;

    // Param with default value: set value to default
    VectorParam(ParamContext *context, const std::string &name,
                const std::string &description,
                const std::vector<T> &dfltValue)
        : BaseParam(context, name, description, true),
          value(dfltValue)
    {
    }

    // Param with no default value: leave value uninitialized
    VectorParam(ParamContext *context,
                const std::string &name, const std::string &description)
        : BaseParam(context, name, description, false)
    {
    }

    virtual ~VectorParam() {}

    // basic vector access methods
    size_type size() const
    {
        if (!isValid())
            die("not found");
        return value.size();
    }

    const T &operator[](size_type n) const
    {
        if (!isValid())
            die("not found");
        return value[n];
    }

    // return reference to value vector
    operator std::vector<T>&()
    {
        if (!isValid())
            die("not found");
        return value;
    }

    // display value to stream
    virtual void showValue(std::ostream &os) const;

    // display type to stream
    virtual void showType(std::ostream &) const;

    // set value by parsing string
    virtual void parse(const std::string &s);
};

//
// Specialization of Param<int> and VectorParam<int> to handle
// enumerated types is done in two ways, using SimpleEnumParam and
// MappedEnumParam (and their vector counterparts,
// SimpleEnumVectorParam and MappedEnumVectorParam).  SimpleEnumParam
// takes an array of strings and maps them to integers based on array
// index.  MappedEnumParam takes an array of string-to-int mappings,
// allowing for mapping strings to non-contiguous integer values, or
// mapping multiple strings to the same integer value.
//
// Both SimpleEnumParam and MappedEnumParam are implemented using a
// single template class, EnumParam<Map>, which takes the type of the map
// as a parameter (const char * or EnumParamMap).  Similarly,
// SimpleEnumVectorParam and MappedEnumVectorParam are both
// implemented using EnumVectorParam<Map>.
//
template <class Map>
class EnumParam : public Param<int>
{
    const int num_values;
    const Map *map;

  public:

    // Param with default value: set value to default
    EnumParam(ParamContext *context,
              const std::string &name, const std::string &description,
              const Map *_map, int _num_values,
              int dfltValue)
        : Param<int>(context, name, description, dfltValue),
          num_values(_num_values), map(_map)
    {
    }

    // Param with no default value: leave value uninitialized
    EnumParam(ParamContext *context,
              const std::string &name, const std::string &description,
              const Map *_map, int _num_values)
        : Param<int>(context, name, description),
          num_values(_num_values), map(_map)
    {
    }

    virtual ~EnumParam() {}

    // display value to stream
    virtual void showValue(std::ostream &os) const;

    // display type to stream
    virtual void showType(std::ostream &) const;

    // set value by parsing string
    virtual void parse(const std::string &s);
};

//
// Vector counterpart to SimpleEnumParam
//
template <class Map>
class EnumVectorParam : public VectorParam<int>
{
    const int num_values;
    const Map *map;

  public:

    // Param with default value: set value to default
    EnumVectorParam(ParamContext *context,
                    const std::string &name, const std::string &description,
                    const Map *_map, int _num_values,
                    std::vector<int> &dfltValue)
        : VectorParam<int>(context, name, description, dfltValue),
          num_values(_num_values), map(_map)
    {
    }

    // Param with no default value: leave value uninitialized
    EnumVectorParam(ParamContext *context,
                    const std::string &name, const std::string &description,
                    const Map *_map, int _num_values)
        : VectorParam<int>(context, name, description),
          num_values(_num_values), map(_map)
    {
    }

    virtual ~EnumVectorParam() {}

    // display value to stream
    virtual void showValue(std::ostream &os) const;

    // display type to stream
    virtual void showType(std::ostream &) const;

    // set value by parsing string
    virtual void parse(const std::string &s);
};

// Specialize EnumParam for a particular enumeration type ENUM
// (automates casting to get value of enum type)

template <class ENUM>
class SimpleEnumParam : public EnumParam<const char *>
{
  public:

    SimpleEnumParam(ParamContext *context,
                    const std::string &name, const std::string &description,
                    const char **_map, int _num_values,
                    ENUM dfltValue)
        : EnumParam<const char *>(context, name, description,
                                  _map, _num_values, (int)dfltValue)
    {
    }

    SimpleEnumParam(ParamContext *context,
                    const std::string &name, const std::string &description,
                    const char **_map, int _num_values)
        : EnumParam<const char *>(context, name, description,
                                  _map, _num_values)
    {
    }

    operator ENUM() const
    {
        if (!isValid())
            die("not found");
        return (ENUM)value;
    }
};


// Specialize EnumParam for a particular enumeration type ENUM
// (automates casting to get value of enum type)

template <class ENUM>
class SimpleEnumVectorParam : public EnumVectorParam<const char *>
{
  public:

    // skip default value constructor: too much pain to convert
    // vector<ENUM> initializer to vector<int>


    SimpleEnumVectorParam(ParamContext *context,
                          const std::string &name,
                          const std::string &description,
                          const char **_map, int _num_values)
        : EnumVectorParam<const char *>(context, name, description,
                                        _map, _num_values)
    {
    }

    ENUM operator[](size_type n)
    {
        if (!isValid())
            die("not found");
        return (ENUM)value[n];
    }
};


//
// Handle enums via string-to-int map (see comment above).
//

// An array of string-to-int mappings must be supplied using the
// following type.
typedef struct {
    const char *name;
    int value;
} EnumParamMap;

// Specialize EnumParam for a particular enumeration type ENUM
// (automates casting to get value of enum type)

template <class ENUM>
class MappedEnumParam : public EnumParam<EnumParamMap>
{
  public:

    MappedEnumParam(ParamContext *context,
                    const std::string &name, const std::string &description,
                    const EnumParamMap *_map, int _num_values,
                    ENUM dfltValue)
        : EnumParam<EnumParamMap>(context, name, description,
                                  _map, _num_values, (int)dfltValue)
    {
    }

    MappedEnumParam(ParamContext *context,
                    const std::string &name, const std::string &description,
                    const EnumParamMap *_map, int _num_values)
        : EnumParam<EnumParamMap>(context, name, description,
                                  _map, _num_values)
    {
    }

    operator ENUM()
    {
        if (!isValid())
            die("not found");
        return (ENUM)value[this->n];
    }
};


// Specialize EnumParam for a particular enumeration type ENUM
// (automates casting to get value of enum type)

template <class ENUM>
class MappedEnumVectorParam : public EnumVectorParam<EnumParamMap>
{
  public:

    // skip default value constructor: too much pain to convert
    // vector<ENUM> initializer to vector<int>


    MappedEnumVectorParam(ParamContext *context,
                          const std::string &name,
                          const std::string &description,
                          const EnumParamMap *_map, int _num_values)
        : EnumVectorParam<EnumParamMap>(context, name, description,
                                        _map, _num_values)
    {
    }

    ENUM operator[](size_type n)
    {
        if (!isValid())
            die("not found");
        return (ENUM)value[n];
    }
};


//
// Parameters that point to other simulation objects (e.g. caches,
// busses, etc.) are handled by specializing SimObjectBaseParam to the
// specific subtype.  The main purpose of SimObjectBaseParam is to
// provide a place to stick several helper functions common to all
// SimObject-derived parameters.
//
class SimObjectBaseParam : public BaseParam
{
  public:

    SimObjectBaseParam(ParamContext *context, const std::string &name,
                       const std::string &description, bool hasDefault)
        : BaseParam(context, name, description, hasDefault)
    {
    }

    virtual ~SimObjectBaseParam() {}

    // helper function for SimObjectParam<T>::showValue()
    void showValue(std::ostream &os, SimObject *obj) const;

    // helper function for SimObjectParam<T>::parse()
    void parse(const std::string &s, SimObject *&value);

    // helper function for SimObjectParam<T>::parse()
    void parse(const std::string &s, std::vector<SimObject *>&value_vec);
};


//
// Parameter to a specific type of SimObject.  Note that T must be a
// pointer to a class derived from SimObject (e.g., <CPU *>).
//

template <class T> class SimObjectParam;

template <class T>
class SimObjectParam<T *> : public SimObjectBaseParam
{
  protected:

    T *value;

  public:

    // initialization w/o default
    SimObjectParam(ParamContext *context,
                   const std::string &name, const std::string &description)
        : SimObjectBaseParam(context, name, description, false)
    {
    }

    // initialization wit=h default
    SimObjectParam(ParamContext *context,
                   const std::string &name, const std::string &description,
                   T *dfltValue)
        : SimObjectBaseParam(context, name, description, true),
          value(dfltValue)
    {
    }

    virtual ~SimObjectParam() {}

    // convert to pointer
    operator T*()
    {
        if (!isValid())
            die("not found");
        return value;
    }

    T *operator->() const
    {
        if (!isValid())
            die("not found");
        return value;
    }

    // display value to stream
    virtual void showValue(std::ostream &os) const
    {
        SimObjectBaseParam::showValue(os, value);
    }

    // display type to stream: see REGISTER_SIM_OBJECT macro in
    // sim_object.hh for declaration
    virtual void showType(std::ostream &os) const;

    // set value by parsing string
    virtual void parse(const std::string &s)
    {
        SimObject *so_ptr;
        // first parse to generic SimObject *
        SimObjectBaseParam::parse(s, so_ptr);
        // now dynamic_cast to specific derived type
        value = dynamic_cast<T *>(so_ptr);
        // check for failure of dynamic_cast
        if (value == NULL && so_ptr != NULL)
            die("not of appropriate type");
    }
};


//
// Vector counterpart to SimObjectParam<T>
//

template <class T> class SimObjectVectorParam;

template <class T>
class SimObjectVectorParam<T *> : public SimObjectBaseParam
{
  protected:

    std::vector<T *> value;

  public:

    typedef typename std::vector<T *>::size_type size_type;

    SimObjectVectorParam(ParamContext *context,
                         const std::string &name,
                         const std::string &description)
        : SimObjectBaseParam(context, name, description, false)
    {
    }

    SimObjectVectorParam(ParamContext *context,
                         const std::string &name,
                         const std::string &description,
                         std::vector<T *> dfltValue)
        : SimObjectBaseParam(context, name, description, true),
          value(dfltValue)
    {
    }

    virtual ~SimObjectVectorParam() {}

    // basic vector access methods
    size_type size() const
    {
        if (!isValid())
            die("not found");
        return value.size();
    }

    T *&operator[](size_type n)
    {
        if (!isValid())
            die("not found");
        return value[n];
    }

    // return reference to value vector
    operator std::vector<T *>&()
    {
        if (!isValid())
            die("not found");
        return value;
    }

    // display value to stream
    virtual void showValue(std::ostream &os) const
    {
        for (int i = 0; i < value.size(); i++) {
            if (i != 0)
                os << " ";
            SimObjectBaseParam::showValue(os, value[i]);
        }
    }

    // display type to stream: see
    virtual void showType(std::ostream &os) const;

    // set value by parsing string
    virtual void parse(const std::string &s)
    {
        std::vector<SimObject *> so_ptr_vec;
        // first parse to generic SimObject * vector (from SimObjectBaseParam)
        SimObjectBaseParam::parse(s, so_ptr_vec);

        value.resize(so_ptr_vec.size());

        for (int i = 0; i < so_ptr_vec.size(); ++i) {
            // now dynamic_cast to specific derived type
            value[i] = dynamic_cast<T *>(so_ptr_vec[i]);
            // check for failure of dynamic_cast
            if (value[i] == NULL && so_ptr_vec[i] != NULL)
                die("not of appropriate type");
        }
    }
};

//
// Macro to define showType() methods for SimObjectParam &
// SimObjectVectorParam.  Can't do this automatically as it requires a
// string name for the type, which you can't get from a template
// argument.  For concrete derived SimObject types, this macro is
// automatically invoked by REGISTER_SIM_OBJECT() (see sim_object.hh).
//
#define DEFINE_SIM_OBJECT_CLASS_NAME(CLASS_NAME, OBJ_CLASS)		\
template<>								\
void									\
SimObjectParam<OBJ_CLASS *>::showType(std::ostream &os)	const		\
{									\
    os << CLASS_NAME;							\
}									\
                                                                        \
template<>								\
void									\
SimObjectVectorParam<OBJ_CLASS *>::showType(std::ostream &os) const	\
{									\
    os << "vector of " << CLASS_NAME;					\
}


//
// Declarations for low-level parsing & displaying functions.  These
// are used internally, but should not be used directly by clients of
// the parameter mechanism, but are declared here so they can be
// shared with the serialization code (see sim/serialize.cc).
template <class T> bool parseParam(const std::string &str, T &data);
template <class T> void showParam(std::ostream &os, const T &data);

#endif // _SIM_PARAM_HH_
