"use strict";

import {
  chartExportOptions,
  checkForAndDeleteUniqueObservationsFromLargerArray,
  convertHexColorToRGBColor,
  createCombinedTextDelimitedByAmpersand,
  createCombinedTextDelimitedByComma,
  abbreviateTemperaturePhenomenonNames,
  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
) {
  // Check if our observation arrays have equal lengths,
  // remove the unique observations, if necessary
  const [obsArrayOneFinal, obsArrayTwoFinal] =
    obsArrayOne.length !== obsArrayTwo.length
      ? checkForAndDeleteUniqueObservationsFromLargerArray(
          obsArrayOne,
          obsArrayTwo
        )
      : [obsArrayOne, obsArrayTwo];

  // Create the combined observations array
  return createCombinedObservationValues(obsArrayOneFinal, obsArrayTwoFinal);
};

/**
 * 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);

  // 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 {
    const combinedNameSymbolArr = phenomenonNamesYAxisArr.map(
      (phenomenonNameYAxis, i) =>
        `${phenomenonNameYAxis} [${unitOfMeasurementSymbolsYAxisArr[i]}]`
    );

    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 local copy of the colors array
  const highchartsColorsForScatterPlotArr = [...highchartsColorsArr];

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

  // Create array of colors in RGBA format
  const seriesColorsArr = highchartsColorsForScatterPlotArr.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: seriesColorsArr[i],
      };
    });
  }
};

/**
 * Match a scatter plot's y-axis phenomenon name to its corresponding symbol
 *
 * @param {String} seriesName A string representing a scatter plot's series name. It is made up of two phenomenon names separated by a comma
 * @returns {String} The phenomenon's symbol
 */
const getYAxisUnitOfMeasurementSymbol = function (seriesName) {
  const phenomenonNameToSymbolMapping = {
    temperature: "\u00B0C",
    flow: "m\u00B3/h",
    power: "kW",
    energy: "MWh",
  };

  // The `series.name` property for the scatter plot is made up of
  // two phenomenon names delimited by a comma
  // We are interested in the first string
  const phenomenonNameYAxis = seriesName.split(",")[0].toLowerCase();

  if (phenomenonNameYAxis.includes("temperature")) {
    return phenomenonNameToSymbolMapping.temperature;
  } else if (phenomenonNameYAxis.includes("flow")) {
    return phenomenonNameToSymbolMapping.flow;
  } else if (phenomenonNameYAxis.includes("power")) {
    return phenomenonNameToSymbolMapping.power;
  } else if (phenomenonNameYAxis.includes("energy")) {
    return phenomenonNameToSymbolMapping.energy;
  }
};

/**
 * Draw a scatter plot using Highcharts library
 * @param {Array} formattedObsArraysForScatterPlot Response from SensorThings API formatted for use in a scatter plot. Currently, only raw observations are supported, i.e. no aggregation
 * @param {Object} extractedFormattedDatastreamProperties An object that contains arrays of formatted Datastream properties
 * @returns {undefined} undefined
 */
const drawScatterPlotHighcharts = function (
  formattedObsArraysForScatterPlot,
  extractedFormattedDatastreamProperties
) {
  // Arrays of datastream properties
  const {
    datastreamDescriptionsArr,
    datastreamNamesArr,
    phenomenonNamesArr,
    unitOfMeasurementSymbolsArr,
  } = extractedFormattedDatastreamProperties;

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

  const CHART_TITLE =
    createCombinedTextForScatterPlotTitles(phenomenonNamesArr);

  const CHART_SUBTITLE = createSubtitleForChart(datastreamNamesArr);

  const X_AXIS_TITLE = createXAxisTitleTextScatterPlot(
    phenomenonNamesArr,
    unitOfMeasurementSymbolsArr
  );

  const Y_AXIS_TITLE = createYAxisTitleTextScatterPlot(
    abbreviateTemperaturePhenomenonNames(phenomenonNamesArr),
    unitOfMeasurementSymbolsArr
  );

  // The unit of measurement symbols for the x-axis is the first element of the array
  const unitOfMeasurementXAxisSymbol = 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
        )} ${getYAxisUnitOfMeasurementSymbol(
          this.series.name
        )}, ${this.point.x.toFixed(2)} ${unitOfMeasurementXAxisSymbol}</b>`;

        return headerString + pointString;
      },
    },

    exporting: chartExportOptions,

    series: seriesOptionsArr,
  });
};

export {
  formatSensorThingsApiResponseForScatterPlot,
  drawScatterPlotHighcharts,
};
