import {
  Component, Input, AfterViewInit, OnDestroy, OnChanges, SimpleChanges, ViewChild, ElementRef,
} from '@angular/core';
import { UUID } from 'angular2-uuid';
import { utcToZonedTime } from 'date-fns-tz';
import Dygraph, { dygraphs } from 'dygraphs';
// eslint-disable-next-line
import smoothPlotter from 'dygraphs/src/extras/smooth-plotter.js';
import { ThemeUtils } from 'app/core/classes/theme-utils/theme-utils';
import { ViewComponent } from 'app/core/components/view/view.component';
import { ReportingData } from 'app/interfaces/reporting.interface';
import { CoreService } from 'app/services/core-service/core.service';
import { ThemeService, Theme } from 'app/services/theme/theme.service';
import { Report } from '../report/report.component';

interface Conversion {
  value: number;
  prefix?: string;
  suffix?: string;
  shortName?: string;
}

@Component({
  selector: 'linechart',
  templateUrl: './line-chart.component.html',
  styleUrls: ['./line-chart.component.scss'],
})
export class LineChartComponent extends ViewComponent implements AfterViewInit, OnDestroy, OnChanges {
  @ViewChild('wrapper', { static: true }) el: ElementRef;
  @Input() chartId: string;
  @Input() chartColors: string[];
  @Input() set data(value: ReportingData) {
    this._data = value;
  }
  get data(): ReportingData {
    return this._data;
  }
  @Input() isReversed = false;
  @Input() report: Report;
  @Input() title: string;
  @Input() timezone: string;
  @Input() stacked = false;

  @Input() legends?: string[];
  @Input() type = 'line';
  @Input() convertToCelsius?: true;
  @Input() dataStructure: 'columns'; // rows vs columns
  @Input() minY?: number = 0;
  @Input() maxY?: number = 100;
  @Input() labelY?: string = 'Label Y';
  @Input() interactive = false;

  library = 'dygraph'; // dygraph or chart.js

  chart: Dygraph;
  conf: any;
  columns: any;

  units = '';
  yLabelPrefix: string;
  showLegendValues = false;

  _colorPattern: string[] = ['#2196f3', '#009688', '#ffc107', '#9c27b0', '#607d8b', '#00bcd4', '#8bc34a', '#ffeb3b', '#e91e63', '#3f51b5'];
  get colorPattern(): string[] {
    return this.chartColors;
  }

  set colorPattern(value) {
    this._colorPattern = value;
  }

  theme: Theme;
  timeFormat = '%H:%M';
  culling = 6;
  controlUid: string;

  private utils: ThemeUtils;

  constructor(private core: CoreService, public themeService: ThemeService) {
    super(themeService);
    this.utils = new ThemeUtils();
    this.controlUid = 'chart_' + UUID.UUID();
  }

  render(update?: boolean): void {
    this.renderGraph(update);
  }

