"use strict";

/**
 * Get the selected option(s) from a dropdown list
 *
 * @param {String} selectorStr A CSS selector string representing the dropdown list
 * @returns {Array} An array of string(s) representing the value(s) of the selected `<option>` elements
 */
const getSelectedOptionsFromDropDownList = function (selectorStr) {
  // Array to store our final result
  const selectedOptionsArr = [];

  // Select all the matching <option> elements as a NodeList
  const optionElements = document.querySelectorAll(`${selectorStr} option`);

  optionElements.forEach((optionEl) => {
    if (optionEl.selected) {
      selectedOptionsArr.push(optionEl.value);
    }
  });

  return selectedOptionsArr;
};

/**
 * Process the selected option(s) from a buildings & data points dropdown list.
 *
 * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
 * @returns {Array} An array of string(s) representing the processed value(s) of the selected buildings & data points option(s)
 */
const processSelectionsFromBuildingDataPointOptions = function (
  selectedOptionsArr
) {
  // Array to store our final result
  const selectedOptionsBuildingDataPointArr = [];

  selectedOptionsArr.forEach((optionStr) => {
    // Case 1: <option> element's value CONTAINS a "/" character
    // We wish to create a string like this `Bau 101/VL`
    if (optionStr.includes("/")) {
      // Split the <option> element's value into two substrings
      const optionsStrPartOne = optionStr.slice(0, 3);
      const optionsStrPartTwo = optionStr.slice(3);

      // Create a new string for the first substring
      const optionsStrPartOneNew = `Bau ${optionsStrPartOne}`;

      // Create a new combined string
      const optionsStrNew = optionsStrPartOneNew + optionsStrPartTwo;

      selectedOptionsBuildingDataPointArr.push(optionsStrNew);
    }
    // Case 2: <option> element's value DOES NOT CONTAIN a "/" character
    // We wish to create a string like this `Other/Außentemp`
    else {
      selectedOptionsBuildingDataPointArr.push(`Other/${optionStr}`);
    }
  });

  return selectedOptionsBuildingDataPointArr;
};

/**
 * Split an option element's value (a string) using a forward slash character ("/") as the delimiter
 *
 * @param {String} selectedOptionsStr A string representing the value of the selected `<option>` element
 * @returns {String} Resulting strings after splitting
 */
const splitOptionsTextDelimitedBySlash = function (selectedOptionsStr) {
  return selectedOptionsStr.split("/");
};

/**
 * Split an array of option element's values (strings) which have a forward slash character ("/") as the delimiter
 *
 * @param {Array} selectedOptionsArr An array of string(s) representing the value(s) of the selected `<option>` elements
 * @returns {Array} An array made up of resulting strings after splitting
 */
const splitMultipleOptionsTextDelimitedBySlash = function (selectedOptionsArr) {
  return selectedOptionsArr.map((selectedOption) =>
    splitOptionsTextDelimitedBySlash(selectedOption)
  );
};

/**
 * Get the values from the currently selected options in ALL the drop down lists
 * @returns {Array} An array containing four arrays, where each array contains the values of the selected options
 */
const getSelectedOptionsFromAllDropDownLists = function () {
  const selectedBuildingDataPointOptionsArr =
    processSelectionsFromBuildingDataPointOptions(
      getSelectedOptionsFromDropDownList("#drop-down--bldg-data-point")
    );

  // Separate the building ID from the data point
  const selectedBuildingDataPointOptionsSplitArr =
    splitMultipleOptionsTextDelimitedBySlash(
      selectedBuildingDataPointOptionsArr
    );

  const selectedAggregationOptionsArr = getSelectedOptionsFromDropDownList(
    "#drop-down--aggregation-type"
  );

  const selectedSamplingRateArr = getSelectedOptionsFromDropDownList(
    "#drop-down--sampling-rate"
  );

  const selectedChartTypeArr = getSelectedOptionsFromDropDownList(
    "#drop-down--chart-type"
  );

  // Ensure that all the options have at least one selection
  if (selectedChartTypeArr.length === 0) {
    throw new Error("Please ensure that the chart type is selected");
  } else if (selectedBuildingDataPointOptionsSplitArr.length === 0) {
    throw new Error("Please ensure that at least one data point is selected");
  } else if (selectedAggregationOptionsArr.length === 0) {
    throw new Error("Please ensure that the aggregation type is selected");
  } else if (selectedSamplingRateArr.length === 0) {
    throw new Error("Please ensure that the sampling rate is selected");
  } else {
    return [
      selectedChartTypeArr,
      selectedBuildingDataPointOptionsSplitArr,
      selectedAggregationOptionsArr,
      selectedSamplingRateArr,
    ];
  }
};

