/************************************************************************
 * Copyright (C) 1996-2003, International Business Machines Corporation *
 * and others. All Rights Reserved.                                     *
 ************************************************************************
 *  2003-nov-07   srl       Port from Java
 */

#ifndef ASTRO_H
#define ASTRO_H

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include "gregoimp.h"  // for Math
#include "unicode/unistr.h"

U_NAMESPACE_BEGIN

/**
 * <code>CalendarAstronomer</code> is a class that can perform the calculations to
 * determine the positions of the sun and moon, the time of sunrise and
 * sunset, and other astronomy-related data.  The calculations it performs
 * are in some cases quite complicated, and this utility class saves you
 * the trouble of worrying about them.
 * <p>
 * The measurement of time is a very important part of astronomy.  Because
 * astronomical bodies are constantly in motion, observations are only valid
 * at a given moment in time.  Accordingly, each <code>CalendarAstronomer</code>
 * object has a <code>time</code> property that determines the date
 * and time for which its calculations are performed.  You can set and
 * retrieve this property with {@link #setDate setDate}, {@link #getDate getDate}
 * and related methods.
 * <p>
 * Almost all of the calculations performed by this class, or by any
 * astronomer, are approximations to various degrees of accuracy.  The
 * calculations in this class are mostly modelled after those described
 * in the book
 * <a href="http://www.amazon.com/exec/obidos/ISBN=0521356997" target="_top">
 * Practical Astronomy With Your Calculator</a>, by Peter J.
 * Duffett-Smith, Cambridge University Press, 1990.  This is an excellent
 * book, and if you want a greater understanding of how these calculations
 * are performed it a very good, readable starting point.
 * <p>
 * <strong>WARNING:</strong> This class is very early in its development, and
 * it is highly likely that its API will change to some degree in the future.
 * At the moment, it basically does just enough to support {@link IslamicCalendar}
 * and {@link ChineseCalendar}.
 *
 * @author Laura Werner
 * @author Alan Liu
 * @internal
 */
class U_I18N_API CalendarAstronomer : public UMemory {
public:
  // some classes

public:
  /**
   * Represents the position of an object in the sky relative to the ecliptic,
   * the plane of the earth's orbit around the Sun.
   * This is a spherical coordinate system in which the latitude
   * specifies the position north or south of the plane of the ecliptic.
   * The longitude specifies the position along the ecliptic plane
   * relative to the "First Point of Aries", which is the Sun's position in the sky
   * at the Vernal Equinox.
   * <p>
   * Note that Ecliptic objects are immutable and cannot be modified
   * once they are constructed.  This allows them to be passed and returned by
   * value without worrying about whether other code will modify them.
   *
   * @see CalendarAstronomer.Equatorial
   * @see CalendarAstronomer.Horizon
   * @internal
   */
  class U_I18N_API Ecliptic : public UMemory {
  public:
    /**
     * Constructs an Ecliptic coordinate object.
     * <p>
     * @param lat The ecliptic latitude, measured in radians.
     * @param lon The ecliptic longitude, measured in radians.
     * @internal
     */
    Ecliptic(double lat = 0, double lon = 0) {
      latitude = lat;
      longitude = lon;
    }

    /**
     * Setter for Ecliptic Coordinate object
     * @param lat The ecliptic latitude, measured in radians.
     * @param lon The ecliptic longitude, measured in radians.
     * @internal
     */
    void set(double lat, double lon) {
      latitude = lat;
      longitude = lon;
    }

    /**
     * Return a string representation of this object
     * @internal
     */
    UnicodeString toString() const;

    /**
     * The ecliptic latitude, in radians.  This specifies an object's
     * position north or south of the plane of the ecliptic,
     * with positive angles representing north.
     * @internal
     */
    double latitude;

    /**
     * The ecliptic longitude, in radians.
     * This specifies an object's position along the ecliptic plane
     * relative to the "First Point of Aries", which is the Sun's position
     * in the sky at the Vernal Equinox,
     * with positive angles representing east.
     * <p>
     * A bit of trivia: the first point of Aries is currently in the
     * constellation Pisces, due to the precession of the earth's axis.
     * @internal
     */
    double longitude;
  };

