#ifndef Numerica_ODECrawler_H
#define Numerica_ODECrawler_H
#include <Glasses/ZGlass.h>
#include <gsl/gsl_odeiv.h>
class ODECrawlerMaster
{
public:
  virtual ~ODECrawlerMaster() {}
  virtual Int_t  ODEOrder() = 0;
  virtual void   ODEStart(Double_t y[], Double_t& x1, Double_t& x2) = 0;
  virtual void   ODEDerivatives(Double_t x, const Double_t y[], Double_t d[]) = 0;
  virtual bool   ODEHasJacobian() const { return false; }
  virtual void   ODEJacobian(Double_t x, const Double_t y[], Double_t* dfdy, Double_t dfdt[]) {}
  ClassDef(ODECrawlerMaster, 1);
};
class ODEStorage
{
protected:
  Int_t      mOrder;
  Int_t      mSize;
public:
  ODEStorage() :
    mOrder(0), mSize(0) {}
  ODEStorage(Int_t order, Int_t capacity=128) :
    mOrder(order), mSize(0) {}
  virtual ~ODEStorage() {}
  Int_t Order() const { return mOrder; }
  Int_t Size()  const { return mSize; }
  void ResetOrder(Int_t order, Int_t capacity=-1)
  {
    
    
    mOrder = order;
    if (capacity == -1)
      capacity = 12*mSize/10;
    Reset(capacity);
  }
  void Reset()
  {
    
    Reset(12*mSize/10);
  }
  virtual void Reset(Int_t capacity) = 0;
  virtual void AddEntry(Double_t x, Double_t* y) = 0;
  virtual Double_t GetMinXStored()   const = 0;
  virtual Double_t GetMaxXStored()   const = 0;
  virtual Double_t GetDeltaXStored() const { return GetMaxXStored() -  GetMinXStored(); }
  ClassDef(ODEStorage, 1);
};
template<typename TT>
class ODEStorageT : public ODEStorage
{
protected:
  vector<TT> mX;
  vector<TT> mY;
public:
  ODEStorageT(Int_t order, Int_t capacity=128) :
    ODEStorage(order, capacity)
  {
    mX.reserve(capacity);
    mY.reserve(mOrder*capacity);
  }
  virtual ~ODEStorageT() {}
  virtual void Reset(Int_t capacity)
  {
    
    vector<TT> x; x.reserve(capacity);        mX.swap(x);
    vector<TT> y; y.reserve(mOrder*capacity); mY.swap(y);
    mSize = 0;
  }
  virtual void AddEntry(Double_t x, Double_t* y)
  {
    mX.push_back(x);
    size_t ys = mY.size();
    mY.resize(ys + mOrder);
    for (Int_t i = 0; i < mOrder; ++i, ++ys)
      mY[ys] = y[i];
    ++mSize;
  }
  virtual Double_t GetMinXStored()   const { return mSize > 0 ? mX[0]       : 0; }
  virtual Double_t GetMaxXStored()   const { return mSize > 0 ? mX[mSize-1] : 0; }
  virtual Double_t GetDeltaXStored() const { return mSize > 0 ? mX[mSize-1] - mX[0] : 0; }
  TT        GetX(Int_t i) const { return  mX[i];        }
  const TT* GetY(Int_t i) const { return &mY[mOrder*i]; }
  const TT* GetXArr()     const { return &mX[0];        }
  const TT* GetYArr()     const { return &mY[0];        }
  ClassDef(ODEStorageT, 1);
};
typedef ODEStorageT<Float_t>  ODEStorageF;
typedef ODEStorageT<Double_t> ODEStorageD;
class ODECrawler : public ZGlass
{
public:
  enum StepFunc_e
  {
    SF_RK2, SF_RK4, SF_RKF45, SF_RKCK, SF_RK8PD, SF_RK2Imp, SF_RK4Imp,
    SF_BSImp, SF_Gear1, SF_Gear2
  };
  static const gsl_odeiv_step_type* s_get_step_func(StepFunc_e sf);
private:
  ODECrawlerMaster  *m_true_master; 
  Bool_t             m_crawling;    
  gsl_odeiv_system  *m_gsl_system;  
  gsl_odeiv_evolve  *m_gsl_evolve;  
  gsl_odeiv_control *m_gsl_control; 
  gsl_odeiv_step    *m_gsl_step;    
  void _init();
  void _gsl_alloc();
  void _gsl_free();
protected:
  ZLink<ZGlass> mODEMaster;     
  Int_t 	mStepOK;	
  Int_t 	mStepChanged;	
  Int_t 	mStored;	
  Int_t 	mMaxSteps;	
  Int_t 	mStoreMax;	
  Double_t	mStoreDx;	
  ODEStorage   *mStorage;       
  vector<Double_t> mY;		
  Int_t		mN;		
  Double_t	mEpsAbs;	
  Double_t	mEpsRel;	
  Double_t	mFacVal;	
  Double_t	mFacDer;	
  Double_t	mH1;		
  Double_t	mHmin;		
  Double_t      mH;             
  Bool_t        bStoreH;	
  StepFunc_e    mStepFunc;      
  StepFunc_e    mPrevStepFunc;  
  Double_t	mX1;		
  Double_t	mX2;		
  void          init_integration(Bool_t call_ode_start);
  void		Integrate();
public:
  ODECrawler(const Text_t* n="ODECrawler", const Text_t* t=0);
  virtual ~ODECrawler();
  void        AdvanceXLimits(Double_t delta_x=0);
  void        SetStorage(ODEStorage* s);
  void        DetachStorage();
  ODEStorage* SwapStorage(ODEStorage* s);
  void        Crawl(Bool_t call_ode_start=true); 
  
  void        ChangeOrderInPlace(Int_t order);
  Double_t*   RawYArray();
#include "ODECrawler.h7"
  ClassDef(ODECrawler, 1);
}; 
#endif