import {HttpClient} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Vector as VectorLayer} from 'ol/layer';

import {HsConfig} from 'hslayers-ng/config.service';
import {HsLayerManagerMetadataService} from 'hslayers-ng/components/layermanager/layermanager-metadata.service';
import {HsLayerManagerService} from 'hslayers-ng/components/layermanager';
import {HsUtilsService} from 'hslayers-ng/components/utils/utils.service';

import attractivenessConfig from '../attractiveness.config.json';
import clusteringMethods from '../data/clustering_methods.json';
import {AdjusterEventService} from './adjuster-event.service';
import {obce, obceIndexLayer, osmLayer} from '../app.config';

@Injectable({providedIn: 'root'})
export class AdjusterService {
  /** To be read from a config file */
  allowedClusteringMethods: Array<string>;
  /** To be read from a config file */
  initialWeights;
  /** To be read from a config file */
  serviceBaseUrl: string;
  /** Used in the UI as a selector */
  allowClusters = true;
  /** Used in the UI as a selector */
  allowIndex = true;
  factors = [];
  numberOfClusters: number;
  //method: string;
  methods: Array<{
    codename: string;
    layer?: VectorLayer;
    name: string;
    type: string;
  }>;
  private _clusteringInProcess: boolean;
  private _clustersLoaded: boolean;
  private _loadInProcess: boolean;
  private _raiInProcess: boolean;

  constructor(
    public adjusterEventService: AdjusterEventService,
    public hsConfig: HsConfig,
    public hsLayerMetadataService: HsLayerManagerMetadataService,
    public hsLayerManagerService: HsLayerManagerService,
    public hsUtilsService: HsUtilsService,
    public $http: HttpClient
  ) {
    // First safely set configurable properties
    this.allowedClusteringMethods =
      attractivenessConfig?.allowedClusteringMethods ?? [];
    this.initialWeights = attractivenessConfig?.initialWeights ?? {};
    this.serviceBaseUrl =
      attractivenessConfig?.serviceBaseUrl ??
      'https://publish.lesprojekt.cz/nodejs/';
    // 'https://jmacura.ml/ws/' // 'http://localhost:3000/'

    this.methods = clusteringMethods.filter((m) =>
      this.allowedClusteringMethods.includes(m.codename)
    );
    this.numberOfClusters = 9;
  }

  /**
   * Sends a request to polirural-attractiveness-service
   * and applies the returned values
   */
  apply(): void {
    if (this.allowIndex) {
      this.calculateIndex();
    }
    if (this.allowClusters) {
      this.calculateClusters();
    }
  }

