/* 
   ORSA - Orbit Reconstruction, Simulation and Analysis
   Copyright (C) 2002-2004 Pasquale Tricarico
   
   This program is free software; you can redistribute it and/or
   modify it under the terms of the GNU General Public License
   as published by the Free Software Foundation; either version 2
   of the License, or (at your option) any later version.
   
   As a special exception, Pasquale Tricarico gives permission to
   link this program with Qt commercial edition, and distribute the
   resulting executable, without including the source code for the Qt
   commercial edition in the source distribution.
   
   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.
   
   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
*/

#ifndef _ORSA_UNIVERSE_H_
#define _ORSA_UNIVERSE_H_

#include <string>
#include <vector>

#include "orsa_body.h"
#include "orsa_integrator.h"
#include "orsa_common.h"
#include "orsa_frame.h"
#include "orsa_error.h"

namespace orsa {
  
  //! This class collects all the Frames of an integration, sampled at a fixed sample_period
  class Evolution : protected std::vector<Frame> {
  public:
    Evolution();
    Evolution(const Evolution &);
    virtual ~Evolution();
    
  public:
    virtual unsigned int size() const { return std::vector<Frame>::size(); }
    virtual void push_back(const Frame &f) { std::vector<Frame>::push_back(f); }
    virtual reference operator[](size_type n) { return std::vector<Frame>::operator[](n); }
    virtual const_reference operator[](size_type n) const { return std::vector<Frame>::operator[](n); }
    virtual void clear() { std::vector<Frame>::clear(); }
    
  public:
    using std::vector<Frame>::iterator;
    using std::vector<Frame>::const_iterator;
    
  public:
    virtual iterator begin() { return std::vector<Frame>::begin(); }
    virtual iterator end()   { return std::vector<Frame>::end(); }
    virtual iterator erase(iterator position) { return std::vector<Frame>::erase(position); }
    //
    virtual const_iterator begin() const { return std::vector<Frame>::begin(); }
    virtual const_iterator end()   const { return std::vector<Frame>::end(); }
    
  public:
    void Integrate(const UniverseTypeAwareTime &time_stop, const bool save_last_anyway=false);
    void Integrate(const Frame&, const UniverseTypeAwareTime&, const UniverseTypeAwareTime&);
    
  public:
    // virtual void step_done(double,double,double,Frame&,bool &continue_integration) { 
    virtual void step_done(const UniverseTypeAwareTime &,
			   const UniverseTypeAwareTime &,
			   const UniverseTypeAwareTimeStep &,
			   const Frame &,
			   bool & continue_integration) { 
      if (bool_stop_integration) continue_integration=false;
    }
    
    virtual void integration_started()  { _integrating=true;  bool_stop_integration=false; };
    virtual void integration_finished() { _integrating=false; };
    
  public:
    void SetMaxUnsavedSubSteps(unsigned int m) {
      max_unsaved_substeps = m;
      max_unsaved_substeps_active = true;
    }
    
  public:
    std::vector<BodyWithEpoch> start_bodies;
    std::vector<JPL_planets>   start_JPL_bodies; // used only in Real Universe mode
    
    // public:
  private:
    Integrator  * integrator;
    Interaction * interaction;
    UniverseTypeAwareTimeStep sample_period;
    
  public:
    void SetIntegrator(const IntegratorType);
    void SetIntegrator(const Integrator *);
    const Integrator * GetIntegrator() const { return integrator; }
    //
    void SetIntegratorTimeStep(const UniverseTypeAwareTimeStep);
    const UniverseTypeAwareTimeStep & GetIntegratorTimeStep() const;
    //
    void SetIntegratorAccuracy(const double);
    double GetIntegratorAccuracy() const;
    // 
    void SetInteraction(const InteractionType);
    void SetInteraction(const Interaction *);
    const Interaction * GetInteraction() const { return interaction; }
    //
    void SetSamplePeriod(const UniverseTypeAwareTimeStep &);
    const UniverseTypeAwareTimeStep & GetSamplePeriod() const { return sample_period; }
    
  public:
    bool integrating() const { return _integrating; }
    virtual void stop_integration() const { bool_stop_integration=true; }
    
  private:
    mutable bool bool_stop_integration;
    
  private:
    bool _integrating;
    
  private:
    unsigned int max_unsaved_substeps;
    bool         max_unsaved_substeps_active;
    
  public:
    std::string name;
    
  private:
    static unsigned int used_evolution_id;
    
  public:
    unsigned int Id() const { return id; }
    
  private:
    const unsigned int id;
  };
  
  //! This enum is used to classify the Universes: a Real Universe is composed
  // exclusively by the JPL planets and massless bodies, while the Simulated
  // Universe is composed by an arbitrary number of massive and massless objects.
  // Another big difference is the following one: in the Real Universe each body
  // can be defined at a different epoch, while in the Simulated all the bodies
  // are defined at the same epoch.
  enum UniverseType {
    Real=1,
    Simulated=2
  };
  
  inline void convert(UniverseType &ut, const unsigned int i)  {
    switch(i) {
    case 1: ut = Real;      break;
    case 2: ut = Simulated; break;
      //
    default:
      ORSA_ERROR("conversion problem: i = %i",i);
      break;       
    }
  }
  
  class Universe : protected std::vector<Evolution*> { 
  public:
    Universe();
    Universe(length_unit,mass_unit,time_unit,UniverseType=Simulated,ReferenceSystem=ECLIPTIC,TimeScale=ET);
    
    virtual ~Universe();
    
  private:
    void common_init(const length_unit, const mass_unit, const time_unit);
    
  public:
    virtual unsigned int size() const { return std::vector<Evolution*>::size(); }
    virtual void push_back(Evolution * const e) { std::vector<Evolution*>::push_back(e); }
    virtual reference operator[](size_type n) { return std::vector<Evolution*>::operator[](n); }
    virtual const_reference operator[](size_type n) const { return std::vector<Evolution*>::operator[](n); }
    virtual void clear() { std::vector<Evolution*>::clear(); }
    
  public:
    typedef std::vector<Evolution*>::iterator             iterator;
    typedef std::vector<Evolution*>::const_iterator const_iterator;
    
  public:
    virtual iterator begin() { return std::vector<Evolution*>::begin(); }
    virtual iterator end()   { return std::vector<Evolution*>::end(); }
    virtual iterator erase(iterator position) { return std::vector<Evolution*>::erase(position); }
    //
    virtual const_iterator begin() const { return std::vector<Evolution*>::begin(); }
    virtual const_iterator end()   const { return std::vector<Evolution*>::end(); }
    
  public:
    inline UniverseType GetUniverseType() const { return type; }
    inline ReferenceSystem GetReferenceSystem() const { return sys; }
    inline TimeScale GetTimeScale() const { return timescale; }
    
  public:
    std::string name;
    std::string description;
    
  public:
    bool modified;
    
  private:
    const UniverseType    type;
    const ReferenceSystem sys;
    const TimeScale       timescale; // sync with default_Date_timescale
  };
  
  //! The active universe
  extern Universe * universe;
  
  //! A good frame to start an integration with 
  Frame StartFrame(const std::vector<BodyWithEpoch> &, std::vector<JPL_planets> &, const Interaction *, const Integrator *, const UniverseTypeAwareTime &);
} 
// namespace orsa

#endif // _ORSA_UNIVERSE_H_
