// $Id: Range.h 1264 2022-02-03 17:50:01Z ge $
/// \file Range.h
/// \brief contains the class Range 
///
/// $Revision: 1264 $
/// \author Gerald Weber <gweberbh@gmail.com>

#ifndef GBC_RANGE_H
#define GBC_RANGE_H "$Id: Range.h 1264 2022-02-03 17:50:01Z ge $"

#include <cmath>
#include <boost/regex.hpp>
#include <boost/lexical_cast.hpp>
#include <sstream>
#include <valarray>
#include "ErrorCodes.h"
#include "MathAux.h"
#include <iomanip>

namespace gbc
{
template<class _Tp>
/// \brief Provides a range of values of type _Tp.
///
/// Handles the automatic assignment and incrementation of a
/// range of values. Usually this is used in conjunction with the class
/// Option, i.e., you would define Option<Range<_Tp> >.
/// The interpretation is done like follow, if the range is given like
/// "1:10" it starts at 1, ends at 10 with increments of 1.
/// If "1:10:2" it has increments of 2. If "1:10/3" the range is divided
/// into 3 steps and the increment is calculated accordingly. Note that
/// for Range<int> it performs an integer division, i.e. (10-1)/3 which results
/// in an increment of 1, therefore in practice there will be more than 3 steps.
class Range
  {
  public:
    typedef _Tp internal_type;
    typedef long int steps_type;

    enum IncPriorityType {StepsPriority     = 1 /* Increment is always calculated from Steps */, 
                          IncrementPriority = 2 /* Steps is always calculated from Increment */};
    std::string  Expr;                      ///< String that was used to interpret the range

  protected:
    internal_type Current;      ///< Current value in use
    internal_type Begin;        ///< Start of the range
    internal_type End;          ///< End of the range
    internal_type Increment;    ///< Increment
    steps_type    Steps;        ///< Number of steps
    steps_type    Current_step; ///< Number of steps

    IncPriorityType Priority;

  public:

    /// \brief void Constructor, all element are set to void.
    Range(void): Expr(), Current(), Begin(), End(), Increment(), 
                 Steps(), Current_step(),
                 Priority(StepsPriority) {};

    Range(std::string expr): Expr(), Current(), Begin(), End(), Increment(), 
                 Steps(), Current_step(),
                 Priority(StepsPriority) 
      {
      interpret_string(expr);
      };

    /// \brief Constructor which sets all values
    Range(double b)  
      : Expr(), Current(b), Begin(b), End(b), Increment(), 
        Steps(), Current_step(),
        Priority(IncrementPriority) 
      {
      increment();
      }

    /// \brief Constructor which sets all values
    Range(internal_type b, internal_type e, internal_type inc=internal_type())  
      : Expr(), Current(b), Begin(b), End(e), Increment(), 
        Steps(), Current_step(),
        Priority(IncrementPriority) 
      {
      increment(inc);
      }
      
    void interpret_string(std::string expr)
      {
      clear();
      Expr=expr;
      boost::regex single("^([-+]?\\d+\\.?\\d*)$");
      boost::regex mult("^([-+]?\\d+\\.?\\d*):([-+]?\\d+\\.?\\d*)([:/]?)([-+]?\\d*\\.?\\d*)$");
      boost::smatch found;
      if (boost::regex_search(expr,found,single))
        {
        begin(boost::lexical_cast<internal_type>(found[1]));
        end(begin());
        Priority=IncrementPriority;//for single elements to be backward compatible
        calculate_steps();
        }
      else
        {
        if (boost::regex_search(expr,found,mult))
          {
          //https://theboostcpplibraries.com/boost.lexical_cast
          begin(boost::lexical_cast<internal_type>(found[1]));
          end(boost::lexical_cast<internal_type>(found[2])); 
          if (found.size() == 5)
            {
            if (found[3] == std::string(":")) increment(boost::lexical_cast<internal_type>(found[4]));
            else 
              if (found[3] == std::string("/")) steps(boost::lexical_cast<steps_type>(found[4]));
              else calculate_steps();
            }
          }
        else
          {
          CERR_ERROR(ERRUIEAAR) << "unable to interpret \"" << expr << "\" as a range" << std::endl;
          CERR_TERM
          }
        }
      }

