// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

<template>
  <Line
    :chart-options="preparedOptions"
    :chart-data="preparedData"
    :width="chartWidth"
    :height="chartHeight"
  />
</template>

<script>
import { Line } from 'vue-chartjs'
import { Chart as ChartJS, Title, Tooltip, Legend, LineElement, CategoryScale, TimeScale, LinearScale, PointElement, Filler } from 'chart.js'

ChartJS.register(Title, Tooltip, Legend, LineElement, CategoryScale, TimeScale, LinearScale, PointElement, Filler)

export default {
  name: 'ResourceStatsLineChart',
  components: { Line },
  props: {
    chartData: {
      type: Object,
      required: true
    },
    chartLabels: {
      type: Array,
      required: true
    },
    yAxisMeasurementUnit: {
      type: String,
      required: true
    },
    yAxisInitialMax: {
      type: Number,
      default: 1
    },
    yAxisIncrementValue: {
      type: Number,
      default: 1
    },
    chartWidth: {
      type: Number,
      default: 1024
    },
    chartHeight: {
      type: Number,
      default: 250
    }
  },
  computed: {
    preparedData () {
      if (this.chartData) {
        return this.prepareData(this.chartData)
      }
      return {}
    },
    preparedOptions () {
      if (this.chartData) {
        return this.getChartOptions(this.calculateMaxYAxisAndStepSize(this.chartData, this.yAxisInitialMax, this.yAxisIncrementValue), this.yAxisMeasurementUnit)
      }
      return {}
    }
  },
  methods: {
    /**
     * Converts a value (Byte-based) from an unit to other one. For example: from Byte to KiB; from GiB to MiB; etc.
     * To use it consider the following sequence: Byte -> KiB -> MiB -> GiB ...
     * So, from Byte to MiB there are 2 steps, while from MiB to Byte there are -2 steps.
     * @param value the value to be converted.
     * @param step the number of steps between Byte-based units of measure.
     * @returns the converted value.
     */
    convertByteBasedUnitOfMeasure (value, step) {
      if (value === 0) {
        return 0.00
      }
      if (step === 0) {
        return value
      }
      if (step > 0) {
        return parseFloat(value / (Math.pow(1024, step))).toFixed(2)
      }
      return parseFloat(value * (Math.pow(1024, Math.abs(step)))).toFixed(2)
    },
    calculateMaxYAxisAndStepSize (chartLines, initialMaxYAxis, incrementValue) {
      const numberOfLabelsOnYaxis = 4
      var highestValue = 0
      var maxYAxis = initialMaxYAxis
      for (const line of chartLines) {
        for (const d of line.data) {
          const currentValue = parseFloat(d.stat)
          if (currentValue > highestValue) {
            highestValue = currentValue
            while (highestValue > maxYAxis) {
              maxYAxis += incrementValue
              if (maxYAxis % incrementValue !== 0) {
                maxYAxis = Math.round(maxYAxis / incrementValue) * incrementValue
              }
            }
          }
        }
      }
      return { maxYAxes: maxYAxis, stepSize: maxYAxis / numberOfLabelsOnYaxis }
    },
    /**
     * Returns the chart options.
     * @param yAxesStepSize the step size for the Y axes.
     * @param yAxesUnitOfMeasurement the unit of measurement label used on the Y axes.
     * @returns the chart options.
     */
    getChartOptions (yAxesOptions, yAxesUnitOfMeasurement) {
      const dateTimes = this.convertStringArrayToDateArray(JSON.parse(JSON.stringify(this.chartLabels)))
      const averageDifference = this.averageDifferenceBetweenTimes(dateTimes)
      const xAxisStepSize = this.calculateStepSize(this.chartLabels.length, averageDifference)
      const startDate = new Date(dateTimes[0])
      const endDate = new Date(dateTimes[dateTimes.length - 1])
      const differentDay = startDate.getDate() !== endDate.getDate()
      const differentYear = startDate.getFullYear() !== endDate.getFullYear()
      var displayFormat = 'HH:mm'
      if (xAxisStepSize < 5 * 60) {
        displayFormat += ':ss'
      }
      if (differentDay) {
        displayFormat = 'MMM-DD ' + displayFormat
      }
      if (xAxisStepSize >= 24 * 60 * 60) {
        displayFormat = 'MMM-DD'
      }
      if (differentYear) {
        displayFormat = 'YYYY-' + displayFormat
      }
      var chartOptions = {
        responsive: true,
        maintainAspectRatio: false,
        scales: {
          yAxis: {
            min: 0,
            max: yAxesOptions.maxYAxes,
            reverse: false,
            ticks: {
              stepSize: yAxesOptions.stepSize,
              callback: function (label) {
                return label + yAxesUnitOfMeasurement
              }
            }
          },
          xAxis: {
            type: 'time',
            autoSkip: false,
            time: {
              parser: 'YYYY-MM-DD HH:mm:ss',
              unit: 'second',
              displayFormats: {
                second: displayFormat
              }
            }
          }
        }
      }
      chartOptions.scales.xAxis.time.stepSize = xAxisStepSize
      return chartOptions
    },
    convertStringArrayToDateArray (stringArray) {
      const dateArray = []
      for (const element of stringArray) {
        dateArray.push(new Date(element.replace(' ', 'T')))
      }
      return dateArray
    },
    averageDifferenceBetweenTimes (timeList) {
      const oneSecond = 1000 // 1 second represented as milliseconds
      const differences = []
      var previous = timeList.splice(0, 1)[0]
      for (const time of timeList) {
        differences.push((time - previous) / oneSecond) // push the difference in seconds
        previous = time
      }
      if (differences.length === 0) {
        return 1
      }
      const averageDifference = Math.trunc(differences.reduce((a, b) => a + b, 0) / differences.length)
      return averageDifference
    },
    calculateStepSize (numberOfDataPoints, differenceBetweenTimes) {
      const idealNumberOfLabels = 8
      const result = numberOfDataPoints / idealNumberOfLabels
      if (result > 1) {
        return result * differenceBetweenTimes
      }
      return differenceBetweenTimes
    },
    prepareData (chartData) {
      const datasetList = []
      for (const element of chartData) {
        datasetList.push(
          {
            backgroundColor: element.backgroundColor,
            borderColor: element.borderColor,
            borderWidth: 3,
            label: element.label,
            data: element.data.map(d => d.stat),
            hidden: this.hideLine(element.data.map(d => d.stat)),
            pointRadius: element.pointRadius,
            fill: 'origin'
          }
        )
      }
      return {
        labels: this.chartLabels,
        datasets: datasetList
      }
    },
    hideLine (data) {
      for (const d of data) {
        if (d < 0) {
          return true
        }
      }
      return false
    }
  }
}
</script>