  /**
   * Represents the position of an
   * object in the sky relative to the plane of the earth's equator.
   * The <i>Right Ascension</i> specifies the position east or west
   * along the equator, relative to the sun's position at the vernal
   * equinox.  The <i>Declination</i> is the position north or south
   * of the equatorial plane.
   * <p>
   * Note that Equatorial objects are immutable and cannot be modified
   * once they are constructed.  This allows them to be passed and returned by
   * value without worrying about whether other code will modify them.
   *
   * @see CalendarAstronomer.Ecliptic
   * @see CalendarAstronomer.Horizon
   * @internal
   */
  class U_I18N_API Equatorial : public UMemory {
  public:
    /**
     * Constructs an Equatorial coordinate object.
     * <p>
     * @param asc The right ascension, measured in radians.
     * @param dec The declination, measured in radians.
     * @internal
     */
    Equatorial(double asc = 0, double dec = 0)
      : ascension(asc), declination(dec) { }

    /**
     * Setter
     * @param asc The right ascension, measured in radians.
     * @param dec The declination, measured in radians.
     * @internal
     */
    void set(double asc, double dec) {
      ascension = asc;
      declination = dec;
    }

    /**
     * Return a string representation of this object, with the
     * angles measured in degrees.
     * @internal
     */
    UnicodeString toString() const;

    /**
     * Return a string representation of this object with the right ascension
     * measured in hours, minutes, and seconds.
     * @internal
     */
    //String toHmsString() {
    //return radToHms(ascension) + "," + radToDms(declination);
    //}

    /**
     * The right ascension, in radians.
     * This is the position east or west along the equator
     * relative to the sun's position at the vernal equinox,
     * with positive angles representing East.
     * @internal
     */
    double ascension;

    /**
     * The declination, in radians.
     * This is the position north or south of the equatorial plane,
     * with positive angles representing north.
     * @internal
     */
    double declination;
  };

  /**
   * Represents the position of an  object in the sky relative to
   * the local horizon.
   * The <i>Altitude</i> represents the object's elevation above the horizon,
   * with objects below the horizon having a negative altitude.
   * The <i>Azimuth</i> is the geographic direction of the object from the
   * observer's position, with 0 representing north.  The azimuth increases
   * clockwise from north.
   * <p>
   * Note that Horizon objects are immutable and cannot be modified
   * once they are constructed.  This allows them to be passed and returned by
   * value without worrying about whether other code will modify them.
   *
   * @see CalendarAstronomer.Ecliptic
   * @see CalendarAstronomer.Equatorial
   * @internal
   */
  class U_I18N_API Horizon : public UMemory {
  public:
    /**
     * Constructs a Horizon coordinate object.
     * <p>
     * @param alt  The altitude, measured in radians above the horizon.
     * @param azim The azimuth, measured in radians clockwise from north.
     * @internal
     */
    Horizon(double alt=0, double azim=0)
      : altitude(alt), azimuth(azim) { }

    /**
     * Setter for Ecliptic Coordinate object
     * @param alt  The altitude, measured in radians above the horizon.
     * @param azim The azimuth, measured in radians clockwise from north.
     * @internal
     */
    void set(double alt, double azim) {
      altitude = alt;
      azimuth = azim;
    }

    /**
     * Return a string representation of this object, with the
     * angles measured in degrees.
     * @internal
     */
    UnicodeString toString() const;

    /**
     * The object's altitude above the horizon, in radians.
     * @internal
     */
    double altitude;

    /**
     * The object's direction, in radians clockwise from north.
     * @internal
     */
    double azimuth;
  };

public:
  //-------------------------------------------------------------------------
  // Assorted private data used for conversions
  //-------------------------------------------------------------------------

  // My own copies of these so compilers are more likely to optimize them away
  static const double PI;

  /**
   * The average number of solar days from one new moon to the next.  This is the time
   * it takes for the moon to return the same ecliptic longitude as the sun.
   * It is longer than the sidereal month because the sun's longitude increases
   * during the year due to the revolution of the earth around the sun.
   * Approximately 29.53.
   *
   * @see #SIDEREAL_MONTH
   * @internal
   * @deprecated ICU 2.4. This class may be removed or modified.
   */
  static const double SYNODIC_MONTH;

  //-------------------------------------------------------------------------
  // Constructors
  //-------------------------------------------------------------------------

  /**
   * Construct a new <code>CalendarAstronomer</code> object that is initialized to
   * the current date and time.
   * @internal
   */
  CalendarAstronomer();

  /**
   * Construct a new <code>CalendarAstronomer</code> object that is initialized to
   * the specified date and time.
   * @internal
   */
  CalendarAstronomer(UDate d);

  /**
   * Construct a new <code>CalendarAstronomer</code> object with the given
   * latitude and longitude.  The object's time is set to the current
   * date and time.
   * <p>
   * @param longitude The desired longitude, in <em>degrees</em> east of
   *                  the Greenwich meridian.
   *
   * @param latitude  The desired latitude, in <em>degrees</em>.  Positive
   *                  values signify North, negative South.
   *
   * @see java.util.Date#getTime()
   * @internal
   */
  CalendarAstronomer(double longitude, double latitude);

