/**
 * @file
 *
 * @date Created  on Sep 30, 2025
 * @author Attila Kovacs
 */

#include <cstring>
#include <cctype>

#ifndef _MSC_VER
#  include <strings.h>            // strcasecmp() / strncasecmp() -- POSIX.1-2001 / 4.4BSD
#endif

/// \cond PRIVATE
#define __NOVAS_INTERNAL_API__      ///< Use definitions meant for internal use by SuperNOVAS only
/// \endcond

#include "supernovas.h"

#if defined(_MSC_VER)
#  define strcasecmp _stricmp                       /// MSVC equivalent
#  define strncasecmp _strnicmp                     /// MSVC equivalent
#endif



namespace supernovas {

static double _epoch_for(double jd) {
  return 2000.0 + (jd - NOVAS_JD_J2000) / NOVAS_JULIAN_YEAR_DAYS;
}

static std::string _name_for(const char *base, double year) {
  char s[20] = {'\0'};
  int n;

  n = snprintf(s, sizeof(s), "%s%.3f", base, year);

  // Remove trailing zeroes and decimal point.
  for(int i = n; --i >= 0; ) {
    if(s[i] == '.') {
      s[i] = '\0';
      break;
    }
    if(s[i] != '0') break;
    s[i] = '\0';
  }

  return std::string(s);
}

Equinox::Equinox(const std::string& name, double jd)
: _name(name), _system(NOVAS_MOD), _jd(jd) {
  _valid = isfinite(jd);
}

Equinox::Equinox(enum novas_reference_system system, double jd_tt)
: _name("invalid"), _system(system), _jd(jd_tt) {
  static const char *fn = "Equinox()";

  _valid = true;

  switch(system) {
    case NOVAS_GCRS:
    case NOVAS_ICRS:
      _name = std::string(NOVAS_SYSTEM_ICRS);
      _system = NOVAS_ICRS;
      _jd = NOVAS_JD_J2000;
      break;
    case NOVAS_J2000:
      _name = std::string(NOVAS_SYSTEM_J2000);
      _jd = NOVAS_JD_J2000;
      break;
    case NOVAS_MOD:
      _name =_name_for("J", epoch());
      break;
    case NOVAS_TOD:
      _name =_name_for("TOD J", epoch());
      break;
    case NOVAS_CIRS:
      _name =_name_for("CIRS J", epoch());
      break;
    default:
      novas_set_errno(EINVAL, fn, "invalid reference system: %d", system);
      _valid = false;
  }

  if(!isfinite(_jd)) {
    novas_set_errno(EINVAL, fn, "input JD date is NAN or infinite", system);
    _valid = false;
  }
}

/**
 * Checks if this equatorial system equals the equatorial system as the right-hand side, with the
 * epochs also matching within the specified precision.
 *
 * @param system  The equatorial system on the right-hand side.
 * @param dt      [s] The tolerance to epoch differences in seconds.
 * @return        `true` if the two equatorial systems are essentially the same, or else `false`.
 *
 * @since 1.6
 * @sa operator==()
 */
bool Equinox::equals(const Equinox& system, double dt) const {
  if(_system != system._system)
    if(!(is_mod() && system.is_mod()))
      return false;

  return fabs(_jd - system._jd) * Unit::day < fabs(dt);
}

/**
 * Checks if this equatorial system equals the equatorial system as the right-hand side, with the
 * epochs also matching within the specified precision.
 *
 * @param system  The equatorial system on the right-hand side.
 * @param dt      [s] The tolerance to epoch differences.
 * @return        `true` if the two equatorial systems are essentially the same, or else `false`.
 *
 * @since 1.6
 * @sa operator==()
 */
bool Equinox::equals(const Equinox& system, const Interval& dt) const {
  return equals(system, dt.seconds());
}

/**
 * Checks if this equatorial system equals the equatorial system as the right-hand side, with the
 * epochs also matching to within 1 second.
 *
 * @param system  The equatorial system on the right-hand side.
 * @return        `true` if the two equatorial systems are essentially the same, or else `false`.
 *
 * @since 1.6
 * @sa equals(), operator!=()
 */
bool Equinox::operator==(const Equinox& system) const {
  return equals(system);
}

/**
 * Checks if this equatorial system differs from the equatorial system as the right-hand side.
 *
 * @param system  The equatorial system on the right-hand side.
 * @return        `true` if the two equatorial systems are differ, or else `false`.
 *
 * @since 1.6
 * @sa operator==()
 */
bool Equinox::operator!=(const Equinox& system) const {
  return !equals(system);
}


/**
 * Checks if this equatorial system is the International Celestial Reference System (ICRS) or
 * one of its variants, such as GCRS, BCRS, HCRS. ICRS is the standard catalog system in the IAU
 * 2000 convention. It has variants, depending on the specific location of the origin, such as
 * the geocdentric GCRS, the barycentric BCRS. And while ICRS is defined against the distant
 * quasars, a specific and generally equivalent realization of it (HCRS) is defined against the
 * Hipparcos catalog.
 *
 * In SuperNOVAS, they are all treated as equivalent, since in SuperNOVAS the coordinate system
 * defines only the direction of the equatorial _x, y, z_ axes, while the origin is defined
 * separately by the observer location.
 *
 * @return    `true` if this equatorial system is the ICRS, or else `false`.
 *
 * @since 1.6
 * @sa is_mod(), is_true()
 */
bool Equinox::is_icrs() const {
  return _system == NOVAS_ICRS;
}

/**
 * Checks if this equatorial system is a Mean-of-Date (MOD) system. MODs include precession, but
 * not nutation, and have been commonly used for catalogs (e.g. J2000, B1950, FK4, or HIP) prior
 * to the IAU 2000 convention, when ICRS became the new standard catalog system.
 *
 * @return    `true` if this equatorial system is a Mean-of-Date (MOD) catalog system, or else
 *            `false`.
 *
 * @since 1.6
 * @sa is_icrs(), is_true()
 */
bool Equinox::is_mod() const {
  return _system == NOVAS_MOD || _system == NOVAS_J2000;
}

/**
 * Checks if this equatorial system is based on the true dynamical equator of date. If `true`, it
 * may be a True-of-Date (TOD) system with the origin at the true equinox of date, or else
 * the Celestial Intermediate Reference System (CIRS) with its origin at the Celestial
 * Intermediate Origin (CIO).
 *
 * @return    `true` if this equatorial system is defined on the true dynamical equator of date, or
 *            else `false`.
 *
 * @since 1.6
 * @sa is_icrs(), is_mod()
 */
bool Equinox::is_true() const {
  return _system == NOVAS_CIRS || _system == NOVAS_TOD;
}

/**
 * Returns the (TT-based) Julian date that corresponds to this system instance. That is, it
 * returns the date for which the mean dynamical equator best matches the equator of this catalog
 * system.
 *
 * @return        [day] the (TT-based) Julian date at which the mean dynamical equator matches
 *                this system. E.g. for 'ICRS' it will return the Julian date for the J2000.0
 *                epoch.
 *
 * @since 1.6
 * @sa mjd(), epoch(), Time
 */
double Equinox::jd() const {
  return _jd;
}

/**
 * Returns the (TT-based) Modified Julian Date (MJD) that corresponds to this system instance.
 * That is, it returns the MJD for which the mean dynamical equator best matches the equator of
 * this catalog system.
 *
 * @return        [day] the (TT-based) Modified Julian Date (MJD) at which the mean dynamical
 *                equator matches this system. E.g. for 'ICRS' it will return the MJD for the
 *                J2000.0 epoch.
 *
 * @since 1.6
 * @sa jd(), epoch(), Time
 */
double Equinox::mjd() const {
  return _jd - NOVAS_JD_MJD0;
}

/**
 * Returns the coordinate reference system type for this equatorial system.
 *
 * @return      the coordinate reference system type
 *
 * @since 1.6
 */
enum novas_reference_system Equinox::system_type() const {
  return _system;
}

/**
 * Returns the type of equator used in this equatorial coordinate system.
 *
 * @return    the type of equator used in this equatorial system.
 *
 * @since 1.6
 */
enum novas_equator_type Equinox::equator_type() const {
  switch(system_type()) {
    case NOVAS_GCRS:
    case NOVAS_ICRS:
      return NOVAS_GCRS_EQUATOR;
    case NOVAS_J2000:
    case NOVAS_MOD:
      return NOVAS_MEAN_EQUATOR;
    case NOVAS_TOD:
    case NOVAS_CIRS:
      return NOVAS_TRUE_EQUATOR;
    default:
      return (enum novas_equator_type) -1;
  }
}

/**
 * Returns the years of the Julian epoch that matches this system. E.g. for both 'ICRS' and
 * 'J2000' this will return 2000.0.
 *
 * @return      [yr] The Julian epoch year of this catalog system.
 *
 * @since 1.6
 * @sa jd(), mjd(), Time::epoch()
 */
double Equinox::epoch() const {
  return _epoch_for(_jd);
}

/**
 * Returns a reference to the name of this system.
 *
 * @return    A reference to the string that stores the system's name
 *
 * @since 1.6
 */
const std::string& Equinox::name() const {
  return _name;
}

/**
 * Provides a string representation of this system. It is pretty much the same as calling name(),
 * except it returns a copy rather than the reference.
 *
 * @return    A string identification of this catalog system.
 *
 * @since 1.6
 */
std::string Equinox::to_string() const {
  return std::string(_name);
}

/**
 * Returns a new equinox instance from a string, such as 'ICRS', 'J2000', 'FK5', B1950', or
 * 'HIP'; or else `{}`. It is generally preferable to use one of the other static
 * initializers, such as icrs(), or j2000(), which are guaranteed to return a valid instance.
 *
 * @param name      The name that defining the type of catalog system. If only a year is give,
 *                  then it prior to 1984.0 they map to Besselian epochs, e.g. '1950' &rarr;
 *                  'B1950', whereas for later dates Julian epochs are assumed, e.g. '2000'
 *                  &rarr; 'J2000'.
 * @return          an optional containing the corresponding valid equatorial system, or else
 *                  Equinox::undefined().
 *
 * @since 1.6
 * @sa from_system_type()
 * @sa is_valid(), icrs(), j2000(), b1950(), b1900()
 */
Equinox Equinox::from_string(const std::string& name) {
  const char *s = name.c_str();
  enum novas_reference_system sys = NOVAS_MOD;
  double ejd;

  if(name.length() < 3) {
    novas_set_errno(EINVAL, "Equinox::from_string()", "No catalog system matching: '%s'", name.c_str());
    return Equinox::undefined();
  }

  if(strcasecmp(&s[1], "CRS") == 0)
    return Equinox(NOVAS_ICRS, NOVAS_JD_J2000);

  if(strcasecmp(s, "FK6") == 0)
    return Equinox(NOVAS_ICRS, NOVAS_JD_J2000);

  if(strcasecmp(s, "FK5") == 0)
    return Equinox(NOVAS_J2000, NOVAS_JD_J2000);

  if(strcasecmp(s, "FK4") == 0)
    return Equinox(NOVAS_MOD, NOVAS_JD_B1950);

  if(strncasecmp(s, "TOD ", 4) == 0) {
    sys = NOVAS_TOD;
    ejd = novas_epoch(&s[4]);
  }
  else if(strncasecmp(s, "MOD ", 4) == 0) {
    s = &s[4];
    ejd = novas_epoch(s);
  }
  else if(strncasecmp(s, "CIRS ", 5) == 0) {
    sys = NOVAS_CIRS;
    ejd = novas_epoch(&s[5]);
  }
  else {
    ejd = novas_epoch(s);
  }

  if(isnan(ejd)) {
    novas_set_errno(EINVAL, "Equinox::from_string()", "No catalog system matching: '%s'", name.c_str());
    return Equinox::undefined();
  }

  if(sys == NOVAS_MOD && fabs(ejd - NOVAS_JD_J2000) * Unit::day < Unit::s)
    sys = NOVAS_J2000;

  return Equinox(sys, ejd);
}

/**
 * Returns a new equinox instance given a coordinate reference system type and a date.
 *
 * @param system    The coordinate reference system type
 * @param jd_tt     [day] The (TT-based) Julian date (for dynamical systems). TDB-based dates
 *                  may be used here also without affecting precision of coordinate
 *                  transformations at the micro-arcsecond (&mu;as) level.
 * @return          an optional containing the corresponding valid equatorial system, or else
 *                  an undefined (invalid) equinox.
 *
 * @since 1.6
 * @sa from_string()
 */
Equinox Equinox::from_system_type(enum novas_reference_system system, double jd_tt) {
  static const char *fn = "Equatorial::for_reference_system()";

  switch(system) {
    case NOVAS_TIRS:
    case NOVAS_ITRS:
      novas_set_errno(EINVAL, fn, "No equinox for Earth-rotating reference systems");
      return Equinox::undefined();
    default:
      Equinox e(system, jd_tt);
      if(!e.is_valid())
        novas_trace_invalid("Equinox::from_system_type()");
      return e;
  }
}

/**
 * Returns a new equinox instance given a coordinate reference system type and a time.
 *
 * @param system    The coordinate reference system type
 * @param time      [day] astrometric time (for dynamical systems).
 * @return          an optional containing the corresponding valid equatorial system, or else
 *                  an undefined (invalid) equinox.
 *
 * @since 1.6
 * @sa from_string()
 */
Equinox Equinox::from_system_type(enum novas_reference_system system, const Time& time) {
  return from_system_type(system, time.jd());
}

/**
 * Mean-of-date (MOD) dynamical coordinate system, with respect to the mean dynamical equator and
 * equinox at the specified Julian epoch. MOD coordinates take into account Earth's slow
 * precession but not nutation. Julian-date based MODs were commonly used for catalogs, such as
 * J2000, or HIP.
 *
 * @param jd_tt     [day] TT-based Julian day.  TDB-based days may be used here also without
 *                  affecting precision of coordinate transformations at the micro-arcsecond
 *                  (&mu;as) level.
 * @return          A reference system with the mean dynamical equator of date, with origin at
 *                  the mean equinox of date.
 *
 * @since 1.6
 * @sa mod_at_besselial_epoch(), j2000(), hip()
 */
Equinox Equinox::mod(double jd_tt) {
  Equinox e(NOVAS_MOD, jd_tt);
  if(!e.is_valid())
    novas_trace_invalid("Equinox::mod()");
  return e;
}

/**
 * Mean-of-date (MOD) dynamical coordinate system, with respect to the mean dynamical equator and
 * equinox at the specified Julian epoch. MOD coordinates take into account Earth's slow
 * precession but not nutation. Julian-date based MODs were commonly used for catalogs, such as
 * J2000, or HIP.
 *
 * @param time      astrometric time specifying the coordinate epoch.
 * @return          A reference system with the mean dynamical equator of date, with origin at
 *                  the mean equinox of date.
 *
 * @since 1.6
 * @sa mod_at_besselial_epoch(), j2000(), hip()
 */
Equinox Equinox::mod(const Time& time) {
  return mod(time.jd());
}

/**
 * Mean-of-date (MOD) dynamical coordinate system, with respect to the mean dynamical equator and
 * equinox at the specified Besselian epoch. MOD coordinates take into account Earth's
 * precession but not nutation. Besselian-date based MODs, now a historical relic, were once
 * commonly used for catalog systems, such as B1900, or B1950.
 *
 * @param year      [yr] UTC-based decimal calendar year.
 * @return          A reference system with the mean dynamical equator of date, with origin at
 *                  the mean equinox of date.
 *
 * @since 1.6
 * @sa at_julian_date(), b1900(), b1950()
 */
Equinox Equinox::mod_at_besselian_epoch(double year) {
  Equinox e(_name_for("B", year), NOVAS_JD_B1950 + (year - 1950.0) * Unit::besselian_year / Unit::day);
  if(!e.is_valid())
    novas_trace_invalid("Equinox::mod_at_besselian_epoch()");
  return e;
}

/**
 * Returns a True-of-Date (TOD) equatorial system for equinox at the given (TT-based) Julian date.
 * It is the system on the true dynamical equator of date, with its origin at the true equinox of
 * date.
 *
 * @param jd_tt     [day] The (TT-based) Julian date for when of the true dynamical equator and
 *                  true equinox of date define the returned equatorial coordinate system.
 * @return          the equatorial coordinate system based on the true dynamical equator and
 *                  equinox of date.
 *
 * @since 1.6
 * @sa cirs()
 */
Equinox Equinox::tod(double jd_tt) {
  Equinox e(NOVAS_TOD, jd_tt);
  if(!e.is_valid())
    novas_trace_invalid("Equinox::tod()");
  return e;
}

/**
 * Returns a True-of-Date (TOD) equatorial system for the given (TT-based) Julian date. It is
 * the system on the true dynamical equator of date, with its origin at the true equinox of
 * date.
 *
 * @param time      astrometric time specifying the coordinate epoch,
 * @return          the equatorial coordinate system based on the true dynamical equator and
 *                  equinox of date.
 *
 * @since 1.6
 * @sa cirs()
 */
Equinox Equinox::tod(const Time& time) {
  return tod(time.jd());
}

/**
 * Returns a Celestial Intermediate Reference System (CIRS) equatorial system for the given
 * (TT-based) Julian date. It is the system on the true dynamical equator of date, with its origin
 * at the Celestial Intermediate Origin (CIO) of date.
 *
 * @param jd_tt     [day] The (TT-based) Julian date for when of the true dynamical equator and
 *                  true equinox of date define the returned equatorial coordinate system,
 * @return          the equatorial coordinate system based on the true dynamical equator, with
 *                  its origin at the CIO
 *
 * @since 1.6
 * @sa tod()
 */
Equinox Equinox::cirs(double jd_tt) {
  Equinox e(NOVAS_CIRS, jd_tt);
  if(!e.is_valid())
    novas_trace_invalid("Equinox::cirs()");
  return e;
}

/**
 * Returns a Celestial Intermediate Reference System (CIRS) equatorial system for the given
 * (TT-based) Julian date. It is the system on the true dynamical equator of date, with its origin
 * at the Celestial Intermediate Origin (CIO) of date.
 *
 * @param time      astrometric time specifying the coordinate epoch.
 * @return          the equatorial coordinate system based on the true dynamical equator, with
 *                  its origin at the CIO
 *
 * @since 1.6
 * @sa tod()
 */
Equinox Equinox::cirs(const Time& time) {
  return cirs(time.jd());
}

/**
 * International Celestial Reference System (ICRS) is the IAU standard catalog coordinate system.
 * It is defined by distant quasars, and is aligned with the J2000 dynamical equator within 22 mas.
 * In SuperNOVAS ICRS is the same as GCRS (the Geocentric Celestial Reference System) or BCRS (the
 * Barycentric International Reference System), which have the same alignment and differ only in the
 * location of their origin. In SuperNOVAS, the origin is determined by the @ref Observer location,
 * while the coordinate system defines only the orientation of the celestial pole. Thus, there is
 * no need to distinguish between these related systems explicitly in SuperNOVAS.
 *
 * ICRS is also the system used for the 6th Catalog of Fundamental Stars (FK6).
 *
 * @return A reference to a reusable statically allocated ICRS system instance.
 *
 * @since 1.6
 * @sa NOVAS_ICRS, NOVAS_GCRS, NOVAS_SYSTEM_ICRS
 */
const Equinox& Equinox::icrs() {
  static const Equinox _icrs = Equinox(NOVAS_ICRS, NOVAS_JD_J2000);
  return _icrs;
}

/**
 * The system of the mean dynamical equator at the J1991.25 epoch, which is adopted as the nominal
 * mean epoch of the Hipparcos catalog.
 *
 * @return A reference to a reusable statically allocated Hipparcos coordinate system instance.
 *
 * @since 1.6
 * @sa icrs(), mod(), Time::hip() NOVAS_JD_HIP, NOVAS_SYSTEM_HIP
 */
const Equinox& Equinox::hip() {
  static const Equinox _hip = Equinox(NOVAS_MOD, NOVAS_JD_HIP);
  return _hip;
}

/**
 * The system of the dynamical equator at the J2000 epoch (12 TT, 1 January 2000). This was a
 * commonly used catalog coordinate system before the advent of the IAU 2000 standard ICRS system.
 * It is also known as FK5, since the 5th realization of the fundamental catalog of stars used
 * J2000 also.
 *
 * @return A reference to a reusable statically allocated J2000 coordinate system instance.
 *
 * @since 1.6
 * @sa icrs(), mod(), Time::j2000(), NOVAS_JD_J2000, NOVAS_SYSTEM_J2000
 */
const Equinox& Equinox::j2000() {
  static const Equinox _j2000 = Equinox(NOVAS_J2000, NOVAS_JD_J2000);
  return _j2000;
}

/**
 * The system of the dynamical equator at the B1950 epoch (0 UTC, 1 January 1950). This was a
 * commonly used catalog coordinate system of old. It is also known as FK4, since the 4th
 * realization of the fundamental catalog of stars used B1950 also.
 *
 * B1950 is also the system used for the 4th Catalog of Fundamental Stars (FK4).
 *
 * @return A reference to a reusable statically allocated B1950 coordinate system instance.
 *
 * @since 1.6
 * @sa icrs(), mod(), Time::b1950(), NOVAS_JD_B1950, NOVAS_SYSTEM_B1950
 */
const Equinox& Equinox::b1950() {
  static const Equinox _b1950 = Equinox::mod_at_besselian_epoch(1950.0);
  return _b1950;
}

/**
 * The system of the dynamical equator at the B1900 epoch (0 UTC, 1 January 1900). This was a
 * commonly used catalog coordinate system of old.
 *
 * @return A reference to a reusable statically allocated B1900 coordinate system instance.
 *
 * @since 1.6
 * @sa icrs(), mod(), Time::b1900(), NOVAS_JD_B1900, NOVAS_SYSTEM_B1900
 */
const Equinox& Equinox::b1900() {
  static const Equinox _b1900 = Equinox::mod_at_besselian_epoch(1900.0);
  return _b1900;
}

/**
 * Returns a reference to a statically defined standard invalid equatorial system. This invalid
 * equatorial system may be used inside any object that is invalid itself.
 *
 * @return    a reference to a static standard invalid equatorial system.
 *
 * @since 1.6
 */
const Equinox& Equinox::undefined() {
  static const Equinox _invalid = Equinox();
  return _invalid;
}

} // namespace supernovas
