/* 
   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_BODY_H_
#define _ORSA_BODY_H_

#include <assert.h>
#include <string>
#include <list>
#include <map>
#include <iostream>

#include "orsa_coord.h"
#include "orsa_common.h"
#include "orsa_file_jpl.h"

namespace orsa {
  
  /* 
     class Life {
     public:
     inline Life() { born_set = false; dead_set = false; }    
     
     public:
     inline void Born(UniverseTypeAwareTime &t) { assert(! dead_set); born_set  = true; born_time = t; }  
     inline void Dead(UniverseTypeAwareTime &t) { assert(born_set); dead_set  = true; dead_time = t; }
     
     inline bool Born() const { return born_set; }
     inline bool Dead() const { return dead_set; }
     
     inline bool StillAlive() const { return born_set && (!dead_set); }    
     inline UniverseTypeAwareTime BornTime() const { return (born_time); }
     inline UniverseTypeAwareTime DeadTime() const { return (dead_time); }
     
     bool Alive(UniverseTypeAwareTime &t) const;    
     
     private:
     bool born_set, dead_set;
     UniverseTypeAwareTime born_time, dead_time;
     };
  */
  
  class Body;
  class BodyWithEpoch;
  class JPLBody;
  
  class BodyConstants {
  public:
    BodyConstants();
    BodyConstants(const std::string & name, const double mass);
    BodyConstants(const std::string & name, const double mass, const double radius);
    BodyConstants(const std::string & name, const double mass, const double radius, const JPL_planets);
    
    ~BodyConstants();
    
  public:
    // bool IsAlive(Body*) const;
    
  public:
    const std::string & name() const { return name_; } 
    double mass() const { return mass_; } 
    double radius() const { return radius_; }
    JPL_planets JPLPlanet() const { return planet_; }
    
  protected:
    unsigned int users;
    
  public:
    void AddUser()    { ++users; }
    void RemoveUser() { --users; }
    unsigned int Users() const { return users; }	
    
  private:
    const std::string name_;
    const double mass_;
    const double radius_;
    const JPL_planets planet_;
    // double  J2,J4; // not yet implemented
    
  private:
    static unsigned int used_body_id;
    
  public:
    unsigned int BodyId() const { return id; }
    unsigned int     Id() const { return id; }
    
  private:
    const unsigned int id;
    
  protected:
    static std::list<BodyConstants*> list_bc;
  };
  

  class Body {
  public:
    // brand new bodies
    Body(); 
    Body(const double mass);
    Body(const std::string & name);
    Body(const std::string & name, const double mass);
    Body(const std::string & name, const double mass, const double radius);
    Body(const std::string & name, const double mass, const double radius, const JPL_planets p);
    Body(const std::string & name, const double mass, const Vector & position, const Vector & velocity);
    Body(const std::string & name, const double mass, const double radius, const Vector & position, const Vector & velocity);
    // copy constructor
    Body(const Body &);
    Body(const BodyWithEpoch &);
    // Body(const JPLBody&);
    Body(const JPLBody &);
    // child bodies
    // Body(Body *parent);
    // Body(Body *parent, const double  mass);
    // Body(Body *parent, const std::string &name);
    // Body(Body *parent, const std::string &name, const double mass);
    // Body(Body *parent, const std::string &name, const double mass, const Vector &position, const Vector &velocity, bool vectors_relative_to_parent=false);
    //
    virtual ~Body();
    
    Body & operator = (const Body &);
    
  public:
    const std::string & name() const { return bc->name(); };
    double mass() const { return bc->mass(); };
    double radius() const { return bc->radius(); };
    JPL_planets JPLPlanet() const { return bc->JPLPlanet(); }
    
    // inline void Born(UniverseTypeAwareTime &t) const { bc->life.Born(t); }
    // inline void Dead(UniverseTypeAwareTime &t) const { bc->life.Dead(t); }
    
    // inline bool Born() const { return (bc->life.Born()); }
    // inline bool Dead() const { return (bc->life.Dead()); }
    
    // inline bool StillAlive()                    const { return (bc->life.StillAlive());  }
    // inline bool Alive(UniverseTypeAwareTime &t) const { return (bc->life.Alive(t)); }
    
    // inline UniverseTypeAwareTime BornTime() const { return (bc->life.BornTime()); }
    // inline UniverseTypeAwareTime DeadTime() const { return (bc->life.DeadTime()); }
    
    ////
    
    // inline virtual Vector position() const { return _position; }   
    // inline virtual Vector velocity() const { return _velocity; }
    // inline Vector position() const { return _position; }   
    // inline Vector velocity() const { return _velocity; }
    const Vector & position() const { return _position; }   
    const Vector & velocity() const { return _velocity; }
    
    // aliases
    /* 
       inline Vector Position() const { return position(); }
       inline Vector R()        const { return position(); }
       inline Vector Velocity() const { return velocity(); }
       inline Vector V()        const { return velocity(); }
    */
    
    /*
     * NOTE: if (sync_child)
     *                       CHILD ABSOLUTE POSITION = UNCHANGED
     *                       CHILD RELATIVE POSITION = UPDATED
     *       else
     *                       CHILD ABSOLUTE POSITION = UPDATED
     *                       CHILD RELATIVE POSITION = UNCHANGED
     */                       
    
    // void AddToPosition(const Vector &v, bool sync_child=true);
    // void AddToVelocity(const Vector &v, bool sync_child=true);
    
    inline void AddToPosition(const Vector & v) { _position += v; }
    inline void AddToVelocity(const Vector & v) { _velocity += v; }
    
    inline void SetPosition(const Vector & v) { _position = v; }
    inline void SetPosition(const double x, const double y, const double z) { Vector v(x,y,z); SetPosition(v); }
    // inline void SetPosition(const double x, const double y, const double z, bool sync_child=true, Body *ref_body=0) { SetPosition(Vector(x,y,z),v,sync_child,ref_body); } 
    inline void SetVelocity(const Vector & v) { _velocity = v; }
    inline void SetVelocity(const double x, const double y, const double z) { Vector v(x,y,z); SetVelocity(v); }
    // void SetVelocity(const Vector &v, bool sync_child=true, Body *ref_body=0); 
    
    // b position - this position
    inline Vector Body::distanceVector(const Body & b) const { return b.position()-position(); }
    inline double distance(const Body & b) const { return distanceVector(b).Length(); }
    
    // alias
    inline Vector DistanceVector(const Body & b) const { return distanceVector(b); }
    inline double Distance(const Body & b)       const { return distance(b); }

    inline double KineticEnergy() const { return (bc->mass() * _velocity.LengthSquared() / 2.0); }

  public:
    unsigned int       BodyId() const { return bc->BodyId(); }
    // inline unsigned int ParentBodyId() const { return (parent != 0) ? parent->bc->BodyId() : -1; }

    // inline bool HaveParent() const { return parent != 0; }
    
  public:
    // used to sort bodies by decreasing mass 
    inline bool operator < (const Body &b) const { return b.mass() < mass(); }
    
  public:
    // void SetParent(Body*, const bool soft=false, const bool recursive=false);
    
  public:
    // void RecursiveSoftSetChild(Body *old_child, Body *new_child);
    
  protected:
    //! used when the parent is changed, to update the index of the family tree 
    // void UpdateFamilyTree();
    
  protected:
    // inline void SyncPosition(const Vector &v) { relative_position -= v; }
    // inline void SyncVelocity(const Vector &v) { relative_velocity -= v; }
    
  protected:
    BodyConstants * bc;
    
  protected:
    // Vector       relative_position, relative_velocity;
    Vector _position, _velocity;
    // Body        *parent;
    // std::list<Body*>  child;
    
  protected:
    // static, class level family id variables
    // static unsigned int used_family_id;
    // inline static unsigned int generate_family_id() { return (used_family_id++); }
    
  public:
    // inline unsigned int GetFamilyId()        const { return (family_id); };
    // inline unsigned int GetFamilyTreeLevel() const { return (family_tree_level); };
      
  private:
    // void local_free(const bool equal_operator=false);
    // void local_create(const Body &b, const bool equal_operator=false);
    
  protected:
    //! same id == same family tree
    // unsigned int family_id;
    
    //! 0 = base, 1 = first child....
    // unsigned int family_tree_level;
  };
  
  // better (if works....)
  // inline bool operator== (const Body &b1, const Body &b2) { return (b1.BodyId() == b2.BodyId()); }
  
  inline bool operator== (const Body &b1, const Body &b2) {
    if (b1.BodyId()             != b2.BodyId())              return false;
    // if (b1.GetFamilyId()        != b2.GetFamilyId())         return false;
    // if (b1.GetFamilyTreeLevel() != b2.GetFamilyTreeLevel())  return false;
    if (b1.name()               != b2.name())                return false;
    if (b1.mass()               != b2.mass())                return false;
    if (b1.position()           != b2.position())            return false;
    if (b1.velocity()           != b2.velocity())            return false;
    return true;
  } 
  
  /* 
  inline bool operator== (const Body &b1, const Body &b2) {
    if (b1.GetFamilyId()        != b2.GetFamilyId())         return false;
    if (b1.GetFamilyTreeLevel() != b2.GetFamilyTreeLevel())  return false;
    if (b1.name()               != b2.name())                return false;
    if (b1.mass()               != b2.mass())                return false;
    if (b1.position()           != b2.position())            return false;
    if (b1.velocity()           != b2.velocity())            return false;
    return true;
  }
  */
  
  inline bool operator!= (const Body &b1, const Body &b2) { return !(b1 == b2); }
  

  //! base element for intepolation
  class BodyWithParameter : public Body {
  public:
    double par;
  };
  
  
  class BodyWithEpoch : public Body {
  public:
    inline BodyWithEpoch() : Body() { }
    
  public:
    // BodyWithEpoch(const JPLBody &);
    BodyWithEpoch(const BodyWithEpoch &);
    inline BodyWithEpoch(const double mass) : Body(mass), epoch() { }
      inline BodyWithEpoch(const std::string & name, const double mass) : Body(name,mass), epoch() { }
	inline BodyWithEpoch(const std::string & name, const double mass, const Vector & r, const Vector & v) : Body(name,mass,r,v), epoch() { }
	  
	  inline BodyWithEpoch(const std::string & name, const double mass, const Vector & r, const Vector & v, const Date & d) : Body(name,mass,r,v), epoch(d) {
	    //epoch.SetDate(d);
	  }
	    
	inline BodyWithEpoch(const std::string & name, const double mass, const Vector & r, const Vector & v, const double t) : Body(name,mass,r,v), epoch(t) {
	  // epoch.SetTime(t);
	}
	  
	  inline BodyWithEpoch(const std::string & name, const double mass, const Vector & r, const Vector & v, const UniverseTypeAwareTime & t) : Body(name,mass,r,v), epoch(t) {
	    // epoch = t;
	  }
	    
    inline BodyWithEpoch(const std::string & name, const double mass, const double radius) : Body(name,mass,radius), epoch() { }
      
    inline BodyWithEpoch(const std::string & name, const double mass, const double radius, const JPL_planets p) : Body(name,mass,radius,p), epoch() { }
      
      inline BodyWithEpoch(const std::string & name, const double mass, const double radius, const JPL_planets p, const UniverseTypeAwareTime & t) : Body(name,mass,radius,p), epoch(t) {
	
      }
	
	// public:
	
  public:
      // UniverseTypeAwareTime Epoch() const { return epoch; }
      // UniverseTypeAwareTime GetEpoch() const { return epoch; }
      const UniverseTypeAwareTime & Epoch() const { return epoch; }
      const UniverseTypeAwareTime & GetEpoch() const { return epoch; }
      
  public:
    virtual void SetEpoch(const UniverseTypeAwareTime & t) {
      epoch = t;
    }
    
  protected:
    UniverseTypeAwareTime epoch;
  };
  
  
  // JPL planets
  class JPLBody : public BodyWithEpoch {
  public:
    JPLBody();
    JPLBody(JPL_planets p, const Date & epoch);
    
  public:
    // void SetEpoch(const Date &);
    void SetEpoch(const UniverseTypeAwareTime &);
    
    /* 
       public:
       Date Epoch() const { return epoch; }
       
       private:
       Date epoch;
    */
  };
  
  void Interpolate(const std::vector < BodyWithParameter > & b_in, const double x, Body &b_out, Body &err_b_out);
  
  void print(const Body&);
  
  double KineticEnergy(const Body&);
  
  // template <class T> void AutoHierarchy(vector<T>&); // T is usually a Body or a BodyWithEpoch
  
  // template <class T> void PrintHierarchy(const vector<T>&); // T is usually a Body or a BodyWithEpoch
  
} // namespace orsa

#endif // _ORSA_BODY_H_