  // dygraph renderer
  renderGraph(update?: boolean): void {
    if (this.isReversed) {
      this.data.legend = this.data.legend.reverse();
      this.data.data.forEach((row, i) => this.data.data[i] = row.slice().reverse());
      this.data.aggregations.min = this.data.aggregations.min.slice().reverse();
      this.data.aggregations.max = this.data.aggregations.max.slice().reverse();
      this.data.aggregations.mean = this.data.aggregations.mean.slice().reverse();
    }

    const data = this.makeTimeAxis(this.data);
    const labels = data.shift();

    const fg2 = this.themeService.currentTheme().fg2;
    const fg2Type = this.utils.getValueType(fg2);
    const fg2Rgb = fg2Type == 'hex' ? this.utils.hexToRgb(this.themeService.currentTheme().fg2).rgb : this.utils.rgbToArray(fg2);
    const gridLineColor = 'rgba(' + fg2Rgb[0] + ', ' + fg2Rgb[1] + ', ' + fg2Rgb[2] + ', 0.25)';
    const yLabelSuffix = this.labelY === 'Bits/s' ? this.labelY.toLowerCase() : this.labelY;

    // TODO: Try: dygraphs.Options
    const options = {
      drawPoints: false, // Must be disabled for smoothPlotter
      pointSize: 1,
      includeZero: true,
      highlightCircleSize: 4,
      strokeWidth: 1,
      colors: this.colorPattern,
      labels, // time axis
      ylabel: this.yLabelPrefix + yLabelSuffix,
      gridLineColor,
      showLabelsOnHighlight: false,
      labelsSeparateLines: true,
      axes: {
        y: {
          yRangePad: 24,
          axisLabelFormatter: (numero: number) => {
            const converted = this.formatLabelValue(numero, this.inferUnits(this.labelY), 1, true);
            const suffix = converted.suffix ? converted.suffix : '';
            return this.limitDecimals(converted.value).toString() + suffix;
          },
        },
      },
      legendFormatter: (data: any) => {
        const getSuffix = (converted: Conversion): string => {
          if (converted.shortName !== undefined) {
            return converted.shortName;
          }

          return converted.suffix !== undefined ? converted.suffix : '';
        };

        const clone = { ...data };
        clone.series.forEach((item: any, index: number) => {
          if (!item.y) { return; }
          const converted = this.formatLabelValue(item.y, this.inferUnits(this.labelY), 1, true);
          const suffix = getSuffix(converted);
          clone.series[index].yHTML = this.limitDecimals(converted.value).toString() + suffix;
          if (!clone.stackedTotal) {
            clone.stackedTotal = 0;
          }
          clone.stackedTotal += item.y;
        });
        if (clone.stackedTotal >= 0) {
          const converted = this.formatLabelValue(clone.stackedTotal, this.inferUnits(this.labelY), 1, true);
          const suffix = getSuffix(converted);
          clone.stackedTotalHTML = this.limitDecimals(converted.value).toString() + suffix;
        }
        this.core.emit({ name: 'LegendEvent-' + this.chartId, data: clone, sender: this });
        return '';
      },
      series: () => {
        const s: any = {};
        this.data.legend.forEach((item) => {
          s[item] = { plotter: smoothPlotter };
        });

        return s;
      },
      drawCallback: (dygraph: any) => {
        if (dygraph.axes_) {
          const numero = dygraph.axes_[0].maxyval;
          const converted = this.formatLabelValue(numero, this.inferUnits(this.labelY));
          if (converted.prefix) {
            this.yLabelPrefix = converted.prefix;
          } else {
            this.yLabelPrefix = '';
          }
        } else {
          console.warn('axes not found');
        }
      },
      stackedGraph: this.stacked,
    } as unknown as dygraphs.Options;

    if (update) {
      this.chart.updateOptions(options);
    } else {
      this.chart = new Dygraph(this.el.nativeElement, data, options);
    }
  }

  makeColumn(data: ReportingData, legendKey: number): number[] {
    const result: any = [];

    data.data.forEach((report) => {
      const value = report[legendKey];
      result.push(value);
    });

    return result;
  }

  protected makeTimeAxis(rd: ReportingData): any[] {
    const structure = this.library == 'chart.js' ? 'columns' : 'rows';
    if (structure == 'rows') {
      // Push dates to row based data...
      const rows = [];
      // Add legend with axis to beginning of array
      const legend = Object.assign([], rd.legend);
      legend.unshift('x');
      rows.push(legend);

      for (let i = 0; i < rd.data.length; i++) {
        const item = Object.assign([], rd.data[i]);
        let dateStr = utcToZonedTime(new Date(rd.start * 1000 + i * rd.step * 1000), this.timezone).toString();
        // UTC: 2020-12-17T16:33:10Z
        // Los Angeles: 2020-12-17T08:36:30-08:00
        // Change dateStr from '2020-12-17T08:36:30-08:00' to '2020-12-17T08:36'
        const list = dateStr.split(':');
        dateStr = list.join(':');
        const date = new Date(dateStr);

        item.unshift(date);
        rows.push(item);
      }

      return rows;
    } if (structure == 'columns') {
      const columns = [];

      for (let i = 0; i < rd.data.length; i++) {
        const date = new Date(rd.start * 1000 + i * rd.step * 1000);
        columns.push(date);
      }

      return columns;
    }
  }

  private processThemeColors(theme: Theme): string[] {
    this.theme = theme;
    return theme.accentColors.map((color) => theme[color]);
  }

