"use strict";

import {
  extractObservationValuesWithinDatesInterval,
  extractObservationValuesWithinMonthInterval,
} from "./aggregateHelpers.mjs";

/**
 * Remove `null` observation values from an array of observation values
 *
 * @param {Array} obsValuesArr An array of observation values
 * @returns {Array} An array with `null` observation values removed
 */
const filterOutNullObservationValues = function (obsValuesArr) {
  return obsValuesArr.filter((obs) => obs !== null);
};

/**
 * Calculate the minimum observation values that fall within a time interval delimited by a start date and end date
 * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
 * @returns {Number} A floating-point number representing the minimum of observation values
 */
const calculateMinimumObservationValuesWithinDatesInterval = function (
  obsValuesForDaysIntervalArr
) {
  // If the observation value is `null`, skip the calculation
  return obsValuesForDaysIntervalArr.reduce((previousValue, obsValue) =>
    obsValue === null ? null : Math.min(previousValue, obsValue)
  );
};

/**
 * Calculate the maximum observation values that fall within a time interval delimited by a start date and end date
 * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
 * @returns {Number} A floating-point number representing the maximum of observation values
 */
const calculateMaximumObservationValuesWithinDatesInterval = function (
  obsValuesForDaysIntervalArr
) {
  // If the observation value is `null`, skip the calculation
  return obsValuesForDaysIntervalArr.reduce((previousValue, obsValue) =>
    obsValue === null ? null : Math.max(previousValue, obsValue)
  );
};

/**
 * Calculate the sum of observation values that fall within a time interval delimited by a start date and end date
 * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
 * @returns {Number} A floating-point number representing the sum of observation values
 */
const calculateSumOfObservationValuesWithinDatesInterval = function (
  obsValuesForDaysIntervalArr
) {
  // Remove the `null` observation values before calculating the sum
  return filterOutNullObservationValues(obsValuesForDaysIntervalArr).reduce(
    (previousValue, obsValue) => previousValue + obsValue,
    0
  );
};

/**
 * Calculate the average (arithmetic mean) of observation values that fall within a time interval delimited by a start date and end date
 * @param {Array} obsValuesForDaysIntervalArr An array of observation values that fall within our time interval
 * @returns {Number} A floating-point number representing the average (arithmetic mean) of observation values
 */
const calculateAverageOfObservationValuesWithinDatesInterval = function (
  obsValuesForDaysIntervalArr
) {
  // The observation values array should only include non-`null` values
  return (
    calculateSumOfObservationValuesWithinDatesInterval(
      obsValuesForDaysIntervalArr
    ) / filterOutNullObservationValues(obsValuesForDaysIntervalArr).length
  );
};

/**
 * Calculate the minimum of observation values that fall within a time interval delimited by the first day and last day of a calendar month
 * @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month
 * @returns {Number} A floating-point number representing the minimum of observation values within one calendar month
 */
const calculateMinimumObservationValuesWithinMonthInterval = function (
  obsValuesForMonthIntervalArr
) {
  // If the observation value is `null`, skip the calculation
  return obsValuesForMonthIntervalArr.reduce((previousValue, obsValue) =>
    obsValue === null ? null : Math.min(previousValue, obsValue)
  );
};

/**
 * Calculate the maximum of observation values that fall within a time interval delimited by the first day and last day of a calendar month
 * @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month
 * @returns {Number} A floating-point number representing the maximum of observation values within one calendar month
 */
const calculateMaximumObservationValuesWithinMonthInterval = function (
  obsValuesForMonthIntervalArr
) {
  // If the observation value is `null`, skip the calculation
  return obsValuesForMonthIntervalArr.reduce((previousValue, obsValue) =>
    obsValue === null ? null : Math.max(previousValue, obsValue)
  );
};

/**
 * Calculate the sum of observation values that fall within a time interval delimited by the first day and last day of a calendar month
 * @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month
 * @returns {Number} A floating-point number representing the sum of observation values within one calendar month
 */
const calculateSumOfObservationValuesWithinMonthInterval = function (
  obsValuesForMonthIntervalArr
) {
  // Remove the `null` observation values before calculating the sum
  return filterOutNullObservationValues(obsValuesForMonthIntervalArr).reduce(
    (previousValue, obsValue) => previousValue + obsValue,
    0
  );
};

/**
 * Calculate the average (arithmetic mean) of observation values that fall within a time interval delimited by the first day and last day of a calendar month
 * @param {Array} obsValuesForMonthIntervalArr An array of observation values that fall within one calendar month
 * @returns {Number} A floating-point number representing the average (arithmetic mean) of observation values within one calendar month
 */
const calculateAverageOfObservationValuesWithinMonthInterval = function (
  obsValuesForMonthIntervalArr
) {
  // The observation values array should only include non-`null` values
  return (
    calculateSumOfObservationValuesWithinMonthInterval(
      obsValuesForMonthIntervalArr
    ) / filterOutNullObservationValues(obsValuesForMonthIntervalArr).length
  );
};

/**
 * Calculate the minimum of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
 * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
 * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
 * @param {Array} uniqueCalendarDatesOrMonthsArr A 1*N array of unique calendar dates or calendar months strings
 * @param {String} aggregationInterval The aggregation interval as a string e.g. "daily", "monthly"
 * @returns {Array} A 1*N array that contains N nested arrays of minimum values
 */