/**
 * Check whether the abbreviated buildings + data points + sampling rate strings
 * contain the temperature difference [dT] between Vorlauf temperature [VL] and
 * Rücklauf temperature [RL] (i.e., dT = VL - RL). Unlike all the other data points,
 * this data point is computed in a separate step
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of strings representing the abbreviated building + data point + sampling rate values
 * @returns {Boolean} true if the selected options contain the string `dT`, false otherwise
 */
const checkIfSelectedOptionsContainTemperatureDifference = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Create a flattened copy of our input array,
  // then check if it contains the string `dT`
  return buildingDataPointSamplingRateAbbrevArr.flat().includes("dT");
};

/**
 * Get the index(es) of the the abbreviated buildings + data points + sampling rate string(s)
 * that contains the temperature difference (dT)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains an integer(s) whose value(s) are the index(es) of the abbreviated building + data point + sampling rate string(s) containing the temperature difference (dT)
 */
const getIndexesOfTemperatureDifferenceOptions = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // An array to store the final result
  const foundIndexesArr = [];

  // Use the index, i, provided by `forEach` array method
  buildingDataPointSamplingRateAbbrevArr.forEach(
    (bldgDataPntSamplingRateAbbrvArr, i) => {
      if (bldgDataPntSamplingRateAbbrvArr.includes("dT")) {
        foundIndexesArr.push(i);
      }
    }
  );

  return foundIndexesArr;
};

/**
 * Delete the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 */
const deleteTemperatureDifferenceOptions = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Calculate the index(es) that we wish to delete
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
    buildingDataPointSamplingRateAbbrevArr
  );

  // Delete the index(es) of `dT`, modifies the array in place
  // Note: The resulting array is sparse
  foundIndexesArr.forEach(
    (foundIndex) => delete buildingDataPointSamplingRateAbbrevArr[foundIndex]
  );

  // Array to store our final result
  const buildingDataPointFinalArr = [];

  // Remove the empty sub array(s) that makes entire array sparse
  // Note: `empty` does not mean `undefined` or `null`
  buildingDataPointSamplingRateAbbrevArr.forEach(
    (bldgDataPntSmplingRateAbbrvArr) => {
      if (typeof bldgDataPntSmplingRateAbbrvArr === "object") {
        buildingDataPointFinalArr.push(bldgDataPntSmplingRateAbbrvArr);
      }
    }
  );

  return buildingDataPointFinalArr;
};

/**
 * Extract the abbreviated building + data point + sampling rate string(s) that contains the temperature difference (dT)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 */
const extractTemperatureDifferenceOptions = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Array to store final result
  const temperatureDifferenceOptionsAbbrevArr = [];

  // Calculate the index(es) that we wish to extract
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
    buildingDataPointSamplingRateAbbrevArr
  );

  foundIndexesArr.forEach((foundIndex) => {
    // Extracted array for a single found index
    const bldgDataPntSamplingRateAbbrvArr =
      buildingDataPointSamplingRateAbbrevArr[foundIndex];

    temperatureDifferenceOptionsAbbrevArr.push(bldgDataPntSamplingRateAbbrvArr);
  });

  return temperatureDifferenceOptionsAbbrevArr;
};

/**
 * Extract the abbreviated building + sampling rate string(s) for use in calculating the temperature difference (dT = VL - RL)
 *
 * @param {Array} buildingDataPointSamplingRateAbbrevArr An array that contains nested array(s) made up of abbreviated building + data point + sampling rate string(s)
 * @returns {Array} An array that contains nested array(s) made up of abbreviated building + sampling rate string(s)
 */
