"use strict";

import {
  chartExportOptions,
  checkForAndDeleteUniqueObservationsFromLargerArray,
  convertHexColorToRGBColor,
  createCombinedTextDelimitedByAmpersand,
  createCombinedTextDelimitedByComma,
  createSubtitleForChart,
  removeTransparencyFromColor,
} from "./chartHelpers.mjs";

/**
 * Extracts and combines observation values from two input observation arrays of equal length
 * @param {Array} obsArrayOne First set of N observations (timestamp + value)
 * @param {Array} obsArrayTwo Second set of N observations (timestamp + value)
 * @returns {Array} A N*2 array of observation values from both input observation arrays
 */
const createCombinedObservationValues = function (obsArrayOne, obsArrayTwo) {
  // Extract the values from the two observation arrays
  const obsValuesOne = obsArrayOne.map((result) => result[1]);
  const obsValuesTwo = obsArrayTwo.map((result) => result[1]);

  //  Since the arrays are of equal length, we need only use one of the arrays for looping
  return obsValuesOne.map((obsValOne, i) => [obsValOne, obsValuesTwo[i]]);
};

/**
 * Format the response from SensorThings API to make it suitable for use in a scatter plot
 * @param {Array} obsArrayOne Array of observations (timestamp + value) that is response from SensorThings API
 * @param {Array} obsArrayTwo Array of observations (timestamp + value) that is response from SensorThings API
 * @returns {Array} Array of formatted observations suitable for use in a scatter plot
 */
const formatSensorThingsApiResponseForScatterPlot = function (
  obsArrayOne,
  obsArrayTwo
) {
  // When our observation arrays have DIFFERENT lengths
  if (obsArrayOne.length !== obsArrayTwo.length) {
    const [obsArrayOneFinal, obsArrayTwoFinal] =
      checkForAndDeleteUniqueObservationsFromLargerArray(
        obsArrayOne,
        obsArrayTwo
      );

    return createCombinedObservationValues(obsArrayOneFinal, obsArrayTwoFinal);
  }
  // When our observation arrays already have SAME lengths
  else {
    return createCombinedObservationValues(obsArrayOne, obsArrayTwo);
  }
};

/**
 * Concatenates metadata properties to create a string for either the title or subtitle of a scatter plot
 * @param {Array} phenomenonNamesArr An array of phenomenon name strings
 * @returns {String} A string made up of combined phenomenon names
 */
const createCombinedTextForScatterPlotTitles = function (phenomenonNamesArr) {
  // x-axis phenomenon name is the first element of array
  const phenomenonNameXAxis = phenomenonNamesArr[0];

  // y-axis phenomenon name(s) array is remaining elements of array
  const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);
  // Use a set to remove duplicates
  const uniquePhenomenonNamesYAxis = new Set(phenomenonNamesYAxisArr);
  const uniquePhenomenonNamesYAxisArr = [...uniquePhenomenonNamesYAxis];

  return `${createCombinedTextDelimitedByAmpersand(
    uniquePhenomenonNamesYAxisArr
  )} versus ${phenomenonNameXAxis}`;
};

/**
 * Create string for the x-axis title of a scatter plot
 * @param {Array} phenomenonNamesArr Array of phenomenon name strings
 * @param {Array} unitOfMeasurementSymbolsArr rray of unit of measurement symbol strings
 * @returns {String} X-axis title string for scatter plot
 */
const createXAxisTitleTextScatterPlot = function (
  phenomenonNamesArr,
  unitOfMeasurementSymbolsArr
) {
  // x-axis phenomenon name string is first element of array
  const phenomenonNameXAxis = phenomenonNamesArr[0];

  // x-axis phenomenon symbol string is first element of array
  const unitOfMeasurementSymbolXAxis = unitOfMeasurementSymbolsArr[0];

  return `${phenomenonNameXAxis} [${unitOfMeasurementSymbolXAxis}]`;
};

/**
 * Create string for the y-axis title of a scatter plot
 * @param {Array} phenomenonNamesArr Array of phenomenon name strings
 * @param {Array} unitOfMeasurementSymbolsArr Array of unit of measurement symbol strings
 * @returns {String} Y-axis title string for scatter plot
 */
const createYAxisTitleTextScatterPlot = function (
  phenomenonNamesArr,
  unitOfMeasurementSymbolsArr
) {
  // y-axis phenomenon names start at array index 1
  const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);

  // y-axis phenomenon symbols start at array index 1
  const unitOfMeasurementSymbolsYAxisArr = unitOfMeasurementSymbolsArr.slice(1);

  const combinedNameSymbolArr = phenomenonNamesYAxisArr.map(
    (phenomenonNameYAxis, i) =>
      `${phenomenonNameYAxis} [${unitOfMeasurementSymbolsYAxisArr[i]}]`
  );

  // The phenomenon names and unit of measurement arrays should have equal lengths
  // Use one of the arrays for looping
  if (
    phenomenonNamesYAxisArr.length !== unitOfMeasurementSymbolsYAxisArr.length
  ) {
    throw new Error(
      "The phenomenon names array and unit of measurement symbols array have different lengths"
    );
  } else {
    return createCombinedTextDelimitedByComma(combinedNameSymbolArr);
  }
};

/**
 * Create an options object for each series drawn in the scatter plot
 * @param {Array} formattedObsArraysForScatterPlot An array of formatted observation array(s) from one or more datastreams
 * @param {Array} phenomenonNamesArr An array of phenomenon name(s)
 * @returns {Array} An array made up of series options object(s)
 */
