import hsv2rgb from 'hsv2rgb';
import {Feature} from 'ol';
import {Fill, Stroke, Style} from 'ol/style';
import {Geometry} from 'ol/geom';
import {Injectable} from '@angular/core';
import {TopoJSON} from 'ol/format';
import {Vector as VectorLayer} from 'ol/layer';
import {Vector as VectorSource} from 'ol/source';

import {HsConfig} from 'hslayers-ng';
import {HsEventBusService} from 'hslayers-ng';
import {HsLanguageService} from 'hslayers-ng';
import {HsLayerManagerService} from 'hslayers-ng';
import {HsLayoutService} from 'hslayers-ng';
import {HsPanelContainerService} from 'hslayers-ng';
import {HsSidebarService} from 'hslayers-ng';

import {AdjusterComponent} from './adjuster/adjuster.component';
import {AdjusterEventService} from './adjuster/adjuster-event.service';
import {AdjusterLegendService} from './adjuster/adjuster-legend.service';
import {AdjusterService} from './adjuster/adjuster.service';
import {AppConfig, krajeLayer, masLayer, obceIndexLayer, okresyLayer} from './app.config';

@Injectable({providedIn: 'root'})
export class AppService {
  constructor(
    public adjusterService: AdjusterService,
    public adjusterEventService: AdjusterEventService,
    public adjusterLegendService: AdjusterLegendService,
    public hsConfig: HsConfig,
    public hsEventBus: HsEventBusService,
    public hsLanguageService: HsLanguageService,
    public hsLayerManagerService: HsLayerManagerService,
    public hsLayoutService: HsLayoutService,
    public hsPanelContainerService: HsPanelContainerService,
    public hsSidebarService: HsSidebarService
  ) {
    this.hsSidebarService.buttons.push({
      panel: 'adjuster',
      module: 'pra.adjuster',
      order: 0,
      title: () =>
        this.hsLanguageService.getTranslation('ADJUSTER.adjustFactors'),
      description: 'Adjust factors for computation',
      icon: 'icon-analytics-piechart',
    });
    this.hsPanelContainerService.create(AdjusterComponent, {});
    this.prepareLayers();
    this.adjusterEventService.loaded.subscribe(({success}) => {
      if (success) {
        this.adjusterLegendService.refreshColorPalette(
          this.adjusterService.numberOfClusters
        );
      }
    });
    this.hsEventBus.layoutLoads.subscribe(() => {
      this.init();
    });
    this.hsEventBus.olMapLoads.subscribe(() => {
      this.ensureLayerIsLoaded(obceIndexLayer);
      for (const method of this.adjusterService.methods) {
        this.ensureLayerIsLoaded(method.layer);
      }
    });
  }

  init(): void {
    this.hsLanguageService.setLanguage('cs');
    this.hsLayoutService.setDefaultPanel('adjuster');
  }

  /**
   * Create separate layer fo each clustering method and add it to default_layers
   */
  prepareLayers(): void {
    for (const method of this.adjusterService.methods) {
      method.layer = new VectorLayer({
        properties: {
          editor: {editable: false},
          autoLegend: false,
          title: `Obce ČR: ${method.name.replace(/\((.+?)\)/g, '')} clusters`,
          attributions: ['CC-BY ČÚZK, 2021'],
          popUp: {
            attributes: [
              {
                attribute: method.codename,
                label: 'ID shluku',
              },
            ],
          },
        },
        source: new VectorSource({
          format: new TopoJSON({dataProjection: 'EPSG:5514'}),
          url: require('./data/obce_cr_20210310_5p_5514.topojson').default,
          overlaps: false,
        }),
        visible: true,
        style: this.generateStyle(method.codename),
      });
      method.layer
        .getSource()
        .on('featuresloadend', () =>
          this.adjusterEventService.layerReady.next({name: method.codename})
        );
      AppConfig.default_layers.push(method.layer as any);
    }
    // obceIndexLayer, okresyLayer and krajeLayer must be pushed in this order
    // so they will display in correct order
    AppConfig.default_layers.push(obceIndexLayer as any);
    obceIndexLayer
      .getSource()
      .on('featuresloadend', () =>
        this.adjusterEventService.layerReady.next({name: 'index'})
      );
    (obceIndexLayer.getSource() as any).legend_categories = this.adjusterLegendService.createIndexLegend();
    AppConfig.default_layers.push(masLayer as any);
    AppConfig.default_layers.push(okresyLayer as any);
    AppConfig.default_layers.push(krajeLayer as any);
    this.hsConfig.update(AppConfig);
  }

  /**
   * Layer must be turned visible so it is loaded into the map.
   * This method ensures that layers critical for further computations
   * are for at least a moment turned visible and thus loaded properly.
   * @param {VectorLayer} layer One of layers used to calculate index or clusters
   */
  ensureLayerIsLoaded(layer: VectorLayer<VectorSource<Geometry>>): void {
    const layerVisible = layer.getVisible();
    if (!layerVisible) {
      layer.setVisible(true);
      setTimeout(() => layer.setVisible(false), 10);
    }
  }

  /**
   * @description Function factory for generating style functions based on different clustering methods
   * @param {string} method selected method
   * @returns {function} style function
   */
  private generateStyle(method: string) {
    return (feature: Feature<Geometry>): Style => {
      if (isNaN(feature.get(method))) {
        return new Style({
          fill: new Fill({
            color: '#FFF',
          }),
          stroke: new Stroke({
            color: '#3399CC',
            width: 0.25,
          }),
        });
      } else {
        return new Style({
          fill: new Fill({
            color: this.adjusterLegendService.colorPalette[
              feature.get(method) - 1
            ],
          }),
          stroke: new Stroke({
            color: '#FFF',
            width: 0.25,
          }),
        });
      }
    };
  }
}
