text: import binarySearch from "./binarySearch.js";
  import defaultValue from "./defaultValue.js";
  import defined from "./defined.js";
  import EarthOrientationParametersSample from "./EarthOrientationParametersSample.js";
  import JulianDate from "./JulianDate.js";
  import LeapSecond from "./LeapSecond.js";
  import Resource from "./Resource.js";
  import RuntimeError from "./RuntimeError.js";
  import TimeConstants from "./TimeConstants.js";
  import TimeStandard from "./TimeStandard.js";
  
  /**
   * Specifies Earth polar motion coordinates and the difference between UT1 and UTC.
   * These Earth Orientation Parameters (EOP) are primarily used in the transformation from
   * the International Celestial Reference Frame (ICRF) to the International Terrestrial
   * Reference Frame (ITRF).
   *
   * @alias EarthOrientationParameters
   * @constructor
   *
   * @param {Object} [options] Object with the following properties:
   * @param {Resource|String} [options.url] The URL from which to obtain EOP data.  If neither this
   *                 parameter nor options.data is specified, all EOP values are assumed
   *                 to be 0.0.  If options.data is specified, this parameter is
   *                 ignored.
   * @param {Object} [options.data] The actual EOP data.  If neither this
   *                 parameter nor options.data is specified, all EOP values are assumed
   *                 to be 0.0.
   * @param {Boolean} [options.addNewLeapSeconds=true] True if leap seconds that
   *                  are specified in the EOP data but not in {@link JulianDate.leapSeconds}
   *                  should be added to {@link JulianDate.leapSeconds}.  False if
   *                  new leap seconds should be handled correctly in the context
   *                  of the EOP data but otherwise ignored.
   *
   * @example
   * // An example EOP data file, EOP.json:
   * {
   *   "columnNames" : ["dateIso8601","modifiedJulianDateUtc","xPoleWanderRadians","yPoleWanderRadians","ut1MinusUtcSeconds","lengthOfDayCorrectionSeconds","xCelestialPoleOffsetRadians","yCelestialPoleOffsetRadians","taiMinusUtcSeconds"],
   *   "samples" : [
   *      "2011-07-01T00:00:00Z",55743.0,2.117957047295119e-7,2.111518721609984e-6,-0.2908948,-2.956e-4,3.393695767766752e-11,3.3452143996557983e-10,34.0,
   *      "2011-07-02T00:00:00Z",55744.0,2.193297093339541e-7,2.115460256837405e-6,-0.29065,-1.824e-4,-8.241832578862112e-11,5.623838700870617e-10,34.0,
   *      "2011-07-03T00:00:00Z",55745.0,2.262286080161428e-7,2.1191157519929706e-6,-0.2905572,1.9e-6,-3.490658503988659e-10,6.981317007977318e-10,34.0
   *   ]
   * }
   *
   * @example
   * // Loading the EOP data
   * const eop = new Cesium.EarthOrientationParameters({ url : 'Data/EOP.json' });
   * Cesium.Transforms.earthOrientationParameters = eop;
   *
   * @private
   */
  function EarthOrientationParameters(options) {
    options = defaultValue(options, defaultValue.EMPTY_OBJECT);
  
    this._dates = undefined;
    this._samples = undefined;
  
    this._dateColumn = -1;
    this._xPoleWanderRadiansColumn = -1;
    this._yPoleWanderRadiansColumn = -1;
    this._ut1MinusUtcSecondsColumn = -1;
    this._xCelestialPoleOffsetRadiansColumn = -1;
    this._yCelestialPoleOffsetRadiansColumn = -1;
    this._taiMinusUtcSecondsColumn = -1;
  
    this._columnCount = 0;
    this._lastIndex = -1;
  
    this._downloadPromise = undefined;
    this._dataError = undefined;
  
    this._addNewLeapSeconds = defaultValue(options.addNewLeapSeconds, true);
  
    if (defined(options.data)) {
      // Use supplied EOP data.
      onDataReady(this, options.data);
    } else if (defined(options.url)) {
      const resource = Resource.createIfNeeded(options.url);
  
      // Download EOP data.
      const that = this;
      this._downloadPromise = resource
        .fetchJson()
        .then(function (eopData) {
          onDataReady(that, eopData);
        })
        .catch(function () {
          that._dataError = `An error occurred while retrieving the EOP data from the URL ${resource.url}.`;
        });
    } else {
      // Use all zeros for EOP data.
      onDataReady(this, {
        columnNames: [
          "dateIso8601",
          "modifiedJulianDateUtc",
          "xPoleWanderRadians",
          "yPoleWanderRadians",
          "ut1MinusUtcSeconds",
          "lengthOfDayCorrectionSeconds",
          "xCelestialPoleOffsetRadians",
          "yCelestialPoleOffsetRadians",
          "taiMinusUtcSeconds",
        ],
        samples: [],
      });
    }
  }
  
  /**
   * A default {@link EarthOrientationParameters} instance that returns zero for all EOP values.
   */
  EarthOrientationParameters.NONE = Object.freeze({
    getPromiseToLoad: function () {
      return Promise.resolve();
    },
    compute: function (date, result) {
      if (!defined(result)) {
        result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
      } else {
        result.xPoleWander = 0.0;
        result.yPoleWander = 0.0;
        result.xPoleOffset = 0.0;
        result.yPoleOffset = 0.0;
        result.ut1MinusUtc = 0.0;
      }
      return result;
    },
  });
  
  /**
   * Gets a promise that, when resolved, indicates that the EOP data has been loaded and is
   * ready to use.
   *
   * @returns {Promise<void>} The promise.
   */
  EarthOrientationParameters.prototype.getPromiseToLoad = function () {
    return Promise.resolve(this._downloadPromise);
  };
  
  /**
   * Computes the Earth Orientation Parameters (EOP) for a given date by interpolating.
   * If the EOP data has not yet been download, this method returns undefined.
   *
   * @param {JulianDate} date The date for each to evaluate the EOP.
   * @param {EarthOrientationParametersSample} [result] The instance to which to copy the result.
   *        If this parameter is undefined, a new instance is created and returned.
   * @returns {EarthOrientationParametersSample} The EOP evaluated at the given date, or
   *          undefined if the data necessary to evaluate EOP at the date has not yet been
   *          downloaded.
   *
   * @exception {RuntimeError} The loaded EOP data has an error and cannot be used.
   *
   * @see EarthOrientationParameters#getPromiseToLoad
   */
  EarthOrientationParameters.prototype.compute = function (date, result) {
    // We cannot compute until the samples are available.
    if (!defined(this._samples)) {
      if (defined(this._dataError)) {
        throw new RuntimeError(this._dataError);
      }
  
      return undefined;
    }
  
    if (!defined(result)) {
      result = new EarthOrientationParametersSample(0.0, 0.0, 0.0, 0.0, 0.0);
    }
  
    if (this._samples.length === 0) {
      result.xPoleWander = 0.0;
      result.yPoleWander = 0.0;
      result.xPoleOffset = 0.0;
      result.yPoleOffset = 0.0;
      result.ut1MinusUtc = 0.0;
      return result;
    }
  
    const dates = this._dates;
    const lastIndex = this._lastIndex;
  
    let before = 0;
    let after = 0;
    if (defined(lastIndex)) {
      const previousIndexDate = dates[lastIndex];
      const nextIndexDate = dates[lastIndex + 1];
      const isAfterPrevious = JulianDate.lessThanOrEquals(
        previousIndexDate,
        date
      );
      const isAfterLastSample = !defined(nextIndexDate);
      const isBeforeNext =
        isAfterLastSample || JulianDate.greaterThanOrEquals(nextIndexDate, date);
  
      if (isAfterPrevious && isBeforeNext) {
        before = lastIndex;
  
        if (!isAfterLastSample && nextIndexDate.equals(date)) {
          ++before;
        }
        after = before + 1;
  
        interpolate(this, dates, this._samples, date, before, after, result);
        return result;
      }
    }
  
    let index = binarySearch(dates, date, JulianDate.compare, this._dateColumn);
    if (index >= 0) {
      // If the next entry is the same date, use the later entry.  This way, if two entries
      // describe the same moment, one before a leap second and the other after, then we will use
      // the post-leap second data.
      if (index < dates.length - 1 && dates[index + 1].equals(date)) {
        ++index;
      }
      before = index;
      after = index;
    } else {
      after = ~index;
      before = after - 1;
  
      // Use the first entry if the date requested is before the beginning of the data.
      if (before < 0) {
        before = 0;
      }
    }
  
    this._lastIndex = before;
  
    interpolate(this, dates, this._samples, date, before, after, result);
    return result;
  };
  
  function compareLeapSecondDates(leapSecond, dateToFind) {
    return JulianDate.compare(leapSecond.julianDate, dateToFind);
  }
  
  function onDataReady(eop, eopData) {
    if (!defined(eopData.columnNames)) {
      eop._dataError =
        "Error in loaded EOP data: The columnNames property is required.";
      return;
    }
  
    if (!defined(eopData.samples)) {
      eop._dataError =
        "Error in loaded EOP data: The samples property is required.";
      return;
    }
  
    const dateColumn = eopData.columnNames.indexOf("modifiedJulianDateUtc");
    const xPoleWanderRadiansColumn = eopData.columnNames.indexOf(
      "xPoleWanderRadians"
    );
    const yPoleWanderRadiansColumn = eopData.columnNames.indexOf(
      "yPoleWanderRadians"
    );
    const ut1MinusUtcSecondsColumn = eopData.columnNames.indexOf(
      "ut1MinusUtcSeconds"
    );
    const xCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf(
      "xCelestialPoleOffsetRadians"
    );
    const yCelestialPoleOffsetRadiansColumn = eopData.columnNames.indexOf(
      "yCelestialPoleOffsetRadians"
    );
    const taiMinusUtcSecondsColumn = eopData.columnNames.indexOf(
      "taiMinusUtcSeconds"
    );
  
    if (
      dateColumn < 0 ||
      xPoleWanderRadiansColumn < 0 ||
      yPoleWanderRadiansColumn < 0 ||
      ut1MinusUtcSecondsColumn < 0 ||
      xCelestialPoleOffsetRadiansColumn < 0 ||
      yCelestialPoleOffsetRadiansColumn < 0 ||
      taiMinusUtcSecondsColumn < 0
    ) {
      eop._dataError =
        "Error in loaded EOP data: The columnNames property must include modifiedJulianDateUtc, xPoleWanderRadians, yPoleWanderRadians, ut1MinusUtcSeconds, xCelestialPoleOffsetRadians, yCelestialPoleOffsetRadians, and taiMinusUtcSeconds columns";
      return;
    }
  
    const samples = (eop._samples = eopData.samples);
    const dates = (eop._dates = []);
  
    eop._dateColumn = dateColumn;
    eop._xPoleWanderRadiansColumn = xPoleWanderRadiansColumn;
    eop._yPoleWanderRadiansColumn = yPoleWanderRadiansColumn;
    eop._ut1MinusUtcSecondsColumn = ut1MinusUtcSecondsColumn;
    eop._xCelestialPoleOffsetRadiansColumn = xCelestialPoleOffsetRadiansColumn;
    eop._yCelestialPoleOffsetRadiansColumn = yCelestialPoleOffsetRadiansColumn;
    eop._taiMinusUtcSecondsColumn = taiMinusUtcSecondsColumn;
  
    eop._columnCount = eopData.columnNames.length;
    eop._lastIndex = undefined;
  
    let lastTaiMinusUtc;
  
    const addNewLeapSeconds = eop._addNewLeapSeconds;
  
    // Convert the ISO8601 dates to JulianDates.
    for (let i = 0, len = samples.length; i < len; i += eop._columnCount) {
      const mjd = samples[i + dateColumn];
      const taiMinusUtc = samples[i + taiMinusUtcSecondsColumn];
      const day = mjd + TimeConstants.MODIFIED_JULIAN_DATE_DIFFERENCE;
      const date = new JulianDate(day, taiMinusUtc, TimeStandard.TAI);
      dates.push(date);
  
      if (addNewLeapSeconds) {
        if (taiMinusUtc !== lastTaiMinusUtc && defined(lastTaiMinusUtc)) {
          // We crossed a leap second boundary, so add the leap second
          // if it does not already exist.
          const leapSeconds = JulianDate.leapSeconds;
          const leapSecondIndex = binarySearch(
            leapSeconds,
            date,
            compareLeapSecondDates
          );
          if (leapSecondIndex < 0) {
            const leapSecond = new LeapSecond(date, taiMinusUtc);
            leapSeconds.splice(~leapSecondIndex, 0, leapSecond);
          }
        }
        lastTaiMinusUtc = taiMinusUtc;
      }
    }
  }
  
  function fillResultFromIndex(eop, samples, index, columnCount, result) {
    const start = index * columnCount;
    result.xPoleWander = samples[start + eop._xPoleWanderRadiansColumn];
    result.yPoleWander = samples[start + eop._yPoleWanderRadiansColumn];
    result.xPoleOffset = samples[start + eop._xCelestialPoleOffsetRadiansColumn];
    result.yPoleOffset = samples[start + eop._yCelestialPoleOffsetRadiansColumn];
    result.ut1MinusUtc = samples[start + eop._ut1MinusUtcSecondsColumn];
  }
  
  function linearInterp(dx, y1, y2) {
    return y1 + dx * (y2 - y1);
  }
  
  function interpolate(eop, dates, samples, date, before, after, result) {
    const columnCount = eop._columnCount;
  
    // First check the bounds on the EOP data
    // If we are after the bounds of the data, return zeros.
    // The 'before' index should never be less than zero.
    if (after > dates.length - 1) {
      result.xPoleWander = 0;
      result.yPoleWander = 0;
      result.xPoleOffset = 0;
      result.yPoleOffset = 0;
      result.ut1MinusUtc = 0;
      return result;
    }
  
    const beforeDate = dates[before];
    const afterDate = dates[after];
    if (beforeDate.equals(afterDate) || date.equals(beforeDate)) {
      fillResultFromIndex(eop, samples, before, columnCount, result);
      return result;
    } else if (date.equals(afterDate)) {
      fillResultFromIndex(eop, samples, after, columnCount, result);
      return result;
    }
  
    const factor =
      JulianDate.secondsDifference(date, beforeDate) /
      JulianDate.secondsDifference(afterDate, beforeDate);
  
    const startBefore = before * columnCount;
    const startAfter = after * columnCount;
  
    // Handle UT1 leap second edge case
    let beforeUt1MinusUtc = samples[startBefore + eop._ut1MinusUtcSecondsColumn];
    let afterUt1MinusUtc = samples[startAfter + eop._ut1MinusUtcSecondsColumn];
  
    const offsetDifference = afterUt1MinusUtc - beforeUt1MinusUtc;
    if (offsetDifference > 0.5 || offsetDifference < -0.5) {
      // The absolute difference between the values is more than 0.5, so we may have
      // crossed a leap second.  Check if this is the case and, if so, adjust the
      // afterValue to account for the leap second.  This way, our interpolation will
      // produce reasonable results.
      const beforeTaiMinusUtc =
        samples[startBefore + eop._taiMinusUtcSecondsColumn];
      const afterTaiMinusUtc =
        samples[startAfter + eop._taiMinusUtcSecondsColumn];
      if (beforeTaiMinusUtc !== afterTaiMinusUtc) {
        if (afterDate.equals(date)) {
          // If we are at the end of the leap second interval, take the second value
          // Otherwise, the interpolation below will yield the wrong side of the
          // discontinuity
          // At the end of the leap second, we need to start accounting for the jump
          beforeUt1MinusUtc = afterUt1MinusUtc;
        } else {
          // Otherwise, remove the leap second so that the interpolation is correct
          afterUt1MinusUtc -= afterTaiMinusUtc - beforeTaiMinusUtc;
        }
      }
    }
  
    result.xPoleWander = linearInterp(
      factor,
      samples[startBefore + eop._xPoleWanderRadiansColumn],
      samples[startAfter + eop._xPoleWanderRadiansColumn]
    );
    result.yPoleWander = linearInterp(
      factor,
      samples[startBefore + eop._yPoleWanderRadiansColumn],
      samples[startAfter + eop._yPoleWanderRadiansColumn]
    );
    result.xPoleOffset = linearInterp(
      factor,
      samples[startBefore + eop._xCelestialPoleOffsetRadiansColumn],
      samples[startAfter + eop._xCelestialPoleOffsetRadiansColumn]
    );
    result.yPoleOffset = linearInterp(
      factor,
      samples[startBefore + eop._yCelestialPoleOffsetRadiansColumn],
      samples[startAfter + eop._yCelestialPoleOffsetRadiansColumn]
    );
    result.ut1MinusUtc = linearInterp(
      factor,
      beforeUt1MinusUtc,
      afterUt1MinusUtc
    );
    return result;
  }
  export default EarthOrientationParameters;
  