  /**
   * Destructor
   * @internal
   */
  ~CalendarAstronomer();

  //-------------------------------------------------------------------------
  // Time and date getters and setters
  //-------------------------------------------------------------------------

  /**
   * Set the current date and time of this <code>CalendarAstronomer</code> object.  All
   * astronomical calculations are performed based on this time setting.
   *
   * @param aTime the date and time, expressed as the number of milliseconds since
   *              1/1/1970 0:00 GMT (Gregorian).
   *
   * @see #setDate
   * @see #getTime
   * @internal
   */
  void setTime(UDate aTime);


  /**
   * Set the current date and time of this <code>CalendarAstronomer</code> object.  All
   * astronomical calculations are performed based on this time setting.
   *
   * @param aTime the date and time, expressed as the number of milliseconds since
   *              1/1/1970 0:00 GMT (Gregorian).
   *
   * @see #getTime
   * @internal
   */
  void setDate(UDate aDate) { setTime(aDate); }

  /**
   * Set the current date and time of this <code>CalendarAstronomer</code> object.  All
   * astronomical calculations are performed based on this time setting.
   *
   * @param jdn   the desired time, expressed as a "julian day number",
   *              which is the number of elapsed days since
   *              1/1/4713 BC (Julian), 12:00 GMT.  Note that julian day
   *              numbers start at <em>noon</em>.  To get the jdn for
   *              the corresponding midnight, subtract 0.5.
   *
   * @see #getJulianDay
   * @see #JULIAN_EPOCH_MS
   * @internal
   */
  void setJulianDay(double jdn);

  /**
   * Get the current time of this <code>CalendarAstronomer</code> object,
   * represented as the number of milliseconds since
   * 1/1/1970 AD 0:00 GMT (Gregorian).
   *
   * @see #setTime
   * @see #getDate
   * @internal
   */
  UDate getTime();

  /**
   * Get the current time of this <code>CalendarAstronomer</code> object,
   * expressed as a "julian day number", which is the number of elapsed
   * days since 1/1/4713 BC (Julian), 12:00 GMT.
   *
   * @see #setJulianDay
   * @see #JULIAN_EPOCH_MS
   * @internal
   */
  double getJulianDay();

  /**
   * Return this object's time expressed in julian centuries:
   * the number of centuries after 1/1/1900 AD, 12:00 GMT
   *
   * @see #getJulianDay
   * @internal
   */
  double getJulianCentury();

  /**
   * Returns the current Greenwich sidereal time, measured in hours
   * @internal
   */
  double getGreenwichSidereal();

private:
  double getSiderealOffset();
public:
  /**
   * Returns the current local sidereal time, measured in hours
   * @internal
   */
  double getLocalSidereal();

  /**
   * Converts local sidereal time to Universal Time.
   *
   * @param lst   The Local Sidereal Time, in hours since sidereal midnight
   *              on this object's current date.
   *
   * @return      The corresponding Universal Time, in milliseconds since
   *              1 Jan 1970, GMT.
   */
  //private:
  double lstToUT(double lst);

  /**
   *
   * Convert from ecliptic to equatorial coordinates.
   *
   * @param ecliptic     The ecliptic
   * @param result       Fillin result
   * @return reference to result
   */
  Equatorial& eclipticToEquatorial(Equatorial& result, const Ecliptic& ecliptic);

  /**
   * Convert from ecliptic to equatorial coordinates.
   *
   * @param eclipLong     The ecliptic longitude
   * @param eclipLat      The ecliptic latitude
   *
   * @return              The corresponding point in equatorial coordinates.
   * @internal
   */
  Equatorial& eclipticToEquatorial(Equatorial& result, double eclipLong, double eclipLat);

  /**
   * Convert from ecliptic longitude to equatorial coordinates.
   *
   * @param eclipLong     The ecliptic longitude
   *
   * @return              The corresponding point in equatorial coordinates.
   * @internal
   */
  Equatorial& eclipticToEquatorial(Equatorial& result, double eclipLong) ;

  /**
   * @internal
   */
  Horizon& eclipticToHorizon(Horizon& result, double eclipLong) ;

  //-------------------------------------------------------------------------
  // The Sun
  //-------------------------------------------------------------------------