const createSeriesOptionsForScatterPlot = function (
  formattedObsArraysForScatterPlot,
  phenomenonNamesArr
) {
  // An array of colors, in hexadecimal format, provided by the global Highcharts object
  const highchartsColorsArr = Highcharts.getOptions().colors;

  // Create a reversed copy of the colors array
  const highchartsColorsReversedArr = [...highchartsColorsArr].reverse();

  // Opacity value for symbol
  const SERIES_SYMBOL_COLOR_OPACITY = ".3";

  // Create array of colors in RGBA format
  const seriesColors = highchartsColorsReversedArr.map(
    (hexColorCode) =>
      `rgba(${convertHexColorToRGBColor(
        hexColorCode
      )}, ${SERIES_SYMBOL_COLOR_OPACITY})`
  );

  // x-axis phenomenon name is the first element of array
  const phenomenonNameXAxis = phenomenonNamesArr[0];
  // y-axis phenomenon name(s) array is remaining elements of array
  const phenomenonNamesYAxisArr = phenomenonNamesArr.slice(1);

  // Create an array of seriesOptions objects
  // Assumes that the observation array of arrays and phenomenon names array are of equal length
  // Use one of the arrays for looping
  if (
    formattedObsArraysForScatterPlot.length !== phenomenonNamesYAxisArr.length
  ) {
    throw new Error(
      "The observations array and phenomenon names array have different lengths"
    );
  } else {
    return formattedObsArraysForScatterPlot.map((formattedObsArray, i) => {
      return {
        name: `${phenomenonNamesYAxisArr[i]}, ${phenomenonNameXAxis}`,
        data: formattedObsArray,
        color: seriesColors[i],
      };
    });
  }
};

/**
 * Draw a scatter plot using Highcharts library
 * @param {Array} formattedObsArrayForSeriesOnePlusSeriesTwo Response from SensorThings API formatted for use in a scatter plot
 * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
 * @returns {undefined} undefined
 */
const drawScatterPlotHighcharts = function (
  formattedObsArrayForSeriesOnePlusSeriesTwo,
  extractedFormattedDatastreamProperties
) {
  // Arrays of datastream properties
  const {
    datastreamDescriptionsArr,
    datastreamNamesArr,
    phenomenonNamesArr,
    unitOfMeasurementSymbolsArr,
  } = extractedFormattedDatastreamProperties;

  // Create the array of series options object(s)
  const seriesOptionsArr = createSeriesOptionsForScatterPlot(
    formattedObsArrayForSeriesOnePlusSeriesTwo,
    phenomenonNamesArr
  );

  const CHART_TITLE =
    createCombinedTextForScatterPlotTitles(phenomenonNamesArr);

  const CHART_SUBTITLE = createSubtitleForChart(datastreamNamesArr);

  const X_AXIS_TITLE = createXAxisTitleTextScatterPlot(
    phenomenonNamesArr,
    unitOfMeasurementSymbolsArr
  );

  const Y_AXIS_TITLE = createYAxisTitleTextScatterPlot(
    phenomenonNamesArr,
    unitOfMeasurementSymbolsArr
  );

  // The unit of measurement symbols for the x-axis is the first element of the array
  // Assume that we will be comparing similar phenomena, so we can reuse this symbol
  const UNIT_OF_MEASUREMENT_SYMBOL = unitOfMeasurementSymbolsArr[0];

  const MARKER_RADIUS = 2;

  Highcharts.chart("chart-scatter-plot", {
    chart: {
      type: "scatter",
      zoomType: "xy",
    },

    boost: {
      useGPUTranslations: true,
      usePreAllocated: true,
    },

    title: {
      text: CHART_TITLE,
      "align": "center",
    },

    subtitle: {
      text: CHART_SUBTITLE,
      "align": "center",
    },

    xAxis: {
      labels: {
        format: `{value}`,
      },
      title: {
        enabled: true,
        text: X_AXIS_TITLE,
      },
      startOnTick: true,
      endOnTick: true,
      showLastLabel: true,
    },

    yAxis: [
      {
        labels: {
          format: `{value}`,
        },
        title: {
          text: Y_AXIS_TITLE,
        },
      },
    ],

    legend: {
      enabled: false,
    },

    plotOptions: {
      scatter: {
        marker: {
          radius: MARKER_RADIUS,
          states: {
            hover: {
              enabled: true,
              lineColor: "rgb(100,100,100)",
            },
          },
        },
        states: {
          hover: {
            marker: {
              enabled: false,
            },
          },
        },
      },
    },

    tooltip: {
      formatter() {
        // The color contained in the series object is in RGBA format,
        // convert it to RGB format so that the text in the tooltip is more legible
        const headerString = `<span style="color:${removeTransparencyFromColor(
          this.point.color
        )}">${this.series.name}</span> <br>`;

        const pointString = `<b>${this.point.y.toFixed(
          2
        )} ${UNIT_OF_MEASUREMENT_SYMBOL}, ${this.point.x.toFixed(
          2
        )} ${UNIT_OF_MEASUREMENT_SYMBOL}</b>`;

        return headerString + pointString;
      },
    },

    exporting: chartExportOptions,

    series: seriesOptionsArr,
  });
};

export {
  formatSensorThingsApiResponseForScatterPlot,
  drawScatterPlotHighcharts,
};