const extractBuildingPlusSamplingRate = function (
  buildingDataPointSamplingRateAbbrevArr
) {
  // Array to store final result
  const temperatureDifferenceOptionsAbbrevArr = [];

  // Calculate the index(es) that we wish to extract
  const foundIndexesArr = getIndexesOfTemperatureDifferenceOptions(
    buildingDataPointSamplingRateAbbrevArr
  );

  foundIndexesArr.forEach((foundIndex) => {
    // Extracted array for a single found index
    const bldgDataPntSamplingRateAbbrvArr =
      buildingDataPointSamplingRateAbbrevArr[foundIndex];

    // Extract the building and sampling rate strings
    // Note: we have ignored the second element
    const [bldgAbbrv, , samplingRateAbbrv] = bldgDataPntSamplingRateAbbrvArr;

    // Create a new array that contains two elements,
    // the building and sampling rate abbreviated strings
    const bldgSamplingRateAbbrvArr = [bldgAbbrv, samplingRateAbbrv];

    temperatureDifferenceOptionsAbbrevArr.push(bldgSamplingRateAbbrvArr);
  });

  return temperatureDifferenceOptionsAbbrevArr;
};

/**
 * Determine if a chart requires raw observations instead of aggregated observations
 *
 * @param {String} selectedAggregationType The selected aggregation type
 * @param {String} selectedAggregationDuration The selected aggregation duration
 * @returns {Boolean} true if the chart requires raw observations, false if not
 */
const checkIfChartRequiresRawObservations = function (
  selectedAggregationType,
  selectedAggregationDuration
) {
  if (
    selectedAggregationType === "None (raw data)" &&
    selectedAggregationDuration === undefined
  ) {
    return true;
  } else {
    return false;
  }
};

/**
 * Check if the selected building(s) + data point(s) options are valid for
 * drawing either a heatmp or scatter plot. If these options are
 * invalid, throw an error
 *
 * @param {Array} selectedChartTypeArr An array of string(s) representing the selected chart type(s)
 * @param {Array} selectedBuildingsDataPointsSamplingRateAbbrevNestedArr An array that is made up of sub array(s) whose elements are strings representing the selected buildings, data points and sampling rates
 * @returns {Boolean} true, if there are no errors thrown
 */
const checkIfSelectedBuildingDataPointsOptionsAreValid = function (
  selectedChartTypeArr,
  selectedBuildingsDataPointsSamplingRateAbbrevNestedArr
) {
  for (const selectedChartType of selectedChartTypeArr) {
    // A heatmap can only visualize one data point
    if (selectedChartType === "Heatmap") {
      if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length === 1)
        return true;
      else if (
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length > 1
      ) {
        throw new Error("A heatmap can only display one data point at a time");
      }
    }
    // A scatter plot requires at least two data points
    else if (selectedChartType === "Scatter Plot") {
      if (selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length >= 2)
        return true;
      else if (
        selectedBuildingsDataPointsSamplingRateAbbrevNestedArr.length < 2
      ) {
        throw new Error("A scatter plot requires at least two data points");
      }
    }
  }
};

/**
 * Check if the selected aggregation type options are valid for
 * drawing either a heatmp or scatter plot. If these options are
 * invalid, throw an error
 *
 * @param {Array} selectedChartTypeArr An array of string(s) representing the selected chart type(s)
 * @param {String} selectedAggregationType The selected aggregation type
 * @param {String} selectedAggregationDuration The selected aggregation duration
 * @returns {Boolean} true, if there are no errors thrown
 */
const checkIfSelectedAggregationOptionsAreValid = function (
  selectedChartTypeArr,
  selectedAggregationType,
  selectedAggregationDuration
) {
  for (const selectedChartType of selectedChartTypeArr) {
    if (
      selectedChartType === "Heatmap" ||
      selectedChartType === "Scatter Plot"
    ) {
      // For both chart types, we are interested in raw observations
      if (
        checkIfChartRequiresRawObservations(
          selectedAggregationType,
          selectedAggregationDuration
        )
      )
        return true;
      // Throw error if we attempt to use aggregated observations
      else {
        throw new Error(
          "The selected chart type does not support aggregated results"
        );
      }
    }
  }
};

