import {ChangeDetectionStrategy, Component, Input, OnChanges} from '@angular/core';
import {DecimalPipe, NgForOf} from "@angular/common";
import {ChartConfiguration, ChartData, DefaultDataPoint} from "chart.js";
import {NgChartsModule} from "ng2-charts";
import {FormsModule} from "@angular/forms";

@Component({
  selector: 'app-chart',
  standalone: true,
  imports: [
    DecimalPipe,
    NgChartsModule,
    NgForOf,
    FormsModule
  ],
  templateUrl: './chart.component.html',
  styleUrl: './chart.component.css',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ChartComponent implements OnChanges {

  barChartOptions: ChartConfiguration<'bar'>['options'];

  ngOnChanges() {
    const showPercentages = this.showPercentages;
    this.barChartOptions = {
      responsive: true,
      //@ts-ignore
      animations: false,
      scales: {
        y: {
          ticks: {
            callback: function(value, index, ticks) {
              return showPercentages ? value + '%' : value;
            }
          }
        }
      }
    };
  }

  @Input() title!: string;
  @Input() data!: number[];

  removeOutliers = true;
  groupData = true;
  showPercentages = true;

  calculateStatistics(): Array<{ name: string, value: number }> {
    if (this.data.length === 0) {
      return [];
    }

    let chartData = this.data;

    if(this.removeOutliers) {
      chartData = this.removeOutliersZScore(this.data);
    }

    // Assuming data is already without outliers and sorted if necessary
    let sortedData = chartData.slice().sort((a, b) => a - b);

    let mean = sortedData.reduce((acc, curr) => acc + curr, 0) / sortedData.length;
    let median = sortedData.length % 2 === 0 ? (sortedData[sortedData.length / 2 - 1] + sortedData[sortedData.length / 2]) / 2 : sortedData[Math.floor(sortedData.length / 2)];
    let variance = sortedData.reduce((acc, curr) => acc + Math.pow(curr - mean, 2), 0) / (sortedData.length - 1);
    let stdDev = Math.sqrt(variance);
    let min = sortedData[0];
    let max = sortedData[sortedData.length - 1];

    const statistics = {
      'mean': mean,
      'median': median,
      'std. dev': stdDev,
      'min': min,
      'max': max
    };

    return Object.entries(statistics).map(([name, value]) => ({ name, value }));
  }

  /**
  * Removes zeros from the start and end of the array, used for charts.
   */
  trimEdgeZeros(dataPoints: { value: number, count: number }[]): { value: number, count: number }[] {
    let startIndex = 0;
    while (startIndex < dataPoints.length && dataPoints[startIndex].count === 0) {
      startIndex++;
    }

    let endIndex = dataPoints.length - 1;
    while (endIndex >= 0 && dataPoints[endIndex].count === 0) {
      endIndex--;
    }

    return dataPoints.slice(startIndex, endIndex + 1);
  }

  buildChartData(): ChartData<"bar", DefaultDataPoint<"bar">, any> {
    let chartData = this.data;

    if(this.removeOutliers) {
      chartData = this.removeOutliersZScore(this.data);
    }

    let sortedData = chartData.slice().sort((a, b) => a - b);
    const minData = Math.floor(sortedData[0]);
    const maxData = Math.ceil(sortedData[sortedData.length - 1]);
    let dataPoints: { value: number; count: number }[] = [];

    if (this.groupData) {
      const rangeSize = 2;
      const groupRanges = Array.from({ length: (maxData - minData) / rangeSize + 1 }, (_, i) => minData + i * rangeSize);

      dataPoints = groupRanges.map(rangeStart => {
        const rangeEnd = rangeStart + rangeSize;
        const countInGroup = sortedData.filter(value => value >= rangeStart && value < rangeEnd).length;
        return { value: rangeStart, count: countInGroup };
      });
    } else {
      // Not grouping data, fill with zeros where needed
      for (let i = minData; i <= maxData; i++) {
        const countInGroup = sortedData.filter(value => Math.round(value) === i).length;
        dataPoints.push({ value: i, count: countInGroup });
      }
    }

    dataPoints = this.trimEdgeZeros(dataPoints);

    const total = dataPoints.reduce((acc, curr) => acc + curr.count, 0);

    const labels = this.groupData ? dataPoints.map(({ value }) => `${value}ms to ${value + 2}ms`) : dataPoints.map(({ value }) => `${value}ms`);
    const datasets = [{
      data: dataPoints.map(({ count }) => this.showPercentages ? (count / total * 100) : count),
      label: this.showPercentages ? this.title + " (%)": this.title,
      backgroundColor: 'rgba(0,255,41,0.66)',
      borderRadius: 5,
    }];

    return { labels, datasets };
  }


  private removeOutliersZScore(data: number[]): number[] {
    const mean = data.reduce((acc, curr) => acc + curr, 0) / data.length;
    const stdDev = Math.sqrt(data.reduce((acc, curr) => acc + Math.pow(curr - mean, 2), 0) / data.length);

    return data.filter(value => {
      const zScore = (value - mean) / stdDev;
      return Math.abs(zScore) <= 4;
    });
  }

}