    inline internal_type current(void)   const {return Current;}
    inline steps_type    current_step(void) const {return Current_step;}

    inline internal_type begin(void)     const {return Begin;}
    inline internal_type end(void)       const {return End;}
    inline steps_type    steps(void)     const {return Steps;} 
    inline internal_type increment(void) const {return Increment;}
    inline IncPriorityType priority(void)  const {return Priority;}

    /// \brief Returns the value of Range::Current
    inline operator internal_type() const {return Current;}


     /// \brief Calculates number of steps from End, Begin and increment.
    inline void calculate_steps(void) 
      {
      if (Begin == End) Steps=steps_type();
      else 
        if (Increment ==  internal_type()) Steps=1;
        else Steps=static_cast<steps_type>(round((End-Begin)/increment()))+1;
      }


    /// \brief Calculates increment from End, Begin and number of steps.
    inline void calculate_increment(void) 
      {
      if (Begin == End) Increment=internal_type();
      else
        if (Steps == internal_type()) Increment=End-Begin;
        else Increment=(End-Begin)/static_cast<internal_type>(Steps);
      }

    inline void begin(const internal_type& b) 
      {
      Begin=b;
      Current=Begin;
      }

    inline void end(const internal_type& e) 
      {
      End=e;
      if ( ((Current > End) and (Increment > 0)) ) Current=End; //avoids Current to be set beyond End
      }

    inline void begin_end(const internal_type& b, const internal_type& e) 
      {
      Begin=b;
      End=e;
      Current=Begin;
      calculate_increment_or_steps();
      }

    inline void calculate_increment_or_steps(void) 
      {
      if (Priority == StepsPriority)
        calculate_increment();
      else
        calculate_steps();
      }

    inline void steps(const steps_type& st) 
      {
      Priority=StepsPriority;
      if (st == steps_type()) 
        Steps=steps_type(1);
      else
        Steps=st;
      calculate_increment();
      }

    inline void increment(const internal_type& inc)
      {
      Priority=IncrementPriority;
      if (inc == internal_type())
        Increment=End-Begin;
      else
        Increment=inc;
      calculate_steps();
      }

    /// \brief Returns the n-th value of the range.
    ///
    /// \attention This operator does not check if st is larger
    /// then the actual number of steps of the range, i.e., a 
    /// value larger than end() may be returned.
    inline internal_type operator[] (const steps_type& st)
      {
      return begin()+st*increment();
      }

    /// \brief returns a random value between begin() and end()   
    inline internal_type linear_random(void)
      {
      double dice=linear_ran();
      return (end()-begin())*dice + begin();
      }      
      
    inline void current_step(const steps_type& st)
      {
      Current_step=st;
      Current=(*this)[st];
      }

    /// \brief Increments the current value of the range.
    inline internal_type next_step(void)
      {
      Current_step++;
      if (Priority == StepsPriority)
        Current=(*this)[Current_step];
      else
        Current+=increment();
      return Current;
      }

    /// \brief Increments the current value of the range.
    ///
    /// \attention This is a prefix operator.
    inline internal_type operator++(void)
      {
      return next_step();
      }

    // Two ranges are considerd the same if they have the same begin/end, the same type of priority,
    // in case of StepsPriority steps should be equal, otherwise Increment should be equal 
    inline bool operator==(const Range& r) const
      {
      bool bep = (Begin == r.Begin) and (End == r.End) and (Priority == r.Priority);
      
      if (bep) 
        {
        if (Priority == StepsPriority) 
             bep = bep and (Steps == r.Steps);
        else bep = bep and (Increment == r.Increment);
        }
       
      return bep;
      }

