'use strict';

Object.defineProperty(exports, '__esModule', { value: true });

var base = require('./base.cjs');
var coord = require('./coord.cjs');
var elementequinox = require('./elementequinox.cjs');
var nutation = require('./nutation.cjs');
var sexagesimal = require('./sexagesimal.cjs');

/**
 * @copyright 2013 Sonia Keys
 * @copyright 2016 commenthol
 * @license MIT
 * @module precess
 */

/**
 * approxAnnualPrecession returns approximate annual precision in right
 * ascension and declination.
 *
 * The two epochs should be within a few hundred years.
 * The declinations should not be too close to the poles.
 *
 * @param {coord.Equatorial} eqFrom
 * @param {Number} epochFrom - use `base.JDEToJulianYear(year)` to get epoch
 * @param {Number} epochTo - use `base.JDEToJulianYear(year)` to get epoch
 * @returns {Array}
 *  {sexa.HourAngle} seconds of right ascension
 *  {sexa.Angle} seconds of Declination
 */
function approxAnnualPrecession (eqFrom, epochFrom, epochTo) {
  const [m, na, nd] = mn(epochFrom, epochTo);
  const [sa, ca] = base['default'].sincos(eqFrom.ra);
  // (21.1) p. 132
  const Δαs = m + na * sa * Math.tan(eqFrom.dec); // seconds of RA
  const Δδs = nd * ca; // seconds of Dec
  const ra = new sexagesimal['default'].HourAngle(false, 0, 0, Δαs).rad();
  const dec = new sexagesimal['default'].Angle(false, 0, 0, Δδs).rad();
  return { ra, dec }
}

/**
 * @param {Number} epochFrom - use `base.JDEToJulianYear(year)` to get epoch
 * @param {Number} epochTo - use `base.JDEToJulianYear(year)` to get epoch
 */
function mn (epochFrom, epochTo) {
  const T = (epochTo - epochFrom) * 0.01;
  const m = 3.07496 + 0.00186 * T;
  const na = 1.33621 - 0.00057 * T;
  const nd = 20.0431 - 0.0085 * T;
  return [m, na, nd]
}

/**
 * ApproxPosition uses ApproxAnnualPrecession to compute a simple and quick
 * precession while still considering proper motion.
 *
 * @param {coord.Equatorial} eqFrom
 * @param {Number} epochFrom
 * @param {Number} epochTo
 * @param {Number} mα - in radians
 * @param {Number} mδ - in radians
 * @returns {coord.Equatorial} eqTo
 */
function approxPosition (eqFrom, epochFrom, epochTo, mα, mδ) {
  const { ra, dec } = approxAnnualPrecession(eqFrom, epochFrom, epochTo);
  const dy = epochTo - epochFrom;
  const eqTo = new coord['default'].Equatorial();
  eqTo.ra = eqFrom.ra + (ra + mα) * dy;
  eqTo.dec = eqFrom.dec + (dec + mδ) * dy;
  return eqTo
}

// constants
const d = Math.PI / 180;
const s = d / 3600;

// coefficients from (21.2) p. 134
const ζT = [2306.2181 * s, 1.39656 * s, -0.000139 * s];
const zT = [2306.2181 * s, 1.39656 * s, -0.000139 * s];
const θT = [2004.3109 * s, -0.8533 * s, -0.000217 * s];
// coefficients from (21.3) p. 134
const ζt = [2306.2181 * s, 0.30188 * s, 0.017998 * s];
const zt = [2306.2181 * s, 1.09468 * s, 0.018203 * s];
const θt = [2004.3109 * s, -0.42665 * s, -0.041833 * s];

/**
 * Precessor represents precession from one epoch to another.
 *
 * Construct with NewPrecessor, then call method Precess.
 * After construction, Precess may be called multiple times to precess
 * different coordinates with the same initial and final epochs.
 */
