#ifndef __JOSCPROB__JOSCPARAMETERSINTERFACE__
#define __JOSCPROB__JOSCPARAMETERSINTERFACE__

#include <iostream>
#include <iomanip>
#include <string>

#include "Jeep/JComment.hh"
#include "Jeep/JProperties.hh"

#include "JSystem/JStat.hh"

#include "JIO/JSerialisable.hh"

#include "JLang/JClonable.hh"
#include "JLang/JParameter.hh"
#include "JLang/JStringStream.hh"
#include "JLang/JEquationParameters.hh"

#include "JTools/JGrid.hh"

#include "JOscProb/JOscParameter.hh"


/**
 * \author bjung, mdejong
 */

namespace JOSCPROB {}
namespace JPP { using namespace JOSCPROB; }

namespace JOSCPROB {
  
  using JIO::JReader;
  using JIO::JWriter;
  using JIO::JSerialisable;

  using JLANG::JClonable;
  using JLANG::JEquationParameters;

  
  /**
   * Interface class for sets of oscillation parameters.
   */
  template<class T>
  class JOscParametersInterface :
    public JClonable<JOscParametersInterface<T> >,
    public JSerialisable
  {
  public:
    
    typedef JOscParametersInterface<T>                   JOscParametersInterface_t;
    typedef JOscParameter<T>                             JOscParameter_t;
    
    typedef typename JOscParameter_t::JParameter_t       JParameter_t;

    typedef typename JParameter_t::argument_type         argument_type;    
    typedef typename JParameter_t::value_type            value_type;


    /**
     * Default constructor.
     */
    JOscParametersInterface()
    {}


    /**
     * Virtual destructor.
     */
    ~JOscParametersInterface()
    {}


    /**
     * Get properties of this class.
     *
     * \param  equation        equation parameters
     * \return                 properties of this class
     */
    virtual JProperties getProperties(const JEquationParameters& equation = JOscParametersInterface_t::getEquationParameters()) = 0;


    /**
     * Get properties of this class.
     *
     * \param  equation        equation parameters
     * \return                 properties of this class
     */
    virtual JProperties getProperties(const JEquationParameters& equation = JOscParametersInterface_t::getEquationParameters()) const = 0;


    /**
     * Get oscillation parameters.
     *
     * \return                 reference to oscillation parameters
     */
    JOscParametersInterface_t& getParameters()
    {
      return static_cast<JOscParametersInterface_t&>(*this);
    }


    /**
     * Get oscillation parameter.
     *
     * \param  name            parameter name
     * \return                 parameter
     */
    const JOscParameter_t& get(const std::string& name) const
    {
      using namespace std;
      using namespace JPP;

      JProperties properties = this->getProperties();

      const JOscParameter_t& parameter = properties.getValue<const JOscParameter_t>(name);
      
      return parameter;
    }
    

    /**
     * Get oscillation parameter.
     *
     * \param  name            parameter name
     * \return                 parameter
     */
    JOscParameter_t& get(const std::string& name)
    {
      using namespace std;
      using namespace JPP;

      JProperties properties = this->getProperties();

      JOscParameter_t& parameter = properties.getValue<JOscParameter_t>(name);
      
      return parameter;
    }
    

    /**
     * Set value for a given oscillation parameter.
     *
     * \param  name            parameter name
     * \param  value           parameter value
     */
    void set(const std::string& name,
	     const value_type&  value)
    {
      using namespace std;
      using namespace JPP;

      JProperties properties = this->getProperties();

      JOscParameter_t& parameter = properties.getValue<JOscParameter_t>(name);

      parameter.setValue(value);
    }


    /**
     * Set value for given list of oscillation parameters.
     *
     * \param  name            parameter name
     * \param  value           parameter value
     * \param  args            remaining pairs of parameter names and values
     */
    template<class ...Args>
    void set(const std::string& name,
	     const value_type&  value,
	     const Args&     ...args)
    {
      set(name, value);
      set(args...);
    }


    /**
     * Set oscillation parameters.
     *
     * \param  parameters      oscillation parameters
     */
    void set(const JOscParametersInterface<value_type>& parameters)
    {
      using namespace std;
      using namespace JPP;

      JProperties properties = parameters.getProperties();

      for (JProperties::const_iterator i = properties.cbegin(); i != properties.cend(); ++i) {
	
	const JOscParameter_t& parameter = i->second.getValue<const JOscParameter_t>();
	
	this->set(i->first, parameter.getValue());
      }
    }
    

    /**
     * Check validity of oscillation parameters.
     *
     * \return                 true if all oscillation parameters are valid; else false
     */
    bool is_valid() const
    {
      bool valid = true;

      const JProperties properties = this->getProperties();

      for (JProperties::const_iterator i = properties.cbegin(); i != properties.cend() && valid; ++i) {

	const JOscParameter_t& parameter = i->second.getValue<const JOscParameter_t>();
	
	valid = parameter.is_valid();
      }

      return valid;
    }
    
    
    /**
     * Check whether these oscillation parameters are equal to given oscillation parameters.
     *
     * \param                  oscillation parameters
     * \return                 true if equal; else false
     */
    bool equals(const JOscParametersInterface<value_type>& parameters) const
    {
      using namespace std;
      using namespace JPP;

      bool equal = true;

      JProperties properties = this->getProperties();

      for (JProperties::const_iterator i = properties.cbegin(); i != properties.cend() && equal; ++i) {

	const JOscParameter_t& parameter = i->second.getValue<const JOscParameter_t>();
	
	equal = (parameter == parameters.get(i->first));
      }

      return equal;
    }


    /**
     * Equal operator.
     *
     * \param  parameters   set of oscillation parameters
     * \return              true if this set of oscillation parameters is equal to the given set; else false
     */
    bool operator==(const JOscParametersInterface<value_type>& parameters)
    { 
      return this->equals(parameters);
    }
    
    
    /**
     * Not equal operator.
     *
     * \param  parameters   set of oscillation parameters
     * \return              true if this set of oscillation parameters is equal to the given set; else false
     */
    bool operator!=(const JOscParametersInterface<value_type>& parameters)
    {
      return !this->equals(parameters);
    }
    
    
    /**
     * Get equation parameters.
     *
     * \return                 equation parameters
     */
    static inline JEquationParameters& getEquationParameters()
    {
      static JEquationParameters equation("=", "\n\r;,", "./", "#");

      return equation;
    }
      

    /**
     * Set equation parameters.
     *
     * \param  equation        equation parameters
     */
    static inline void setEquationParameters(const JEquationParameters& equation)
    {
      getEquationParameters() = equation;
    }


    /**
     * Binary stream input of oscillation parameters.
     *
     * \param  in              input stream
     * \return                 input stream
     */
    JReader& read(JReader& in) override
    {
      JProperties properties = this->getProperties();
      
      for (JProperties::iterator i = properties.begin(); i != properties.end(); ++i) {
	
	bool       is_defined;
	value_type value;
	
	if ((in >> is_defined >> value) && is_defined) {

	  JOscParameter_t& parameter = i->second.getValue<JOscParameter_t>();
	  
	  parameter.setValue(value);
	}
      }

      return in;
    }
    

    /**
     * Binary stream output of oscillation parameters.
     *
     * \param  out             output stream
     * \return                 output stream
     */
    JWriter& write(JWriter& out) const override
    {
      const JProperties properties = this->getProperties();
      
      for (JProperties::const_iterator i = properties.cbegin(); i != properties.cend(); ++i) {

	const JOscParameter_t& parameter = i->second.getValue<const JOscParameter_t>();

	out << parameter.isDefined() << parameter.getValue();
      }

      return out;
    }


    /**
     * Stream input of oscillation parameters.
     *
     * \param  in              input stream
     * \param  parameters      oscillation parameters
     * \return                 input stream
     */
    friend inline std::istream& operator>>(std::istream& in, JOscParametersInterface_t& parameters)
    {
      using namespace std;
      using namespace JPP;
      
      JStringStream is(in);

      if (getFileStatus(is.str().c_str())) {
	is.load();
      }

      JProperties properties = parameters.getProperties();
      is >> properties;

      return in;
    }


    /**
     * Stream output of oscillation parameters.
     *
     * \param  out             output stream
     * \param  parameters      oscillation parameters
     * \return                 output stream
     */
    friend inline std::ostream& operator<<(std::ostream& out, const JOscParametersInterface_t& parameters)
    {
      return out << parameters.getProperties();
    }
  };


  /**
   * Get size of given oscillation parameters set.
   *
   * \param   parameters     oscillation parameters set
   * \return                 size (= number of defined parameters)
   */
  template<class T>
  inline size_t getSize(const JOscParametersInterface<T>& parameters)
  {
    size_t n = 0;
      
    const JProperties properties = parameters.getProperties();

    for (JProperties::const_iterator i = properties.cbegin(); i != properties.cend(); ++i) {
      const JOscParameter<T>& parameter = i->second.getValue<const JOscParameter<T> >();
      
      n += (size_t) parameter.isDefined();
    }

    return n;
  }


  /**
   * Get size of given oscillation parameters grid.
   *
   * \param  parameters      oscillation parameters grid
   * \return                 size (= number of defined parameters)
   */
  inline size_t getSize(const JOscParametersInterface<JGrid<double> >& parameters)
  {
    size_t n = 1;
      
    const JProperties properties = parameters.getProperties();

    for (JProperties::const_iterator i = properties.cbegin(); i != properties.cend(); ++i) {
      const JOscParameter<JGrid<double> >& parameter = i->second.getValue<const JOscParameter<JGrid<double> > >();

      if (parameter.isDefined()) {
	n *= getSize(parameter);
      }
    }

    return n;
  }  
}

#endif