  /**
   * The longitude of the sun at the time specified by this object.
   * The longitude is measured in radians along the ecliptic
   * from the "first point of Aries," the point at which the ecliptic
   * crosses the earth's equatorial plane at the vernal equinox.
   * <p>
   * Currently, this method uses an approximation of the two-body Kepler's
   * equation for the earth and the sun.  It does not take into account the
   * perturbations caused by the other planets, the moon, etc.
   * @internal
   */
  double getSunLongitude();

  /**
   * TODO Make this public when the entire class is package-private.
   */
  /*public*/ void getSunLongitude(double julianDay, double &longitude, double &meanAnomaly);

  /**
   * The position of the sun at this object's current date and time,
   * in equatorial coordinates.
   * @param result fillin for the result
   * @internal
   */
  Equatorial& getSunPosition(Equatorial& result);

public:
  /**
   * Constant representing the vernal equinox.
   * For use with {@link #getSunTime getSunTime}.
   * Note: In this case, "vernal" refers to the northern hemisphere's seasons.
   * @internal
   */
  static double VERNAL_EQUINOX();

  /**
   * Constant representing the summer solstice.
   * For use with {@link #getSunTime getSunTime}.
   * Note: In this case, "summer" refers to the northern hemisphere's seasons.
   * @internal
   */
  static double SUMMER_SOLSTICE();

  /**
   * Constant representing the autumnal equinox.
   * For use with {@link #getSunTime getSunTime}.
   * Note: In this case, "autumn" refers to the northern hemisphere's seasons.
   * @internal
   */
  static double AUTUMN_EQUINOX();

  /**
   * Constant representing the winter solstice.
   * For use with {@link #getSunTime getSunTime}.
   * Note: In this case, "winter" refers to the northern hemisphere's seasons.
   * @internal
   */
  static double WINTER_SOLSTICE();

  /**
   * Find the next time at which the sun's ecliptic longitude will have
   * the desired value.
   * @internal
   */
  UDate getSunTime(double desired, UBool next);

  /**
   * Returns the time (GMT) of sunrise or sunset on the local date to which
   * this calendar is currently set.
   *
   * NOTE: This method only works well if this object is set to a
   * time near local noon.  Because of variations between the local
   * official time zone and the geographic longitude, the
   * computation can flop over into an adjacent day if this object
   * is set to a time near local midnight.
   *
   * @internal
   */
  UDate getSunRiseSet(UBool rise);

  //-------------------------------------------------------------------------
  // The Moon
  //-------------------------------------------------------------------------

  /**
   * The position of the moon at the time set on this
   * object, in equatorial coordinates.
   * @internal
   * @return const reference to internal field of calendar astronomer. Do not use outside of the lifetime of this astronomer.
   */
  const Equatorial& getMoonPosition();

  /**
   * The "age" of the moon at the time specified in this object.
   * This is really the angle between the
   * current ecliptic longitudes of the sun and the moon,
   * measured in radians.
   *
   * @see #getMoonPhase
   * @internal
   */
  double getMoonAge();

  /**
   * Calculate the phase of the moon at the time set in this object.
   * The returned phase is a <code>double</code> in the range
   * <code>0 <= phase < 1</code>, interpreted as follows:
   * <ul>
   * <li>0.00: New moon
   * <li>0.25: First quarter
   * <li>0.50: Full moon
   * <li>0.75: Last quarter
   * </ul>
   *
   * @see #getMoonAge
   * @internal
   */
  double getMoonPhase();

  class U_I18N_API MoonAge : public UMemory {
  public:
    MoonAge(double l)
      :  value(l) { }
    void set(double l) { value = l; }
    double value;
  };

  /**
   * Constant representing a new moon.
   * For use with {@link #getMoonTime getMoonTime}
   * @internal
   */
  static const MoonAge NEW_MOON();

  /**
   * Constant representing the moon's first quarter.
   * For use with {@link #getMoonTime getMoonTime}
   * @internal
   */
  static const MoonAge FIRST_QUARTER();

  /**
   * Constant representing a full moon.
   * For use with {@link #getMoonTime getMoonTime}
   * @internal
   */
  static const MoonAge FULL_MOON();

  /**
   * Constant representing the moon's last quarter.
   * For use with {@link #getMoonTime getMoonTime}
   * @internal
   */
  static const MoonAge LAST_QUARTER();

  /**
   * Find the next or previous time at which the Moon's ecliptic
   * longitude will have the desired value.
   * <p>
   * @param desired   The desired longitude.
   * @param next      <tt>true</tt> if the next occurrance of the phase
   *                  is desired, <tt>false</tt> for the previous occurrance.
   * @internal
   */
  UDate getMoonTime(double desired, UBool next);
  UDate getMoonTime(const MoonAge& desired, UBool next);