class Precessor {
  /**
   * constructs a Precessor object and initializes it to precess
   * coordinates from epochFrom to epochTo.
   * @param {Number} epochFrom
   * @param {Number} epochTo
   */
  constructor (epochFrom, epochTo) {
    // (21.2) p. 134
    let ζCoeff = ζt;
    let zCoeff = zt;
    let θCoeff = θt;
    if (epochFrom !== 2000) {
      const T = (epochFrom - 2000) * 0.01;
      ζCoeff = [
        base['default'].horner(T, ζT),
        0.30188 * s - 0.000344 * s * T,
        0.017998 * s
      ];
      zCoeff = [
        base['default'].horner(T, zT),
        1.09468 * s + 0.000066 * s * T,
        0.018203 * s
      ];
      θCoeff = [
        base['default'].horner(T, θT),
        -0.42665 * s - 0.000217 * s * T,
        -0.041833 * s
      ];
    }
    const t = (epochTo - epochFrom) * 0.01;
    this.ζ = base['default'].horner(t, ζCoeff) * t;
    this.z = base['default'].horner(t, zCoeff) * t;
    const θ = base['default'].horner(t, θCoeff) * t;
    this.sθ = Math.sin(θ);
    this.cθ = Math.cos(θ);
  }

  /**
   * Precess precesses coordinates eqFrom, leaving result in eqTo.
   *
   * @param {coord.Equatorial} eqFrom
   * @returns {coord.Equatorial} eqTo
   */
  precess (eqFrom) {
    // (21.4) p. 134
    const [sδ, cδ] = base['default'].sincos(eqFrom.dec);
    const [sαζ, cαζ] = base['default'].sincos(eqFrom.ra + this.ζ);
    const A = cδ * sαζ;
    const B = this.cθ * cδ * cαζ - this.sθ * sδ;
    const C = this.sθ * cδ * cαζ + this.cθ * sδ;
    const eqTo = new coord['default'].Equatorial();
    eqTo.ra = Math.atan2(A, B) + this.z;
    if (C < base['default'].CosSmallAngle) {
      eqTo.dec = Math.asin(C);
    } else {
      eqTo.dec = Math.acos(Math.hypot(A, B)); // near pole
    }
    return eqTo
  }
}

/**
 * Position precesses equatorial coordinates from one epoch to another,
 * including proper motions.
 *
 * If proper motions are not to be considered or are not applicable, pass 0, 0
 * for mα, mδ
 *
 * Both eqFrom and eqTo must be non-nil, although they may point to the same
 * struct.  EqTo is returned for convenience.
 * @param {coord.Equatorial} eqFrom
 * @param {coord.Equatorial} eqTo
 * @param {Number} epochFrom
 * @param {Number} epochTo
 * @param {Number} mα - in radians
 * @param {Number} mδ - in radians
 * @returns {coord.Equatorial} [eqTo]
 */
function position (eqFrom, epochFrom, epochTo, mα, mδ) {
  const p = new Precessor(epochFrom, epochTo);
  const t = epochTo - epochFrom;
  const eqTo = new coord['default'].Equatorial();
  eqTo.ra = eqFrom.ra + mα * t;
  eqTo.dec = eqFrom.dec + mδ * t;
  return p.precess(eqTo)
}

// coefficients from (21.5) p. 136
const ηT = [47.0029 * s, -0.06603 * s, 0.000598 * s];
const πT = [174.876384 * d, 3289.4789 * s, 0.60622 * s];
const pT = [5029.0966 * s, 2.22226 * s, -0.000042 * s];
const ηt = [47.0029 * s, -0.03302 * s, 0.000060 * s];
const πt = [174.876384 * d, -869.8089 * s, 0.03536 * s];
const pt = [5029.0966 * s, 1.11113 * s, -0.000006 * s];

/**
 * EclipticPrecessor represents precession from one epoch to another.
 *
 * Construct with NewEclipticPrecessor, then call method Precess.
 * After construction, Precess may be called multiple times to precess
 * different coordinates with the same initial and final epochs.
 */
