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

import {Chart as ChartJs, ChartType, DefaultDataPoint} 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 {BarPositions} from "./model/BarPositions";
import {ChartBarSettings} from "./model/ChartBarSettings";
import {ChartLegendSettings} from "./model/ChartLegendSettings";
import {Options} from "./model/Options";
import {ChartPlugin} from "./plugin/ChartPlugin";
import {ChartUtils} from "./ChartUtils";

@Component({
             moduleId: module.id,
             selector: 'ervu-chart-v2',
             templateUrl: './../../../../../src/resources/template/ervu_business_metrics/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 bars: ChartBarSettings;
  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)";

  private chartRpcService: ChartV2RpcService;
  private filterMap: { [key: string]: Filter } = {};
  private loadEnabled: boolean = true;
  private filterDelegate: FilterDelegate;
  private chartConfig;
  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_business_metrics.component.chart.plugin.ChartPlugin');
  }

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

    if (this.loadOnStart) {
      this.chartRpcService
          .getChartData(null, null, [])
          .then((config: ChartConfigDto) => {
            this.loadDataAndPaint(
                config
            )
          })
    }
  }

  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;
  }

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

  @Visible()
  public refresh() {
    this.chartRpcService
        .getChartData(null, null, this.getFilters())
        .then((configDto: ChartConfigDto) => this.loadDataAndPaint(configDto));
  }

  public 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 : {};
    chartOptions.plugins.tooltip = {enabled: this.showTooltip};

    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.bars) {
      chartOptions.scales = chartOptions.scales ? chartOptions.scales : {};
      chartOptions.scales.x = this.bars.x;
      chartOptions.scales.y = this.bars.y;

      // shadow bar treatment
      if (this.bars.shadowBar && datasetsLength > 0) {
        this.initShadowBar(datasets);
      }
      // bar positions treatment
      if (this.bars.barPositions && datasetsLength > 0) {
        this.initBarPositions(chartOptions, datasets, this.bars.barPositions);
      }
    }

    if (this.options) {
      if (this.options.borderRadiusNumber || this.options.borderRadius) {
        // border radius treatment
        this.initBorderRadius(chartOptions);
      }
    }

    chartConfig.tooltips = {
      callbacks: {
        title: function (tooltipItem, data) {
          return data.labels[tooltipItem[0].index]
        },
        label: function (tooltipItem, data) {
          let dataset = data.datasets[tooltipItem.datasetIndex];
          return dataset.label + ": " + dataset.data[tooltipItem.index];
        }
      }
    };
    this.chartConfig = chartConfig;
  }

  private initBorderRadius(options) {
    if (this.options.borderRadiusNumber
        || (this.options.borderRadius &&
            (this.options.borderRadius.topLeft ||
             this.options.borderRadius.topRight ||
             this.options.borderRadius.bottomLeft ||
             this.options.borderRadius.bottomRight))) {

      const borderRadiusNumber: number = this.options.borderRadiusNumber;
      options.borderRadius = borderRadiusNumber ? borderRadiusNumber : this.options.borderRadius;
    }
  }

  private initShadowBar(datasets) {
    const shadowBarStack: string = this.bars.shadowBar;
    const shadowBarIndex = datasets.findIndex(element => element.stack === shadowBarStack);

    if (shadowBarIndex > -1) {
      const shadowBar = datasets[shadowBarIndex];

      const stacks: string[] = [];
      datasets.forEach((element, index) => {
        let stack: string = element.stack;
        if (stack) {
          if (stack !== shadowBarStack && !stacks.includes(stack)) {
            stacks.push(stack);
          }
        }
        else {
          stack = shadowBarStack + index;
          element.stack = stack;
          stacks.push(stack);
        }
      });

      datasets.splice(shadowBarIndex, 1);

      stacks.forEach(value => {
        const cloneShadowBar = {...shadowBar};
        cloneShadowBar.data = [];
        shadowBar.data.forEach((item) => {
          cloneShadowBar.data.push({...item});
        });
        cloneShadowBar.stack = value;
        datasets.push(cloneShadowBar);
      });
    }
  }

  private initBarPositions(options, datasets, barPositions: BarPositions) {
    if (barPositions.barStackIndexes && barPositions.barStackIndexes.length > 0) {
      options.grouped = false;
      if (barPositions.barThickness) {
        options.barThickness = barPositions.barThickness;
      }

      const indexScales = options.scales[this.indexAxis];
      indexScales.type = 'linear';
      if (barPositions.max || barPositions.max === 0) {
        indexScales.max = barPositions.max;
      }
      if (barPositions.min || barPositions.min === 0) {
        indexScales.min = barPositions.min;
      }

      barPositions.barStackIndexes.forEach(barStackIndex => {
        if (barStackIndex.barStack && (barStackIndex.index || barStackIndex.index === 0)) {
          datasets.forEach(dataset => {
            if (dataset.stack == barStackIndex.barStack) {
              dataset.data.forEach(dataObject => {
                dataObject[this.indexAxis] = barStackIndex.index;
              });
            }
          })
        }
      });

      options.plugins.tooltip.callbacks = {
        title: function (tooltipItems) {
          return tooltipItems.length > 0 ? tooltipItems[0].dataset.stack : undefined;
        }
      }
    }
  }

  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));
  }

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