import 'chartjs-adapter-moment';
import {
  ChangeDetectionStrategy,
  ChangeDetectorRef,
  Component,
  ElementRef,
  Input,
  ViewChild
} from "@angular/core";

import {Chart as ChartJs, ChartData, ChartType, DefaultDataPoint, TooltipModel} from "chart.js";
import "chartjs-adapter-moment";
import {
  AdvancedProperty,
  ChartConfigDto,
  ChartScaleSettings,
  ChartTitleSettings,
  ChartV2AdditionalElementProvider,
  ChartV2RpcService,
  Control,
  Filter,
  Filterable,
  FilterDelegate,
  NotNull,
  PointSettings,
  UnsupportedOperationError,
  Visible
} from "@webbpm/base-package";
import {ChartLegendSettings} from "./model/ChartLegendSettings";
import {Options} from "./model/Options";
import {ChartPlugin} from "./plugin/ChartPlugin";
import {ChartUtils} from "./ChartUtils";
import {ErvuChartTooltip} from "./ErvuChartTooltip";

@Component({
             moduleId: module.id,
             selector: 'ervu-chart-v2',
             templateUrl: './../../../../../src/resources/template/ervu-dashboard/ErvuChartV2.html',
             changeDetection: ChangeDetectionStrategy.OnPush
           })
export class ErvuChartV2 extends Control implements Filterable {
  @Visible("false")
  public disabled: boolean;

  @Input()
  public indexAxis: string = 'x';
  @Input()
  public title: ChartTitleSettings;
  @Input()
  public legend: ChartLegendSettings;
  @Input()
  public points: PointSettings;
  @Input()
  public scales: ChartScaleSettings[];

  public options: Options;

  @NotNull()
  public loadOnStart: boolean = true;

  @Visible("false")
  @Input()
  public chart: ChartJs<ChartType, DefaultDataPoint<ChartType>, unknown>;

  @NotNull()
  @AdvancedProperty()
  public showTooltip: boolean = true;

  @NotNull()
  @AdvancedProperty()
  public responsive: boolean = true;

  @NotNull()
  @AdvancedProperty()
  public maintainAspectRatio: boolean = false;

  @NotNull()
  @AdvancedProperty()
  public addDataSetsVisibilityToggleDataset: boolean = false;

  @AdvancedProperty()
  @Visible("addDataSetsVisibilityToggleDataset==true")
  @NotNull("addDataSetsVisibilityToggleDataset==true")
  public hideAllDatasetsLabel: string = 'Cкрыть все';

  @AdvancedProperty()
  @Visible("addDataSetsVisibilityToggleDataset==true")
  @NotNull("addDataSetsVisibilityToggleDataset==true")
  public showAllDatasetsLabel: string = 'Показать все';

  @AdvancedProperty()
  public noDataText: string = "Нет данных для отображения";
  @AdvancedProperty()
  public noDataFont: string = "20px Arial";
  @AdvancedProperty()
  public noDataStyle: string = "rgb(100,100,100)";

  @ViewChild(ErvuChartTooltip)
  @Visible("false")
  public chartTooltip: ErvuChartTooltip;
  @Visible("false")
  public tooltipPosition: any;
  @Visible("false")
  public tooltipModel: TooltipModel<any>;

  private chartRpcService: ChartV2RpcService;
  private filterMap: { [key: string]: Filter } = {};
  private loadEnabled: boolean = true;
  private filterDelegate: FilterDelegate;
  private chartConfig: any;
  private plugins: ChartPlugin[] = [];

  private noDataPlugin = {
    afterDraw: (chart) => {

      if (ChartUtils.isEmpty(chart)) {
        let ctx = chart.ctx;
        let width = chart.width;
        let height = chart.height;
        ctx.textAlign = "center";
        ctx.textBaseline = "middle";
        ctx.font = this.noDataFont;
        ctx.fillStyle = this.noDataStyle;
        ctx.fillText(this.noDataText, width / 2, height / 2);
        ctx.restore();
      }
    },
  };

  private addAdditionalElementsPlugin = {
    afterDraw: () => {
      this.getScripts(ChartV2AdditionalElementProvider)
          .forEach(additionalElementProvider =>
                       additionalElementProvider.addAdditionalElement(this))
    },
  };

  constructor(el: ElementRef, cd: ChangeDetectorRef) {
    super(el, cd);
  }

  initialize(): void {
    super.initialize();
    this.chartRpcService = this.getScript(ChartV2RpcService);
    this.filterDelegate = this.getScript(FilterDelegate);
    this.plugins = this.getScripts('ervu-dashboard.component.chart.plugin.ChartPlugin');
  }

  start(): void {
    super.start();

    if (this.loadOnStart) {
      this.loadData();
    }
  }

  addFilter(filter: Filter): void {
    this.filterMap[filter.componentGuid] = filter;

    if (this.filterDelegate) {
      this.filterDelegate.addFilter(filter);
    }
  }

  disableInitialLoading() {
    this.loadOnStart = false;
    this.loadEnabled = false;

    if (this.filterDelegate) {
      this.filterDelegate.disableInitialLoading();
    }
  }

  reload(filters: Filter[]): void {
    filters.forEach((filter: Filter) => this.addFilter(filter));
    this.refresh();

    if (this.filterDelegate) {
      this.filterDelegate.reload(filters);
    }
  }

  removeFilter(guid: string): Filter {
    let ret = this.filterMap[guid];
    delete this.filterMap[guid];

    if (this.filterDelegate) {
      this.filterDelegate.removeFilter(guid);
    }
    return ret;
  }

  private getFilters(): Filter[] {
    return Object.values(this.filterMap);
  }

  @Visible()
  public refresh() {
    this.loadData(this.getFilters());
  }

