/* 
   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 Date;
  class UniverseTypeAwareTime;
  
  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(const std::string & name, const double mass, const double radius, const double J2, const double J3, const double J4);
    BodyConstants(const std::string & name, const double mass, const double radius, const JPL_planets, const double J2, const double J3, const double J4);
    BodyConstants(const std::string & name, const double mass, const double radius, const double J2, const double J3, const double J4, const double C22, const double C31, const double C32, const double C33, const double C41, const double C42, const double C43, const double C44, const double S31, const double S32, const double S33, const double S41, const double S42, const double S43, const double S44);
    BodyConstants(const std::string & name, const double mass, const double radius, const JPL_planets, const double J2, const double J3, const double J4, const double C22, const double C31, const double C32, const double C33, const double C41, const double C42, const double C43, const double C44, const double S31, const double S32, const double S33, const double S41, const double S42, const double S43, const double S44);
    
    ~BodyConstants();
    
  public:
    inline const std::string & name() const { return name_; } 
    inline double mass() const { return mass_; }
    inline double mu() const { return mu_; }
    inline bool has_zero_mass() const { return zero_mass_; }
    inline double radius() const { return radius_; }
    inline JPL_planets JPLPlanet() const { return planet_; }
    inline double J2() const { return J2_; }
    inline double J3() const { return J3_; }
    inline double J4() const { return J4_; }
    inline double C22() const { return C22_; }
    inline double C31() const { return C31_; }
    inline double C32() const { return C32_; }
    inline double C33() const { return C33_; }
    inline double C41() const { return C41_; }
    inline double C42() const { return C42_; }
    inline double C43() const { return C43_; }
    inline double C44() const { return C44_; }
    inline double S31() const { return S31_; }
    inline double S32() const { return S32_; }
    inline double S33() const { return S33_; }
    inline double S41() const { return S41_; }
    inline double S42() const { return S42_; }
    inline double S43() const { return S43_; }
    inline double S44() const { return S44_; }
    
  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 mu_;
    const bool zero_mass_;
    const double radius_;
    const JPL_planets planet_;
    const double J2_, J3_, J4_;
    const double C22_, C31_, C32_, C33_, C41_, C42_, C43_, C44_; 
    const double       S31_, S32_, S33_, S41_, S42_, S43_, S44_;
    
  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);
    Body(const std::string & name, const double mass, const double radius, const double J2, const double J3, const double J4);
    Body(const std::string & name, const double mass, const double radius, const JPL_planets p, const double J2, const double J3, const double J4);
    Body(const std::string & name, const double mass, const double radius, const double J2, const double J3, const double J4, const double C22, const double C31, const double C32, const double C33, const double C41, const double C42, const double C43, const double C44, const double S31, const double S32, const double S33, const double S41, const double S42, const double S43, const double S44);
    Body(const std::string & name, const double mass, const double radius, const JPL_planets p, const double J2, const double J3, const double J4, const double C22, const double C31, const double C32, const double C33, const double C41, const double C42, const double C43, const double C44, const double S31, const double S32, const double S33, const double S41, const double S42, const double S43, const double S44);
    Body(const std::string & name, const double mass, const Vector & position, const Vector & velocity, const double J2, const double J3, const double J4);
    Body(const std::string & name, const double mass, const double radius, const Vector & position, const Vector & velocity, const double J2, const double J3, const double J4);
    // copy constructors
    Body(const Body &);
    Body(const BodyWithEpoch &);
    Body(const JPLBody &);
    
    virtual ~Body();
    
    Body & operator = (const Body &);
    
  public:
    inline const std::string & name() const { return bc->name(); };
    inline double mass() const { return bc->mass(); };
    inline double mu() const { return bc->mu(); };
    inline bool has_zero_mass() const { return bc->has_zero_mass(); }
    inline double radius() const { return bc->radius(); };
    inline JPL_planets JPLPlanet() const { return bc->JPLPlanet(); }
    inline double J2() const { return bc->J2(); }
    inline double J3() const { return bc->J3(); }
    inline double J4() const { return bc->J4(); }
    inline double C22() const { return bc->C22(); }
    inline double C31() const { return bc->C31(); }
    inline double C32() const { return bc->C32(); }
    inline double C33() const { return bc->C33(); }
    inline double C41() const { return bc->C41(); }
    inline double C42() const { return bc->C42(); }
    inline double C43() const { return bc->C43(); }
    inline double C44() const { return bc->C44(); }
    inline double S31() const { return bc->S31(); }
    inline double S32() const { return bc->S32(); }
    inline double S33() const { return bc->S33(); }
    inline double S41() const { return bc->S41(); }
    inline double S42() const { return bc->S42(); }
    inline double S43() const { return bc->S43(); }
    inline double S44() const { return bc->S44(); }
    
    const Vector & position() const { return _position; }   
    const Vector & velocity() const { return _velocity; }
    
    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 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); }
    
    // b position - this position
    inline Vector 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(); }
    
  public:
    // used to sort bodies by decreasing mass 
    inline bool operator < (const Body &b) const { return b.mass() < mass(); }
    
  protected:
    BodyConstants * bc;
    
  protected:
    Vector _position, _velocity;
  };
  
  inline bool operator== (const Body &b1, const Body &b2) {
    if (b1.BodyId()             != b2.BodyId())              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 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) { }
    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) { }
    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) { }
    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) { }
    inline BodyWithEpoch(const std::string & name, const double mass, const double radius, const double J2, const double J3, const double J4) : Body(name,mass,radius,J2,J3,J4), epoch() { }
    inline BodyWithEpoch(const std::string & name, const double mass, const double radius, const JPL_planets p, const double J2, const double J3, const double J4) : Body(name,mass,radius,p,J2,J3,J4), epoch() { }
    inline BodyWithEpoch(const std::string & name, const double mass, const double radius, const JPL_planets p, const UniverseTypeAwareTime & t, const double J2, const double J3, const double J4) : Body(name,mass,radius,p,J2,J3,J4), epoch(t) { }
    inline BodyWithEpoch(const std::string & name, const double mass, const double radius, const JPL_planets p, const UniverseTypeAwareTime & t, const double J2, const double J3, const double J4, const double C22, const double C31, const double C32, const double C33, const double C41, const double C42, const double C43, const double C44, const double S31, const double S32, const double S33, const double S41, const double S42, const double S43, const double S44) : Body(name,mass,radius,p,J2,J3,J4,C22,C31,C32,C33,C41,C42,C43,C44,S31,S32,S33,S41,S42,S43,S44), epoch(t) { }
    
  public:
    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(const JPL_planets p, const Date & epoch);
    
  public:
    void SetEpoch(const UniverseTypeAwareTime &);
  };
  
  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_