/**
 * Get the abbreviated form of building IDs, phenomenon names and sensor sampling rates
 * @param {String} buildingFullForm A string representation of the full form of a building ID
 * @param {String} phenomenonFullForm A string representation of the full form of a phenomenon name
 * @param {String} samplingRateFullForm A string representation of the full form of a sensor's sampling rate
 * @returns {Array} An array of abbreviated strings
 */
const getBuildingSensorSamplingRateAbbreviation = function (
  buildingFullForm,
  phenomenonFullForm,
  samplingRateFullForm
) {
  const fullFormToAbbreviationMapping = {
    buildings: {
      "Bau 101": "101",
      "Bau 102": "102",
      "Bau 107": "107",
      "Bau 112": "112, 118",
      "Bau 125": "125",
      "Bau 225": "225",
      "Other": "weather_station_521",
    },

    phenomenon: {
      VL: "vl",
      RL: "rl",
      dT: "dT",
      Durchfluss: "flow",
      Leistung: "power",
      Energie: "energy",
      Energie_VERBR: "energy_verb",
      Außentemp: "outside_temp",
    },

    samplingRate: {
      "15 min": "15min",
      "60 min": "60min",
    },
  };

  const buildingAbbrev =
    fullFormToAbbreviationMapping["buildings"]?.[buildingFullForm];

  const phenomenonAbbrev =
    fullFormToAbbreviationMapping["phenomenon"]?.[phenomenonFullForm];

  const samplingRateAbbrev =
    fullFormToAbbreviationMapping["samplingRate"]?.[samplingRateFullForm];

  if (buildingAbbrev === undefined) {
    throw new Error(
      "The provided building ID is not valid or is not supported by this function"
    );
  } else if (phenomenonAbbrev === undefined) {
    throw new Error(
      "The provided data point is not valid or is not supported by this function"
    );
  } else if (samplingRateAbbrev === undefined) {
    throw new Error(
      "The provided sampling rate is not valid or is not supported by this function"
    );
  } else {
    return [buildingAbbrev, phenomenonAbbrev, samplingRateAbbrev];
  }
};

/**
 * Get the abbreviated form for the currently selected options in ALL the drop down lists
 *
 * @param {Array} allSelectedOptionsArr An array containing four arrays, where each array contains the values of the selected options
 * @returns {Array} An array which contains one or more nested arrays of abbreviations of building(s), data point(s) and sampling rate(s)
 */
const getAbbreviationsForSelectedOptionsFromAllDropDownLists = function (
  allSelectedOptionsArr
) {
  // Note: The buildings + data points array is the second element and
  // the sampling rate array is the fourth element, therefore we skip the first and third elementa
  const [
    ,
    selectedBuildingDataPointOptionsSplitArr,
    ,
    selectedSamplingRateArr,
  ] = allSelectedOptionsArr;

  // The building is the first element
  const selectedBuildingsArr = selectedBuildingDataPointOptionsSplitArr.map(
    (selectedBuildingDataPoint) => selectedBuildingDataPoint[0]
  );

  // The data point is the second element
  const selectedDataPointsArr = selectedBuildingDataPointOptionsSplitArr.map(
    (selectedBuildingDataPoint) => selectedBuildingDataPoint[1]
  );

  // Assume that the buildings and data points arrays have equal length
  // use one of the arrays for looping
  if (selectedBuildingsArr.length !== selectedDataPointsArr.length) {
    throw new Error(
      "The buildings array and data points array have different lengths"
    );
  } else {
    return selectedBuildingsArr.map((selectedBuilding, i) =>
      getBuildingSensorSamplingRateAbbreviation(
        selectedBuilding,
        selectedDataPointsArr[i],
        ...selectedSamplingRateArr
      )
    );
  }
};

export {
  splitMultipleOptionsTextDelimitedBySlash,
  getSelectedOptionsFromAllDropDownLists,
  checkIfSelectedOptionsContainTemperatureDifference,
  deleteTemperatureDifferenceOptions,
  extractTemperatureDifferenceOptions,
  extractBuildingPlusSamplingRate,
  checkIfSelectedBuildingDataPointsOptionsAreValid,
  checkIfSelectedAggregationOptionsAreValid,
  getAbbreviationsForSelectedOptionsFromAllDropDownLists,
};