    inline bool operator!=(const Range& r) const
      {
      return not ((*this) == r);
      }
      
    /// \brief clears all fiels
    inline void clear(void) 
      {
      Current=Begin=End=Increment=internal_type();
      Steps=Current_step=steps_type();
      Priority=StepsPriority;
      Expr.clear();
      }
      
      
    /// \brief Resets the range to its initial condition.
    inline void reset() 
      {
      Current=Begin;
      Current_step=steps_type();
      }

    /// \brief Resets the range to its initial condition.
    inline void restart() 
      {
      reset();
      }

    /// \brief Shrinks the interval around a certain value and keeps number
    /// of steps constant.
    ///
    /// \attention pm should not be larger than half of steps. 
    /// Switches to StepsPriority.
    inline void re_centralise(const steps_type& pm,  ///< plus/minus the step interval
                              const Range* ref=NULL) ///< reference range, keep new range withing this limit
      {
      if (pm >= steps()/2) 
        {
        CERR_WARN(WRCPPMTL) << "Re-centralize problems, pm too large" << std::endl;
        }

      if (Current_step > steps()) 
        {
        CERR_WARN(WRCPCSTL) << "Re-centralize problems, Current_step too large" << std::endl;
        }

      internal_type new_Begin=(*this)[Current_step-pm];
      internal_type new_End  =(*this)[Current_step+pm];
 
      if (ref != NULL)
        {
        if (new_Begin < ref->Begin) new_Begin=ref->Begin;
        if (new_End   > ref->End)   new_End  =ref->End;
        }

      Priority = StepsPriority;
      begin_end(new_Begin,new_End);
      }
      
    //Checks if this value meets end condition
    inline bool end_condition(const internal_type &verify)
      {
      return ((verify > End) && (verify != End));
      }

    /// \brief The end condition.
    inline bool finished(void) 
      {
      bool finishing;
      if (increment() != internal_type())
        {
        finishing = end_condition(Current);
        CERR_DEBUG(DRAN_FIN) << " finished=" << finishing << " Current=" << MAXPRECSION << Current << " End=" << End 
#if __GNUC__ >= 5
        << std::defaultfloat
#endif
         << std::endl;
        }
      else
        {
        //if increment() is zero then any step beyond zero signals an ending
        finishing = (Current_step > steps_type());
        CERR_DEBUG(DRAN_FIN) << " finished=" << finishing << " Current_step=" << Current_step << std::endl;
        }
      return finishing;
      }

    inline std::string str(void) const
      {
      std::stringstream st;
      st << begin() << ":" << end();
      if (Priority == StepsPriority) st << "/" << steps();
      else             st << ":" << increment();
      return st.str();
      }

      
    inline friend std::ostream& operator<<(std::ostream& out,const Range& r)
      {
      out << r.str();
      return out;
      }
      
    
    inline std::valarray<internal_type> convert2valarray (void)
      {
      Range<internal_type> r;
      r=*this; //make a copy so that we don't loose where we are in the loop
      r.calculate_increment_or_steps();
      CERR_DEBUG(DRAN_C2V) << " range: " << r << std::endl;
      
      int vec_size=r.Steps;
      if (r.end_condition(r.begin() + vec_size*r.increment())) 
        {
        vec_size--;
        CERR_DEBUG(DRAN_C2V) << " reduced size to= " << vec_size << std::endl;
        }
      
      std::valarray<internal_type> vec(vec_size);
      r.reset();
      for(int n=0; n < vec_size; r.next_step(), n++) vec[n]=r.current();
      CERR_DEBUG(DRAN_C2V) << " last value is= " << MAXPRECSION << vec[vec_size-1] 
#if __GNUC__ >= 5
        << std::defaultfloat
#endif
      << std::endl;

      return vec;        
      }

  };


};
#endif