  /**
   * Returns the time (GMT) of sunrise or sunset on the local date to which
   * this calendar is currently set.
   * @internal
   */
  UDate getMoonRiseSet(UBool rise);

  //-------------------------------------------------------------------------
  // Interpolation methods for finding the time at which a given event occurs
  //-------------------------------------------------------------------------

  // private
  class U_I18N_API AngleFunc : public UMemory {
  public:
    virtual double eval(CalendarAstronomer&) = 0;
  };
  friend class AngleFunc;

  UDate timeOfAngle(AngleFunc& func, double desired,
                    double periodDays, double epsilon, UBool next);

  class U_I18N_API CoordFunc : public UMemory {
  public:
    virtual void eval(Equatorial& result, CalendarAstronomer&) = 0;
  };
  friend class CoordFunc;

  double riseOrSet(CoordFunc& func, UBool rise,
                   double diameter, double refraction,
                   double epsilon);

  //-------------------------------------------------------------------------
  // Other utility methods
  //-------------------------------------------------------------------------
private:
  /***
   * Given 'value', add or subtract 'range' until 0 <= 'value' < range.
   * The modulus operator.
   */
  inline static double normalize(double value, double range)  {
    return value - range * Math::floorDivide(value, range);
  }

  /**
   * Normalize an angle so that it's in the range 0 - 2pi.
   * For positive angles this is just (angle % 2pi), but the Java
   * mod operator doesn't work that way for negative numbers....
   */
  inline static double norm2PI(double angle)  {
    return normalize(angle, CalendarAstronomer::PI * 2.0);
  }

  /**
   * Normalize an angle into the range -PI - PI
   */
  inline static  double normPI(double angle)  {
    return normalize(angle + PI, CalendarAstronomer::PI * 2.0) - PI;
  }

  /**
   * Find the "true anomaly" (longitude) of an object from
   * its mean anomaly and the eccentricity of its orbit.  This uses
   * an iterative solution to Kepler's equation.
   *
   * @param meanAnomaly   The object's longitude calculated as if it were in
   *                      a regular, circular orbit, measured in radians
   *                      from the point of perigee.
   *
   * @param eccentricity  The eccentricity of the orbit
   *
   * @return The true anomaly (longitude) measured in radians
   */
  double trueAnomaly(double meanAnomaly, double eccentricity);

  /**
   * Return the obliquity of the ecliptic (the angle between the ecliptic
   * and the earth's equator) at the current time.  This varies due to
   * the precession of the earth's axis.
   *
   * @return  the obliquity of the ecliptic relative to the equator,
   *          measured in radians.
   */
  double eclipticObliquity();

  //-------------------------------------------------------------------------
  // Private data
  //-------------------------------------------------------------------------
private:
  /**
   * Current time in milliseconds since 1/1/1970 AD
   * @see java.util.Date#getTime
   */
  UDate fTime;

  /* These aren't used yet, but they'll be needed for sunset calculations
   * and equatorial to horizon coordinate conversions
   */
  double fLongitude;
  double fLatitude;
  double fGmtOffset;

  //
  // The following fields are used to cache calculated results for improved
  // performance.  These values all depend on the current time setting
  // of this object, so the clearCache method is provided.
  //

  double    julianDay       ;
  double    julianCentury   ;
  double    sunLongitude    ;
  double    meanAnomalySun  ;
  double    moonLongitude   ;
  double    moonEclipLong   ;
  double    meanAnomalyMoon ;
  double    eclipObliquity  ;
  double    siderealT0      ;
  double    siderealTime    ;

  void clearCache();

  Equatorial  moonPosition;
  UBool       moonPositionSet;

  /**
   * @internal
   */
  UDate local(UDate localMillis);
};

U_NAMESPACE_END

struct UHashtable;

U_NAMESPACE_BEGIN

/**
 * Cache of month -> julian day
 * @internal
 */
class U_I18N_API CalendarCache : public UMemory {
public:
  static int32_t get(CalendarCache** cache, int32_t key, UErrorCode &status);
  static void put(CalendarCache** cache, int32_t key, int32_t value, UErrorCode &status);
  virtual ~CalendarCache();
private:
  CalendarCache(int32_t size, UErrorCode& status);
  static void createCache(CalendarCache** cache, UErrorCode& status);
  /**
   * not implemented
   */
  CalendarCache();
  UHashtable *fTable;
};

U_NAMESPACE_END

#endif
#endif