class EclipticPrecessor {
  /**
   * constructs an EclipticPrecessor object and initializes
   * it to precess coordinates from epochFrom to epochTo.
   * @param {Number} epochFrom
   * @param {Number} epochTo
   */
  constructor (epochFrom, epochTo) {
    // (21.5) p. 136
    let ηCoeff = ηt;
    let πCoeff = πt;
    let pCoeff = pt;
    if (epochFrom !== 2000) {
      const T = (epochFrom - 2000) * 0.01;
      ηCoeff = [
        base['default'].horner(T, ηT),
        -0.03302 * s + 0.000598 * s * T,
        0.000060 * s
      ];
      πCoeff = [
        base['default'].horner(T, πT),
        -869.8089 * s - 0.50491 * s * T,
        0.03536 * s
      ];
      pCoeff = [
        base['default'].horner(T, pT),
        1.11113 * s - 0.000042 * s * T,
        -0.000006 * s
      ];
    }
    const t = (epochTo - epochFrom) * 0.01;
    this.π = base['default'].horner(t, πCoeff);
    this.p = base['default'].horner(t, pCoeff) * t;
    const η = base['default'].horner(t, ηCoeff) * t;
    this.sη = Math.sin(η);
    this.cη = Math.cos(η);
  }

  /**
   * EclipticPrecess precesses coordinates eclFrom, leaving result in eclTo.
   *
   * The same struct may be used for eclFrom and eclTo.
   * EclTo is returned for convenience.
   * @param {coord.Ecliptic} eclFrom
   * @param {coord.Ecliptic} eclTo
   * @returns {coord.Ecliptic} [eclTo]
   */
  precess (eclFrom) {
    // (21.7) p. 137
    const [sβ, cβ] = base['default'].sincos(eclFrom.lat);
    const [sd, cd] = base['default'].sincos(this.π - eclFrom.lon);
    const A = this.cη * cβ * sd - this.sη * sβ;
    const B = cβ * cd;
    const C = this.cη * sβ + this.sη * cβ * sd;
    const eclTo = new coord['default'].Ecliptic();
    eclTo.lon = this.p + this.π - Math.atan2(A, B);
    if (C < base['default'].CosSmallAngle) {
      eclTo.lat = Math.asin(C);
    } else {
      eclTo.lat = Math.acos(Math.hypot(A, B)); // near pole
    }
    return eclTo
  }

  /**
   * ReduceElements reduces orbital elements of a solar system body from one
   * equinox to another.
   *
   * This function is described in chapter 24, but is located in this
   * package so it can be a method of EclipticPrecessor.
   *
   * @param {elementequinox.Elements} eFrom
   * @returns {elementequinox.Elements} eTo
   */
  reduceElements (eFrom) {
    const ψ = this.π + this.p;
    const [si, ci] = base['default'].sincos(eFrom.inc);
    const [snp, cnp] = base['default'].sincos(eFrom.node - this.π);
    const eTo = new elementequinox['default'].Elements();
    // (24.1) p. 159
    eTo.inc = Math.acos(ci * this.cη + si * this.sη * cnp);
    // (24.2) p. 159
    eTo.node = Math.atan2(si * snp, this.cη * si * cnp - this.sη * ci) + ψ;
    // (24.3) p. 159
    eTo.peri = Math.atan2(-this.sη * snp, si * this.cη - ci * this.sη * cnp) + eFrom.peri;
    return eTo
  }
}

/**
 * eclipticPosition precesses ecliptic coordinates from one epoch to another,
 * including proper motions.
 * While eclFrom is given as ecliptic coordinates, proper motions mα, mδ are
 * still expected to be equatorial.  If proper motions are not to be considered
 * or are not applicable, pass 0, 0.
 * Both eclFrom and eclTo must be non-nil, although they may point to the same
 * struct.  EclTo is returned for convenience.
 *
 * @param {coord.Ecliptic} eclFrom,
 * @param {Number} epochFrom
 * @param {Number} epochTo
 * @param {sexa.HourAngle} mα
 * @param {sexa.Angle} mδ
 * @returns {coord.Ecliptic} eclTo
 */