  private loadData(filters?: Filter[]) {
    this.chartRpcService
        .getChartData(null, null, filters ? filters : [])
        .then((configDto: ChartConfigDto) => this.loadDataAndPaint(configDto));
  }

  private loadDataAndPaint(config: ChartConfigDto) {
    this.initAndSaveChartData(config);
    this.repaint(this.chartConfig);
  }

  private initAndSaveChartData(chartConfig) {
    //To make visible empty chart in preview mode
    if (!chartConfig) {
      chartConfig = {};
    }
    let chartOptions: any = {};
    chartConfig.options = chartOptions;
    chartOptions.maintainAspectRatio = this.maintainAspectRatio;
    chartOptions.responsive = this.responsive;

    if (this.indexAxis) {
      chartOptions.indexAxis = this.indexAxis;
    }

    chartOptions.plugins = chartOptions.plugins ? chartOptions.plugins : {};

    chartOptions.plugins.legend = this.legend ? this.legend : {};

    if (this.title && this.title.text) {
      chartOptions.plugins.title = this.title;
    }

    let datasets;
    let datasetsLength: number = 0;
    if (chartConfig.data && chartConfig.data.datasets && chartConfig.data.datasets.length > 0) {
      datasets = chartConfig.data.datasets;
      datasetsLength = datasets.length;
    }

    if (this.addDataSetsVisibilityToggleDataset && datasetsLength > 0) {
      datasets.push({
                      label: this.hideAllDatasetsLabel,
                      xAxisID: {display: false},
                      yAxisID: {display: false}
                    });

      chartOptions.plugins.legend.onClick = (e, legendItem, legend) => {
        const index = legendItem.datasetIndex;
        const chartObj = legend.chart;

        let datasetMeta = chartObj.getDatasetMeta(index);

        if (datasetsLength == index) {
          let showAll = datasetMeta.label == this.showAllDatasetsLabel;
          chartObj.data.datasets[index].label =
              showAll ? this.hideAllDatasetsLabel : this.showAllDatasetsLabel;

          for (let i = 0; i < datasetsLength; i++) {
            chartObj.setDatasetVisibility(i, showAll);
          }
          chartObj.update();
        }
        else {
          if (chartObj.isDatasetVisible(index)) {
            chartObj.hide(index);
            legendItem.hidden = true;
          }
          else {
            chartObj.show(index);
            legendItem.hidden = false;
          }
        }
      };
    }

    if (datasetsLength > 0) { // remove it after change type of ChartDataSetDto.backgroundColor
      datasets.forEach(dataset => {
        if (dataset.backgroundColors) dataset.backgroundColor = dataset.backgroundColors;
      });
    }

    if (this.points) {
      chartOptions.elements = chartOptions.elements ? chartOptions.elements : {};
      chartOptions.elements.point = this.points;
    }

    if (this.scales) {
      chartOptions.scales = {};
      this.scales.forEach((scale: ChartScaleSettings) => {
        chartOptions.scales[scale.scaleId] = scale.toChartJsSettings();
      });
    }

    if (this.options) {
      Object.assign(chartOptions, this.options);
    }

    let labelGroups: string[][];
    if (chartConfig.type == 'doughnut' && datasets.length > 1) { //Group labels only for the doughnut with inner rings
      labelGroups = this.splitLabels(chartConfig.data.labels, datasets);
    }

    chartOptions.plugins.tooltip = {
      enabled: false, //Disables default tooltip from charjs (we'll use a custom tooltip)
      external: (context) => {
        if (this.showTooltip) {
          if (!context || !this.chartTooltip) {
            return;
          }
          const positionX = this.chart.canvas.offsetLeft;
          const positionY = this.chart.canvas.offsetTop;

          this.tooltipModel = context.tooltip;
          this.tooltipPosition = {
            'left': positionX  + context.tooltip.caretX,
            'top': positionY + context.tooltip.caretY
          }
          this.cd.markForCheck();
        }
      },
      callbacks: {
        title: function (tooltipItems) {
          const item = tooltipItems[0];
          if (!labelGroups) {
            return item.label || '';
          }
          return labelGroups[item.datasetIndex][item.dataIndex] || '';
        },
        label: function (tooltipItem) {
          const dataset = tooltipItem.dataset;
          if (dataset.type == 'bar' || dataset.type == 'line') {
            return dataset.data[tooltipItem.dataIndex].y.toLocaleString();
          }
          return dataset.data[tooltipItem.dataIndex].toLocaleString();
        }
      }
    };

    if (ChartUtils.hasNotData(chartConfig.data)) {
      this.filterData(chartConfig.data);
    }

    this.chartConfig = chartConfig;
  }

  private repaint(chartData) {
    if (this.chart) {
      this.chart.destroy();
    }
    let canvasEl = this.el.nativeElement.querySelector('canvas');
    let chartConfig = {
      plugins: [this.noDataPlugin, this.addAdditionalElementsPlugin, ...this.plugins]
    };

    this.chart = new ChartJs(canvasEl, jQuery.extend(true, chartConfig, chartData));
  }

  private filterData(chartData: ChartData) {
    chartData.datasets
        .map(dataset => dataset.data)
        .reduce((acc, data) => acc.concat(data), [])
        .filter(val => val.y === null)
        .map(val => val.y = '');
  }

  private splitLabels(labels: string[], datasets: { data: number[] }[]) {
    let result: string[][] = [];
    let startIndex = 0;
    datasets.forEach((dataset) => {
      const length = dataset.data.length;
      const endIndex = startIndex + length;
      result.push(labels.slice(startIndex, endIndex));
      startIndex = endIndex;
    })
    return result;
  }


  clear() { //todo SUPPORT-4909
    throw new UnsupportedOperationError("Unsupported operation");
  }
}