  private createColorObject(): Record<string, string> {
    const obj: Record<string, string> = {};
    this.legends.forEach((item, index) => {
      obj[item] = this.colorPattern[index];
    });
    return obj;
  }

  fetchData(rrdOptions: { start: number; end: number }, timeformat?: string, culling?: number): void {
    if (timeformat) {
      this.timeFormat = timeformat;
    }
    if (culling) {
      this.culling = culling;
    }

    // Convert from milliseconds to seconds for epoch time
    rrdOptions.start = Math.floor(rrdOptions.start / 1000);
    if (rrdOptions.end) {
      rrdOptions.end = Math.floor(rrdOptions.end / 1000);
    }
  }

  inferUnits(label: string): string {
    // if(this.report.units){ return this.report.units; }
    // Figures out from the label what the unit is
    let units = label;
    if (label.includes('%')) {
      units = '%';
    } else if (label.includes('°')) {
      units = '°';
    } else if (label.toLowerCase().includes('bytes')) {
      units = 'bytes';
    } else if (label.toLowerCase().includes('bits')) {
      units = 'bits';
    }

    if (typeof units == 'undefined') {
      console.warn('Could not infer units from ' + this.labelY);
    }

    return units;
  }

  formatLabelValue(value: number, units: string, fixed?: number, prefixRules?: boolean): Conversion {
    let output: Conversion = { value };
    if (!fixed) { fixed = -1; }
    if (typeof value !== 'number') { return value; }

    switch (units.toLowerCase()) {
      case 'bits':
      case 'bytes':
        output = this.convertKmgt(value, units.toLowerCase(), fixed, prefixRules);
        break;
      case '%':
      case '°':
      default:
        output = this.convertByKilo(value);
    }

    return output;
  }

  convertByKilo(input: number): Conversion {
    if (typeof input !== 'number') { return input; }
    let output = input;
    let suffix = '';

    if (input >= 1000000) {
      output = input / 1000000;
      suffix = 'm';
    } else if (input < 1000000 && input >= 1000) {
      output = input / 1000;
      suffix = 'k';
    }

    return { value: output, suffix };
  }

  limitDecimals(numero: number): string | number {
    const subZero = numero.toString().split('.');
    const decimalPlaces = subZero && subZero[1] ? subZero[1].length : 0;
    return decimalPlaces > 2 ? numero.toFixed(2) : numero;
  }

  convertKmgt(value: number, units: string, fixed?: number, prefixRules?: boolean): Conversion {
    const kilo = 1024;
    const mega = kilo * 1024;
    const giga = mega * 1024;
    const tera = giga * 1024;

    let prefix = '';
    let output: number = value;
    let shortName = '';

    if (value > tera || (prefixRules && this.yLabelPrefix == 'Tera')) {
      prefix = 'Tera';
      shortName = 'TiB';
      output = value / tera;
    } else if ((value < tera && value > giga) || (prefixRules && this.yLabelPrefix == 'Giga')) {
      prefix = 'Giga';
      shortName = 'GiB';
      output = value / giga;
    } else if ((value < giga && value > mega) || (prefixRules && this.yLabelPrefix == 'Mega')) {
      prefix = 'Mega';
      shortName = 'MiB';
      output = value / mega;
    } else if ((value < mega && value > kilo || (prefixRules && this.yLabelPrefix == 'Kilo'))) {
      prefix = 'Kilo';
      shortName = 'KB';
      output = value / kilo;
    }

    if (units == 'bits') {
      shortName = shortName.replace(/i/, '').trim();
      shortName = ` ${shortName.charAt(0).toUpperCase()}${shortName.substr(1).toLowerCase()}`; // Kb, Mb, Gb, Tb
    }

    return { value: output, prefix, shortName };
  }

  ngAfterViewInit(): void {
    this.render();
  }

  ngOnChanges(changes: SimpleChanges): void {
    if (changes.data) {
      this.render();
    }

    if (changes.data) {
      if (this.chart) {
        // this.chart.destroy();
        this.render(true);
      } else {
        this.render();// make an update method?
      }
    }
  }

  ngOnDestroy(): void {
    this.core.unregister({ observerClass: this });

    this.chart.destroy();
  }
}