function eclipticPosition (eclFrom, epochFrom, epochTo, mα, mδ) {
  const p = new EclipticPrecessor(epochFrom, epochTo);

  if (mα !== 0 || mδ !== 0) {
    const { lon, lat } = properMotion(mα.rad(), mδ.rad(), epochFrom, eclFrom);
    const t = epochTo - epochFrom;
    eclFrom.lon += lon * t;
    eclFrom.lat += lat * t;
  }
  return p.precess(eclFrom)
}

/**
 * @param {Number} mα - anual proper motion (ra)
 * @param {Number} mδ - anual proper motion (dec)
 * @param {Number} epoch
 * @param {coord.Ecliptic} ecl
 * @returns {Number[]} [mλ, mβ]
 */
function properMotion (mα, mδ, epoch, ecl) {
  const ε = nutation['default'].meanObliquity(base['default'].JulianYearToJDE(epoch));
  const [εsin, εcos] = base['default'].sincos(ε);
  const { ra, dec } = ecl.toEquatorial(ε);
  const [sα, cα] = base['default'].sincos(ra);
  const [sδ, cδ] = base['default'].sincos(dec);
  const cβ = Math.cos(ecl.lat);
  const lon = (mδ * εsin * cα + mα * cδ * (εcos * cδ + εsin * sδ * sα)) / (cβ * cβ);
  const lat = (mδ * (εcos * cδ + εsin * sδ * sα) - mα * εsin * cα * cδ) / cβ;
  return new coord['default'].Ecliptic(lon, lat)
}

/**
 * ProperMotion3D takes the 3D equatorial coordinates of an object
 * at one epoch and computes its coordinates at a new epoch, considering
 * proper motion and radial velocity.
 *
 * Radial distance (r) must be in parsecs, radial velocitiy (mr) in
 * parsecs per year.
 *
 * Both eqFrom and eqTo must be non-nil, although they may point to the same
 * struct.  EqTo is returned for convenience.
 *
 * @param {coord.Equatorial} eqFrom,
 * @param {Number} epochFrom
 * @param {Number} r
 * @param {Number} mr
 * @param {sexa.HourAngle} mα
 * @param {sexa.Angle} mδ
 * @returns {coord.Equatorial} eqTo
 */
function properMotion3D (eqFrom, epochFrom, epochTo, r, mr, mα, mδ) {
  const [sα, cα] = base['default'].sincos(eqFrom.ra);
  const [sδ, cδ] = base['default'].sincos(eqFrom.dec);
  const x = r * cδ * cα;
  const y = r * cδ * sα;
  const z = r * sδ;
  const mrr = mr / r;
  const zmδ = z * mδ.rad();
  const mx = x * mrr - zmδ * cα - y * mα.rad();
  const my = y * mrr - zmδ * sα + x * mα.rad();
  const mz = z * mrr + r * mδ.rad() * cδ;
  const t = epochTo - epochFrom;
  const xp = x + t * mx;
  const yp = y + t * my;
  const zp = z + t * mz;
  const eqTo = new coord['default'].Equatorial();
  eqTo.ra = Math.atan2(yp, xp);
  eqTo.dec = Math.atan2(zp, Math.hypot(xp, yp));
  return eqTo
}

var precess = {
  approxAnnualPrecession,
  mn,
  approxPosition,
  Precessor,
  position,
  EclipticPrecessor,
  eclipticPosition,
  properMotion,
  properMotion3D
};

exports.EclipticPrecessor = EclipticPrecessor;
exports.Precessor = Precessor;
exports.approxAnnualPrecession = approxAnnualPrecession;
exports.approxPosition = approxPosition;
exports.default = precess;
exports.eclipticPosition = eclipticPosition;
exports.mn = mn;
exports.position = position;
exports.properMotion = properMotion;
exports.properMotion3D = properMotion3D;