  calculateIndex(): void {
    this._raiInProcess = true;
    this.$http
      .post(this.serviceBaseUrl + 'cz/scores/', {
        factors: this.factors.map((f) => {
          return {
            factor: f.name,
            weight: f.weight,
            datasets: f.datasets
              .filter((ds) => ds.included)
              .map((ds) => ds.name),
          };
        }),
      })
      .toPromise()
      .then((attractivenessData: any[]) => {
        console.log(attractivenessData);
        // Spread the 'aggregate' value between 0 and 1
        const min = attractivenessData.reduce((a, b) =>
          a.aggregate < b.aggregate ? a : b
        ).aggregate;
        const max = attractivenessData.reduce((a, b) =>
          a.aggregate > b.aggregate ? a : b
        ).aggregate;
        const coefficient = 1 / (max - min);
        const constant = -min * coefficient;
        attractivenessData.forEach((a) => {
          a.aggregate *= coefficient;
          a.aggregate += constant;
        });
        // Store relation between region and its data in a hash-table-like structure
        // more memory consuming, but faster then find()
        const codeRecordRelations = {};
        attractivenessData.forEach((a) => {
          codeRecordRelations[a.code] = a;
        });
        let errs = 0;
        //let logs = 0;
        console.time('forEach-Index');
        obce.forEachFeature((feature) => {
          // Pair each feature with its attractivity data
          const featureData = codeRecordRelations[feature.get('nationalCode')];
          if (!featureData) {
            if (errs < 20) {
              errs++;
              console.warn(
                `No data for feature ${feature.get('nationalCode')}`
              );
              console.log(feature);
            }
            return;
          }
          /*logs++;
          if (logs % 100 == 0) {
            console.log(`processed ${logs} items`);
          }*/
          Object.keys(featureData).forEach((key, index) => {
            if (key !== 'lau2') {
              feature.set(key, featureData[key], true); //true stands for "silent" - important for performance!
            }
          });
        });
        // Since we are updating the features silently, we now have to refresh manually
        obce.getFeatures()[0].dispatchEvent('change');
        console.timeEnd('forEach-Index');
        this._raiInProcess = false;
        this.adjusterEventService.loaded.next({
          success: true,
          type: 'index',
        });
      })
      .catch((error) => {
        console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`);
        console.log(error);
        this._raiInProcess = false;
        this.adjusterEventService.loaded.next({
          success: true,
          type: 'index',
          err: error,
        });
      });
  }

  calculateClusters(): void {
    this._clusteringInProcess = true;
    this.$http
      .post(this.serviceBaseUrl + 'cz/clusters/', {
        numberOfClusters: this.numberOfClusters,
        factors: this.factors.map((f) => {
          return {
            factor: f.name,
            weight: f.weight,
            datasets: f.datasets
              .filter((ds) => ds.included)
              .map((ds) => ds.name),
          };
        }),
      })
      .toPromise()
      .then((data: any) => {
        console.log('data received', data);
        let logs = 0;
        let errs = 0;
        const clusterData = data.response;
        console.log(obceIndexLayer);
        /*let sublayers = [];
          const oldSublayers = obceLayer.get('Layer');
          if (oldSublayers !== undefined && Array.isArray(oldSublayers)) {
            for (const sublyr of oldSublayers) {
              sublyr.getSource().clear();
            }
            if (oldSublayers.length > this.numberOfClusters) {
              sublayers = oldSublayers.slice(0, this.numberOfClusters);
            }
          }
          let i = sublayers.length;
          while (sublayers.length < this.numberOfClusters) {
            const sublyr = new VectorLayer({
              title: `Cluster ${i + 1}`,
              name: i + 1 + '',
              source: new VectorSource({}),
            });
            sublayers.push(sublyr);
            sublyr.Title = `Cluster ${i + 1}`;
            sublyr.Name = `c${i + 1}`;
            i++;
          }*/
        console.time('forEach-Cluster');
        obce.forEachFeature((feature) => {
          // Pair each feature with its clustering data
          const featureData = clusterData.find(
            // NOTE: Do NOT add triple equal sign!
            (item) => item['lau2'] == feature.get('nationalCode')
          );
          if (!featureData) {
            if (errs < 20) {
              errs++;
              console.warn(
                `No data for feature ${feature.get('nationalCode')}`
              );
              console.log(feature);
            }
            return;
          }
          logs++;
          if (logs % 100 == 0) {
            console.log(`processed ${logs} items`);
          }
          Object.keys(featureData).forEach(function (key, index) {
            if (key !== 'lau2') {
              feature.set(key, featureData[key], true);
            }
          });
        });
        //const clusters = [];
        //const obceFeatures: Array<any> = obce.getFeatures();
        /*for (const region of clusterData) {
            if (!clusters.includes(region[this.method])) {
              clusters.push(region[this.method]);
            }
            const feature = obceFeatures.find(
              // NOTE: Do NOT add triple equal sign!
              (f) => f.get('nationalCode') == region['lau2']
            );
            if (!feature) {
              if (errs < 20) {
                errs++;
                console.warn(`No feature matches region ${region['lau2']}`);
                console.log(region);
              }
              continue;
            }
            logs++;
            if (logs % 100 == 0) {
              console.log(`processed ${logs} items`);
            }
            Object.keys(region).forEach(function (key, index) {
              if (key !== 'lau2') {
                feature.set(key, region[key], true);
              }
            });
            if (logs % 100 == 0) {
              //sublayers[0].getSource().addFeature(feature);
            }
            //sublayers[region[this.method] - 1].getSource().addFeature(feature);
          }*/
        //obceLayer.set('Layer', sublayers);
        //this.hsLayerMetadataService.fillMetadata(obceLayer);
        //console.log(sublayers[0].getSource().getFeatures());
        obce.getFeatures()[0].dispatchEvent('change');
        console.timeEnd('forEach-Cluster');
        console.log('clustering done!');
        //this.clusters = clusters;
        this._clustersLoaded = true;
        this._clusteringInProcess = false;
        this.adjusterEventService.loaded.next({
          success: true,
          type: 'clusters',
        });
      })
      .catch((error) => {
        console.warn(`Error obtaining data from ${this.serviceBaseUrl}.`);
        console.log(error);
        this._clusteringInProcess = false;
        this.adjusterEventService.loaded.next({
          success: false,
          type: 'clusters',
          err: error,
        });
      });
  }

  init(): void {
    this._loadInProcess = true;
    this.$http
      .get(this.serviceBaseUrl + 'cz/datasets/')
      .toPromise()
      .then((data: any) => {
        this.factors = data.map((dataset) => {
          return {
            name: dataset.Factor,
            weight: this.initialWeights[dataset.Factor] ?? 1,
            datasets: [],
          };
        });
        this.factors = this.hsUtilsService.removeDuplicates(
          this.factors,
          'name'
        );
        this.factors.forEach((factor) => {
          factor.datasets = data
            .filter((ds) => ds.Factor === factor.name)
            .map((ds) => {
              return {
                name: ds.Name,
                desc: ds.Description,
                included: true,
              };
            });
        });
        this._loadInProcess = false;
        this.apply();
        this.hsLayerManagerService.setGreyscale(osmLayer);
      })
      .catch((error) => {
        console.warn(`Web service at ${this.serviceBaseUrl} unavailable!`);
        console.log(error);
        this._loadInProcess = false;
      });
  }

  clustersLoaded(): boolean {
    return this._clustersLoaded;
  }

  /**
   * @returns {boolean} true if clustering or index processing is in process or loading data, false otherwise
   */
  isInProcess(): boolean {
    return (
      this._loadInProcess || this._clusteringInProcess || this._raiInProcess
    );
  }

  isLoading(): boolean {
    return this._loadInProcess;
  }

  isClustering(): boolean {
    return this._clusteringInProcess;
  }

  isCalculatingRAI(): boolean {
    return this._raiInProcess;
  }
}
