import {Injectable} from '@angular/core';
import {HttpClient, HttpErrorResponse} from '@angular/common/http';
import {BehaviorSubject, from, Observable, of, Subscriber, throwError, zip} from 'rxjs';
import {AutoCompleteValue} from '../../searchPages/searchUtils/searchHelperClasses.class';

import {EnvProperties} from '../properties/env-properties';
import {catchError, map} from 'rxjs/operators';

@Injectable({  providedIn: 'root' })
export class ISVocabulariesService {
  private vocabularies: Map<string, BehaviorSubject<AutoCompleteValue[]>> = new Map<string, BehaviorSubject<AutoCompleteValue[]>>();
  private provenanceActionVocabulary: BehaviorSubject<{}> = new BehaviorSubject(null);
  private subjectsVocabulary: BehaviorSubject<any> = new BehaviorSubject<any>(null);
  private relationsVocabulary: BehaviorSubject<any> = new BehaviorSubject<any>(null);
  private subscriptions = [];
  private vocabulariesPromises: Map<string, Promise<void>> = new Map<string, Promise<void>>();
  constructor(private http: HttpClient) {}

  ngOnDestroy() {
    this.clearSubscriptions();
  }

  clearSubscriptions() {
    this.subscriptions.forEach(subscription => {
      if (subscription instanceof Subscriber) {
        subscription.unsubscribe();
      }
    });
  }
  getVocabularyByType(field: string, entity: string, properties: EnvProperties): Observable<any> {
    let file = "";
    let vocabulary = "";
    if (field == "lang") {
      vocabulary = "dnet:languages.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if (field == "type" && (entity == "publication")) {
      vocabulary = "dnet:publication_resource.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if (field == "type" && (entity == "dataset")) {
      vocabulary = "dnet:dataCite_resource.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if (field == "type" && (entity == "software" || entity == "other")) {
      return of([]);
    } else if (field == "type" && entity == "result" ) {
      return zip(this.getVocabularyFromService("dnet:publication_resource.json", properties),this.getVocabularyFromService("dnet:dataCite_resource.json", properties));
    } else if (field == "access" && (entity == "publication" || entity == "dataset" || entity == "software" || entity == "other" || entity == "result")) {
      vocabulary = "dnet:access_modes.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if ((field == "type") && (entity == "dataprovider" || entity == "service")) {
      vocabulary = "dnet:datasource_typologies.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);

    } else if (field == "compatibility" && (entity == "dataprovider" || entity == "service")) {
      vocabulary = "dnet:datasourceCompatibilityLevel.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if (field == "country") {
      vocabulary = "dnet:countries.json";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if (field == "fos") {
      vocabulary = "fos";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    } else if (field == "sdg") {
      vocabulary = "sdg";
      return this.getVocabularyFromServiceAsync(vocabulary, properties);
    }
    return null;

  }

  getVocabularyFromServiceAsync(vocabularyName: string, properties: EnvProperties)  {
    if(!this.vocabularies.has(vocabularyName)) {
      this.vocabularies.set(vocabularyName, new BehaviorSubject<any>(null));
      if(!this.vocabulariesPromises.has(vocabularyName)) {
        this.vocabulariesPromises.set(vocabularyName,
          new Promise<void>(resolve => {
            this.subscriptions.push(this.getVocabularyFromService(vocabularyName, properties).subscribe(
              vocabularyRes => {
                this.vocabularies.get(vocabularyName).next(vocabularyRes);
                resolve();
              }, error => {
                this.vocabularies.get(vocabularyName).next(null);
                resolve();
              }
            ));
          }));
      }
    }
    return from(this.getAsync(vocabularyName));
  }
  async getAsync(vocabularyName: string): Promise<AutoCompleteValue[]> {
      await this.vocabulariesPromises.get(vocabularyName);
    return this.vocabularies.get(vocabularyName).getValue();
  }
 
  getVocabularyFromService(vocabularyName: string, properties: EnvProperties): Observable<AutoCompleteValue[]> {
    let url = properties.vocabulariesAPI + vocabularyName;
    if(vocabularyName == 'fos' || vocabularyName == 'sdg'){
      return this.getLocalVocabularyFromService(vocabularyName, properties);
    }
    return this.http.get((properties.useLongCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
      //.map(res => <any> res.json())
      .pipe(map(res => res['terms']))
      .pipe(map(res => this.parse(res, vocabularyName)))
      .pipe(catchError(this.handleError));

  }

  getLocalVocabularyFromService(vocabularyName: string, properties: EnvProperties): Observable<AutoCompleteValue[]> {
    if(vocabularyName == "sdg"){
      return this.getSDGs(properties)
          .pipe(map(res => res['sdg']))
          .pipe(map(res => this.parseSDGs(res)))
          .pipe(catchError(this.handleError));
    }else if( vocabularyName == "fos"){
      return this.getFos(properties)
          .pipe(map(res => res['fos']))
          .pipe(map(res => this.parseFOS(res)))
          .pipe(catchError(this.handleError));
    }
  }
  
  
  getFos(properties: EnvProperties): Observable<any> {
    let url = "/assets/common-assets/vocabulary/fos.json";
    return this.http.get(url).pipe(map(res => (res && res['fos']) ? res : {fos: []}));
  }
  
  getSDGs(properties: EnvProperties): Observable<any> {
    let url = "/assets/common-assets/vocabulary/sdg.json";
    return this.http.get(url).pipe(map(res => (res && res['sdg'])?res:{sdg: []}));
  }

  parseSDGs(data: any): AutoCompleteValue[] {
    var array: AutoCompleteValue[] = []
    for (var i = 0; i < data.length; i++) {
      var value: AutoCompleteValue = new AutoCompleteValue();
      value.id = data[i].id;//data[i].code;
      value.label = data[i].id;
      array.push(value);
    }
    return array;
  }

  parseFOS(data: any): AutoCompleteValue[] {
    let array: AutoCompleteValue[] = []
    let children = data.reverse();
    while(children && children.length > 0) {
      let fos = children.pop();
      let value: AutoCompleteValue = new AutoCompleteValue();
      value.id = fos.id;//data[i].code;
      value.label = fos.label;
      array.push(value);
      if(fos.children && fos.children.length > 0) {
        for (let i=fos.children.length-1; i>=0; i--) {
          children.push(fos.children[i]);
        }
      }
    }
    // for (let fos of data) {
    //   let value: AutoCompleteValue = new AutoCompleteValue();
    //   value.id = fos.id;//data[i].code;
    //   value.label = fos.label;
    //   array.push(value);
    //   if(fos.children) {
    //     for (let fos2 of fos.children) {
    //       let value: AutoCompleteValue = new AutoCompleteValue();
    //       value.id = fos2.id;//data[i].code;
    //       value.label = fos2.label;
    //       array.push(value);
    //       if(fos2.children) {
    //         for (let fos3 of fos2.children) {
    //           let value: AutoCompleteValue = new AutoCompleteValue();
    //           value.id = fos3.id;//data[i].code;
    //           value.label = fos3.label;
    //           array.push(value);
    //           // if(fos3.children) {
    //           //   for (let fos4 of fos3.children) {
    //           //     let value: AutoCompleteValue = new AutoCompleteValue();
    //           //     value.id = fos4.id;//data[i].code;
    //           //     value.label = fos4.label;
    //           //     array.push(value);
    //           //   }
    //           // }
    //         }
    //       }
    //     }
    //   }
    // }
    return array;
  }
  
  parse(data: any, vocabularyName: string): AutoCompleteValue[] {
    var array: AutoCompleteValue[] = []
    for (var i = 0; i < data.length; i++) {
      var value: AutoCompleteValue = new AutoCompleteValue();
      value.id = data[i].englishName;//data[i].code;
      if (vocabularyName == 'dnet:countries.json') { //use Country code instead of country name
        value.id = data[i].code;
      }
      value.label = data[i].englishName;
      array.push(value);
    }

    return array;

  }

  getProvenanceActionVocabulary(properties: EnvProperties): Observable<any> {
      let vocabulary = "dnet:provenanceActions.json";
      return from(this.getProvenanceActionVocabularyFromServiceAsync(vocabulary, properties));
    }

  async getProvenanceActionVocabularyFromServiceAsync (vocabularyName: string, properties: EnvProperties): Promise<{}> {
    if(!this.provenanceActionVocabulary || !this.provenanceActionVocabulary.getValue()) {
      await  new Promise<void>(resolve => {
        this.subscriptions.push(this.getProvenanceActionVocabularyFromService(vocabularyName, properties).subscribe(
          vocabularyRes => {
            this.provenanceActionVocabulary.next(vocabularyRes);
            resolve();
          },
          error => {
            this.provenanceActionVocabulary.next(null);
            resolve();
          }
        ));
      });
      // this.clearSubscriptions();
    }
    return this.provenanceActionVocabulary.getValue();
  }

  getProvenanceActionVocabularyFromService (vocabularyName: string, properties: EnvProperties): any {
    let url = properties.vocabulariesAPI+vocabularyName;

    return this.http.get((properties.useLongCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
      .pipe(map(res => res['terms']))
      .pipe(map(res => this.parseProvenanceActionVocabulary(res)));
  }

  parseProvenanceActionVocabulary(terms: any) {
    var provenanceActionVocabulary: {} = {};
    for(let term of terms) {
      provenanceActionVocabulary[term.code] = term.englishName;
    }
    return provenanceActionVocabulary;
  }

  getSubjectsVocabulary(properties: EnvProperties): Observable<any> {
    let vocabulary = "dnet:subject_classification_typologies.json";
    return from(this.getSubjectsVocabularyFromServiceAsync(vocabulary, properties));
  }

  async getSubjectsVocabularyFromServiceAsync (vocabularyName: string, properties: EnvProperties): Promise<{}> {
    if(!this.subjectsVocabulary || !this.subjectsVocabulary.getValue()) {
      await  new Promise<void>(resolve => {
        this.subscriptions.push(this.getSubjectsVocabularyFromService(vocabularyName, properties).subscribe(
          vocabularyRes => {
            // console.log(vocabularyRes);
            this.subjectsVocabulary.next(vocabularyRes);
            resolve();
          },
          error => {
            this.subjectsVocabulary.next(null);
            resolve();
          }
        ));
      });
      // this.clearSubscriptions();
    }
    return this.subjectsVocabulary.getValue();
  }

  getSubjectsVocabularyFromService (vocabularyName: string, properties: EnvProperties): any {
    let url = properties.vocabulariesAPI+vocabularyName;

    return this.http.get((properties.useLongCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
      .pipe(map(res => res['terms']))
    // .pipe(map(res => res.code))
      .pipe(map(res => this.parseSubjectsVocabulary(res)));
  }

  parseSubjectsVocabulary(terms: any) {
    var subjectsVocabulary: {} = {};
    for(let term of terms) {
      if(term.code != "keyword") {
        subjectsVocabulary[term.code] = term.englishName;
      }
    }
    return subjectsVocabulary;
  }

  getRelationsVocabulary(properties: EnvProperties): Observable<any> {
    let vocabulary = "dnet:relation_relClass.json";
    return from(this.getRelationsVocabularyFromServiceAsync(vocabulary, properties));
  }

  async getRelationsVocabularyFromServiceAsync (vocabularyName: string, properties: EnvProperties): Promise<{}> {
    if(!this.relationsVocabulary || !this.relationsVocabulary.getValue()) {
      await  new Promise<void>(resolve => {
        this.subscriptions.push(this.getRelationsVocabularyFromService(vocabularyName, properties).subscribe(
          vocabularyRes => {
            this.relationsVocabulary.next(vocabularyRes);
            resolve();
          },
          error => {
            this.relationsVocabulary.next(null);
            resolve();
          }
        ));
      });
    }
    return this.relationsVocabulary.getValue();
  }

  getRelationsVocabularyFromService (vocabularyName: string, properties: EnvProperties): any {
    let url = properties.vocabulariesAPI+vocabularyName;

    return this.http.get((properties.useLongCache) ? (properties.cacheUrl + encodeURIComponent(url)) : url)
      .pipe(map(res => res['terms']))
      .pipe(map(res => this.parseRelationsVocabulary(res)));
  }

  parseRelationsVocabulary(terms: any) {
    var relationsVocabulary: {} = {};
    for(let term of terms) {
      if(term.code != "keyword") {
        relationsVocabulary[term.code] = term.englishName;
      }
    }
    return relationsVocabulary;
  }


  private handleError(error: HttpErrorResponse) {
    // in a real world app, we may send the error to some remote logging infrastructure
    // instead of just logging it to the console
    console.log(error);
    return throwError(error || 'Server error');
  }
}