const calculateMinimumObservationValuesWithinInterval = function (
  obsNestedArr,
  samplingRate,
  uniqueCalendarDatesOrMonthsArr,
  aggregationInterval
) {
  // Calculate minimum values of observations - daily
  // Note the use of the two nested `map` methods
  if (aggregationInterval === "daily") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarDatesArr, i) =>
      uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
        calculateMinimumObservationValuesWithinDatesInterval(
          extractObservationValuesWithinDatesInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarDate,
            uniqueCalendarDate
          )
        )
      )
    );
  }
  // Calculate minimum values of observations - monthly
  // Note the use of the two nested `map` methods
  else if (aggregationInterval === "monthly") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
      uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
        calculateMinimumObservationValuesWithinMonthInterval(
          extractObservationValuesWithinMonthInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarMonth
          )
        )
      )
    );
  }
};

/**
 * Calculate the maximum of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
 * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
 * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
 * @param {Array} uniqueCalendarDatesOrMonthsArr A 1*N array of unique calendar dates or calendar months strings
 * @param {String} aggregationInterval The aggregation interval as a string e.g. "daily", "monthly"
 * @returns {Array} A 1*N array that contains N nested arrays of maximum values
 */
const calculateMaximumObservationValuesWithinInterval = function (
  obsNestedArr,
  samplingRate,
  uniqueCalendarDatesOrMonthsArr,
  aggregationInterval
) {
  // Calculate maximum values of observations - daily
  // Note the use of the two nested `map` methods
  if (aggregationInterval === "daily") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarDatesArr, i) =>
      uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
        calculateMaximumObservationValuesWithinDatesInterval(
          extractObservationValuesWithinDatesInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarDate,
            uniqueCalendarDate
          )
        )
      )
    );
  }
  // Calculate maximum values of observations - monthly
  // Note the use of the two nested `map` methods
  else if (aggregationInterval === "monthly") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
      uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
        calculateMaximumObservationValuesWithinMonthInterval(
          extractObservationValuesWithinMonthInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarMonth
          )
        )
      )
    );
  }
};

/**
 * Calculate the sum of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
 * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
 * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
 * @param {Array} uniqueCalendarDatesOrMonthsArr A 1*N array of unique calendar dates or calendar months strings
 * @param {String} aggregationInterval The aggregation interval as a string e.g. "daily", "monthly"
 * @returns {Array} A 1*N array that contains N nested arrays of sum values
 */
const calculateSumOfObservationValuesWithinInterval = function (
  obsNestedArr,
  samplingRate,
  uniqueCalendarDatesOrMonthsArr,
  aggregationInterval
) {
  // Calculate sum of values of observations - daily
  // Note the use of the two nested `map` methods
  if (aggregationInterval === "daily") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarDatesArr, i) =>
      uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
        calculateSumOfObservationValuesWithinDatesInterval(
          extractObservationValuesWithinDatesInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarDate,
            uniqueCalendarDate
          )
        )
      )
    );
  }
  // Calculate sum of values of observations - monthly
  // Note the use of the two nested `map` methods
  else if (aggregationInterval === "monthly") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
      uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
        calculateSumOfObservationValuesWithinMonthInterval(
          extractObservationValuesWithinMonthInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarMonth
          )
        )
      )
    );
  }
};

/**
 * Calculate the average (arithmetic mean) of observation values within a time interval delimited by a start date and end date. The time interval may be daily or monthly
 * @param {Array} obsNestedArr A 1*N array that contains N nested arrays of observations (timestamp + value)
 * @param {String} samplingRate The sampling rate of observations as a string, e.g. "15min", "60min"
 * @param {Array} uniqueCalendarDatesOrMonthsArr A 1*N array of unique calendar dates or calendar months strings
 * @param {String} aggregationInterval The aggregation interval as a string e.g. "daily", "monthly"
 * @returns {Array} A 1*N array that contains N nested arrays of average (arithmetic mean) values
 */
const calculateAverageOfObservationValuesWithinInterval = function (
  obsNestedArr,
  samplingRate,
  uniqueCalendarDatesOrMonthsArr,
  aggregationInterval
) {
  // Calculate average of values of observations - daily
  // Note the use of the two nested `map` methods
  if (aggregationInterval === "daily") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarDatesArr, i) =>
      uniqueCalendarDatesArr.map((uniqueCalendarDate) =>
        calculateAverageOfObservationValuesWithinDatesInterval(
          extractObservationValuesWithinDatesInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarDate,
            uniqueCalendarDate
          )
        )
      )
    );
  }
  // Calculate average of values of observations - monthly
  // Note the use of the two nested `map` methods
  else if (aggregationInterval === "monthly") {
    return uniqueCalendarDatesOrMonthsArr.map((uniqueCalendarMonthsArr, i) =>
      uniqueCalendarMonthsArr.map((uniqueCalendarMonth) =>
        calculateAverageOfObservationValuesWithinMonthInterval(
          extractObservationValuesWithinMonthInterval(
            obsNestedArr[i],
            samplingRate,
            uniqueCalendarMonth
          )
        )
      )
    );
  }
};

export {
  calculateMinimumObservationValuesWithinInterval,
  calculateMaximumObservationValuesWithinInterval,
  calculateSumOfObservationValuesWithinInterval,
  calculateAverageOfObservationValuesWithinInterval,
};
