import {UrlSegment} from '@angular/router';
import {AbstractControl, UntypedFormGroup, ValidationErrors, ValidatorFn, Validators} from "@angular/forms";
import {Stakeholder} from "../monitor/entities/stakeholder";
import {CommunityInfo} from "../connect/community/communityInfo";
import {properties} from "../../../environments/environment";
import {OpenaireEntities} from "./properties/searchFields";
import {StakeholderConfiguration} from "../monitor-admin/utils/indicator-utils";

export class Dates {
  public static yearMin = 1800;
  public static yearMax = (new Date().getFullYear()) + 10;
  public static currentYear = (new Date().getFullYear());
  
  public static isValidYear(yearString, yearMin = this.yearMin, yearMax = this.yearMax) {
    // First check for the pattern
    if (!/^\d{4}$/.test(yearString))
      return false;
    var year = parseInt(yearString, 10);
    
    // Check the ranges of month and year
    return !(year < yearMin || year > yearMax);
  }
  
  //format YYYY-MM-DD
  public static isValidDate(dateString: string) {
    // First check for the pattern
    if (!/^\d{4}\-\d{1,2}\-\d{1,2}$/.test(dateString))
      return false;
    
    // Parse the date parts to integers
    var parts = dateString.split("-");
    var day = parseInt(parts[2], 10);
    var month = parseInt(parts[1], 10);
    var year = parseInt(parts[0], 10);
    if (!this.isValidYear(parts[0])) {
      return false;
    }
    
    // Check the ranges of month and year
    if (month == 0 || month > 12)
      return false;
    
    var monthLength = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
    
    // Adjust for leap years
    if (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0))
      monthLength[1] = 29;
    
    // Check the range of the day
    return day > 0 && day <= monthLength[month - 1];
    
  }
  
  public static getDateToday(): Date {
    var myDate = new Date();
    return myDate;
    
  }
  
  public static getDateToString(myDate: Date): string {
    var date: string = myDate.getFullYear() + "-";
    date += ((myDate.getMonth() + 1) < 10) ? "0" + (myDate.getMonth() + 1) : (myDate.getMonth() + 1);
    date += "-";
    date += (myDate.getDate() < 10) ? "0" + myDate.getDate() : myDate.getDate();
    return date;
    
  }
  
  public static getDateXMonthsAgo(x: number): Date {
    var myDate = new Date();
    myDate.setMonth(myDate.getMonth() - x);
    return myDate;
    
  }
  
  public static getDateXYearsAgo(x: number): Date {
    var myDate = new Date();
    myDate.setFullYear(myDate.getFullYear() - x);
    return myDate;
    
  }
  
  public static getDateFromString(date: string): Date {
    
    var myDate = new Date();
    myDate.setFullYear(+date.substring(0, 4));
    myDate.setMonth(((date.length > 5) ? (+date.substring(5, 7) - 1) : (0)));
    myDate.setDate(((date.length > 8) ? (+date.substring(8, 11)) : (1)));
    return myDate;
    
  }
  
  public static getDate(dateString: string): Date {
    let date = new Date(dateString);
    if (Object.prototype.toString.call(date) === "[object Date]") {
      if (isNaN(date.getTime())) {
        return null;
      } else {
        return date;
      }
    } else {
      return null;
    }
  }
  
  public static timeSince(date: Date) {
    
    let seconds = Math.floor((new Date().getTime() - new Date(date).getTime()) / 1000);
    
    let interval = seconds / (365*24*60*60);
    
    if (interval > 1) {
      let years = Math.floor(interval);
      return  (years > 1?(years + ' years ago'):'a year ago');
    }
    interval = seconds / (7*24*60*60);
    if (interval > 1) {
      let weeks = Math.floor(interval);
      return  (weeks > 1?(weeks + ' weeks ago'):'a week ago');
    }
    interval = seconds / (24*60*60);
    if (interval > 1) {
      let days = Math.floor(interval);
      return  (days > 1?(days + ' days ago'):'a day ago');
    }
    interval = seconds / (60*60);
    if (interval > 1) {
      let hours = Math.floor(interval);
      return  (hours > 1?(hours + ' hours ago'):'an hour ago');
    }
    interval = seconds / 60;
    if (interval > 1) {
      let minutes = Math.floor(interval);
      return  (minutes > 1?(minutes + ' minutes ago'):'a minute ago');
    }
    seconds = Math.floor(interval);
    return  (seconds > 1?(seconds + ' seconds ago'):' just now');
  }
}

export class DOI {
  
  public static getDOIsFromString(str: string): string[] {
    return Identifier.getDOIsFromString(str);
  }
  
  public static isValidDOI(str: string): boolean {
    return Identifier.isValidDOI(str);
  }
}

export class Identifier {
  class: "doi" | "pmc" | "pmid" | "handle" | "ORCID" | "re3data" | "swhid" | "ror" | "wikidata" | "fundref" | "isni" = null;
  id: string;
  
  public static getDOIsFromString(str: string): string[] {
    var DOIs: string[] = [];
    var words: string[] = str.split(" ");
    
    for (var i = 0; i < words.length; i++) {
      let id = words[i];
      if (DOI.isValidDOI(id) ) {
        id = Identifier.getRawDOIValue(id);
        if( DOIs.indexOf(id) == -1){
          DOIs.push(id);
        }
      }
    }
    return DOIs;
  }
  public static getRawDOIValue(id: string): string {
    if(id.indexOf("doi.org")!=-1 && id.split("doi.org/").length > 1){
      id = id.split("doi.org/")[1];
    }
    return id;
  }
  public static getIdentifiersFromString(str: string): Identifier[] {
    let identifiers: Identifier[] = [];
    let words: string[] = str.split(" ");
    
    for (let id of words) {
      if (id.length > 0) {
        let identifier: Identifier = this.getIdentifierFromString(id);
        if (identifier) {
          identifiers.push(identifier);
        }
      }
    }
    return identifiers;
  }
  
  public static getIdentifierFromString(pid: string,strict:boolean = true): Identifier {
    if (Identifier.isValidDOI(pid)) {
      pid = Identifier.getRawDOIValue(pid);
      return {"class": "doi", "id": pid};
    } else if (Identifier.isValidORCID(pid)) {
      return {"class": "ORCID", "id": Identifier.getRawORCID(pid)};
    } else if (Identifier.isValidPMCID(pid)) {
      return {"class": "pmc", "id": pid};
    } else if (Identifier.isValidPMID(pid)) {
      return {"class": "pmid", "id": pid};
    } else if (Identifier.isValidHANDLE(pid)) {
      return {"class": "handle", "id": pid};
    } else if (Identifier.isValidRe3Data(pid)) {
      return {"class": "re3data", "id": pid};
    } else if (Identifier.isValidSwhId(pid)) {
      return {"class": "swhid", "id": pid};
    } else if (Identifier.isValidRor(pid)) {
      return {"class": "ror", "id": pid};
    } else if (Identifier.isValidFundRef(pid)) {
      return {"class": "fundref", "id": pid};
    } else if (Identifier.isValidWikidata(pid)) {
      return {"class": "wikidata", "id": pid};
    } else if (Identifier.isValidIsni(pid)) {
      return {"class": "isni", "id": pid};
    }
    //set it as a doi, to catch the case that doi has not valid format
    return (strict?null:{"class": "doi", "id": pid});
  }

  public static getPIDFromIdentifiers(identifiers: Map<string, string[]>): Identifier {
    let classes:string [] = ["doi", "handle", "pmc", "pmid", "re3data", "swhid", "ror", "wikidata", "fundref", "isni"];
    if(identifiers && identifiers.size > 0) {
      for (let cl of classes) {
        if (identifiers.get(cl)) {
          for (let pid of identifiers.get(cl)) {
            let identifier = Identifier.getIdentifierFromString(pid);
            if (identifier) {
              return identifier;
            }
          }
        }
      }
    }
    return null;
  }
  
  public static isValidDOI(str: string): boolean {
    //keep only exp3?
    let exp1 = /\b(10[.][0-9]{4,}(?:[.][0-9]+)*\/(?:(?!["&\'<>])\S)+)\b/g;
    let exp2 = /\b(10[.][0-9]{4,}(?:[.][0-9]+)*\/(?:(?!["&\'<>])[[:graph:]])+)\b/g;
    let exp3 = /\b(10[.]*)\b/g;
    return (str.match(exp1) != null || str.match(exp2) != null || str.match(exp3) != null);
  }
  
  public static isValidORCID(str: string): boolean {
    let exp = /\b\d{4}-\d{4}-\d{4}-(\d{3}X|\d{4})\b/g;
    return str.match(exp) != null || this.getRawORCID(str).match(exp) != null;
  }
  public static getRawORCID(id: string): string {
    if(id.indexOf("orcid.org")!=-1 && id.split("orcid.org/").length > 1){
      id = id.split("orcid.org/")[1];
    }
    return id;

  }
  public static isValidPMID(str: string): boolean {
    let exp = /^\d*$/g;
    return str.match(exp) != null;
    
  }
  
  public static isValidPMCID(str: string): boolean {
    let exp = /^(PMC\d{7})$/g;
    return str.match(exp) != null;
  }
  
  public static isValidHANDLE(str: string): boolean {
    // let exp = /\b[0-9a-zA-Z-]*\/[0-9a-zA-Z-]*$/g;  // resolve with url - need to add method for raw value
    let exp = /^[0-9a-zA-Z-]*\/[0-9a-zA-Z-]*$/g;
    return str.match(exp) != null;
  }

  public static isValidRe3Data(str: string): boolean {
    let exp = /(r3d[1-9]\d{0,8})/g;
    return str.match(exp) != null;
  }

  public static isValidSwhId(str: string): boolean {
    // let exp = /swh:1:(snp|rel|rev|dir|cnt):[0-9a-f]{40}/g;
    let exp = /swh:1:snp:[0-9a-f]{40}/g;
    return str.match(exp) != null;
  }

  public static isValidRor(str: string): boolean {
    let exp = /0[a-z|0-9]{6}[0-9]{2}\b/g;
    return str.match(exp) != null;
  }

  public static isValidIsni(str: string): boolean {
    let exp = /^[0]{7}[0-9]{8}[0-9X]$/g;
    return str.match(exp) != null;
  }

  public static isValidWikidata(str: string): boolean {
    let exp = /^Q\d+$/g;
    return str.match(exp) != null;
  }

  public static isValidFundRef(str: string): boolean {
    // let exp = /aaa/g;
    let exp = /^[1-9]\d+$/g;
    return str.match(exp) != null;
  }

}

export class StringUtils {
  
  public static urlRegex = 'https?:\\/\\/(?:www(2?)\\.|(?!www(2?)))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www(2?)\\.' +
    '[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www(2?)\\.|(?!www(2?)))[a-zA-Z0-9]+\\.[^\\s]{2,}|www(2?)\\.' +
    '[a-zA-Z0-9]+\\.[^\\s]{2,}';
  
  public static routeRegex = '^[a-zA-Z0-9\/][a-zA-Z0-9\/-]*$';

  public static jsonRegex = /^[\],:{}\s]*$/;
  
  public static urlPrefix(url: string): string {
    if (!url || url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
      return "";
    } else {
      return "//";
    }
  }
  
  public static quote(params: string): string {
    return '"' + params + '"';
  }
  
  public static unquote(params: string): string {
    if (params.length > 2 && (params[0] == '"' && params[params.length - 1] == '"') || (params[0] == "'" && params[params.length - 1] == "'")) {
      params = params.substring(1, params.length - 1);
    }
    return params;
  }
  
  public static URIEncode(params: string): string {
    return encodeURIComponent(params);
  }
  
  public static URIDecode(params: string): string {
    return decodeURIComponent(params);
  }
  
  public static validateEmails(emails: string): boolean {
    return (emails.split(',')
      .map(email => Validators.email(<AbstractControl>{value: email.trim()}))
      .find(_ => _ !== null) === undefined);
  }
  
  public static b64DecodeUnicode(str) {
    return decodeURIComponent(Array.prototype.map.call(atob(str), function (c) {
      return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
    }).join(''));
  }
  
  private emailValidator(email: any): boolean {
    return !!email.match("^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,4}$");
  }
  
  public static getLogoUrl(result: Stakeholder | CommunityInfo): string {
    if(result && result.logoUrl) {
      return (result.isUpload)?(properties.utilsService + '/download/' + result.logoUrl):result.logoUrl;
    } else {
      return "assets/common-assets/placeholder.png";
    }
  }
  
  public static isValidUrl(url: string): boolean {
    return new RegExp(this.urlRegex).test(url);
  }
  
  public static urlValidator(): ValidatorFn {
    return Validators.pattern(StringUtils.urlRegex);
  }

  public static jsonValidator(error: string = ''): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if(control.value) {
        let test = control.getRawValue().replace(/\\["\\\/bfnrtu]/g, '@').replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').replace(/(?:^|:|,)(?:\s*\[)+/g, '');
        if(!new RegExp(this.jsonRegex).test(test)) {
          return {error: 'Please provide a valid JSON.' + error}
        }
      }
      return null;
    };
  }
  
  public static validatorType(options: string[]): ValidatorFn  {
    return  (control: AbstractControl): { [key: string]: boolean } | null => {
      if (options.filter(type => type === control.value).length === 0) {
        return {'type': false};
      }
      return null;
    }
  }
  
  public static validRoute(pages: any[], field: string, initial: string = null): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      if(control.value) {
        if(!new RegExp(this.routeRegex).test(control.value)) {
          return {error: 'Route should contain only letters or numbers, e.g /route or route'}
        }
        if(pages && pages.length > 0 && control.value !== initial) {
          const forbidden = pages.filter(page => page[field].replace('/', '') === control.value.replace('/', '')).length > 0;
          return forbidden ? {error: 'This route is used by an other page'} : null;
        }
      }
      return null;
    };
  }
  
  public static sliceString(mystr, size: number): string {
    const sliced = String(mystr).substr(0, size);
    return sliced + (String(mystr).length > size ? '...' : '');
  }
  
  /**
   * Splits a text to words base on a list of separators. Returns the words of the text including the separators.
   * DO NOT TOUCH, IT WORKS
   *
   * @param text
   * @param separators
   */
  public static split(text: string, separators: string[]): string[] {
    let words: (string | string[])[] = [text];
    separators.forEach(separator => {
      words.forEach((word, index) => {
        if (typeof word === "string" && separators.indexOf(word) === -1) {
          let tokens: string[] = word.split(separator).filter(value => value !== '');
          if (tokens.length > 1) {
            words[index] = [];
            tokens.forEach((token, i) => {
              (<string[]>(words[index])).push(token);
              if (i !== (tokens.length - 1)) {
                (<string[]>(words[index])).push(separator);
              }
            });
          }
        }
      });
      words = [].concat.apply([], words);
    });
    return <string []>words;
  }
  
  public static capitalize(value: string): string {
    return value.charAt(0).toUpperCase() + value.slice(1);
  }
  
  /**
   * Checks if a text contains a word
   */
  public static containsWord(text: string, word: string): boolean {
    return (text && text.toLowerCase().includes(word));
  }
  
  public static URLSegmentsToPath(segments: UrlSegment[]): string {
    let path = '';
    segments.forEach(route => {
      path += '/' + route.path;
    })
    return path;
  }
  
  public static isEuropeanCountry(country: string) {
    let countries = ["Albania", "Andorra", "Armenia", "Austria", "Azerbaijan", "Belarus", "Belgium", "Bosnia and Herzegovina",
      "Bulgaria", "Croatia", "Cyprus", "Czech Republic", "Denmark", "Estonia", "Finland", "France", "Georgia", "Germany", "Greece", "Hungary", "Iceland", "Ireland",
      "Italy", "Kosovo", "Latvia", "Liechtenstein", "Lithuania", "Luxembourg", "Macedonia", "Malta", "Moldova", "Monaco", "Montenegro", "The Netherlands", "Norway", "Poland",
      "Portugal", "Romania", "Russia", "San Marino", "Serbia", "Slovakia", "Slovenia", "Spain", "Sweden", "Switzerland", "Turkey", "Ukraine", "United Kingdom", "Vatican City",
    ];
    return (country && countries.indexOf(country) != -1);
  }
  
  public static isOpenAIREID(id: string) {
    if (id && id.length == 46) {
      let exp1 = /^.{12}::([0-9a-z]{32})$/g;
      return (id.match(exp1) != null);
    }
    return false;
  }
  public static HTMLToString( html:string){
     try {
       html = html.replace(/&nbsp;/g, ' ');
       html = html.replace(/(\r\n|\n|\r| +(?= ))|\s\s+/gm, " ");
       html = html.replace(/<[^>]*>/g, '');
    }catch( e){
    }
   return html;
  }

  public static inValidYearValidator(minYear, maxYear): ValidatorFn {
    return (control: AbstractControl): {[key: string]: any} | null => {
      return ((control.value && !Dates.isValidYear(control.value, minYear, maxYear)) ?
        {error: 'Year must be between ' + minYear + ' and ' + maxYear + '.'} : null);
    };
  }

  public static fromYearAfterToYearValidator: ValidatorFn = (control: UntypedFormGroup): ValidationErrors | null => {
    const yearFrom = control.get('yearFrom');
    const yearTo = control.get('yearTo');
    return ((yearFrom && yearTo && (parseInt(yearFrom.value, 10) >  parseInt(yearTo.value, 10))) ?
      {error: 'Starting year must be greater than or equal to ending year.' } : null);
  }

  public static rangeRequired( enabled:boolean): ValidatorFn {
    return (control: AbstractControl): ValidationErrors | null => {
      const yearFrom = control.get('yearFrom');
      const yearTo = control.get('yearTo');
      return ((yearFrom && yearTo && enabled && (yearFrom.value == "" || yearTo.value == "")) ? { error: 'Both starting and ending year are required' } : null);
    };
  }


  public static getEntityName(entityType: string, plural: boolean = false): string {
    if (entityType == "publication") {
      return plural ? OpenaireEntities.PUBLICATIONS : OpenaireEntities.PUBLICATION;
    } else if (entityType == "dataset") {
      return plural ? OpenaireEntities.DATASETS : OpenaireEntities.DATASET;
    } else if (entityType == "software") {
      return plural ? OpenaireEntities.SOFTWARE : OpenaireEntities.SOFTWARE_SINGULAR;
    } else if (entityType == "other" || entityType == "orp") {
      return plural ? OpenaireEntities.OTHER : OpenaireEntities.OTHER_SINGULAR;
    } else if (entityType == "result") {
      return plural ? OpenaireEntities.RESULTS : OpenaireEntities.RESULT;
    } else if (entityType == "project") {
      return plural ? OpenaireEntities.PROJECTS : OpenaireEntities.PROJECT;
    } else if (entityType == "organization") {
      return plural ? OpenaireEntities.ORGANIZATIONS : OpenaireEntities.ORGANIZATION;
    } else if (entityType == "dataprovider") {
      return plural ? OpenaireEntities.DATASOURCES : OpenaireEntities.DATASOURCE;
    } else if (entityType == "service") {
      return plural ? OpenaireEntities.SERVICES : OpenaireEntities.SERVICE;
    } else if (entityType == "community") {
      return plural ? OpenaireEntities.COMMUNITIES : OpenaireEntities.COMMUNITY;
    }
    return entityType;
  }

  public static getEntityFileName(entityType: string): string {
    if (entityType == "publication" || entityType == "publications") {
      return OpenaireEntities.PUBLICATIONS_FILE;
    } else if (entityType == "dataset" || entityType == "datasets") {
      return OpenaireEntities.DATASETS_FILE;
    } else if (entityType == "software") {
      return OpenaireEntities.SOFTWARE_FILE;
    } else if (entityType == "other" || entityType == "orp") {
      return OpenaireEntities.OTHER_FILE;
    } else if (entityType == "result" || entityType == "results") {
      return OpenaireEntities.RESULTS_FILE;
    } else if (entityType == "project" || entityType == "projects") {
      return OpenaireEntities.PROJECTS_FILE;
    } else if (entityType == "organization" || entityType == "organizations") {
      return OpenaireEntities.ORGANIZATIONS_FILE;
    } else if (entityType == "dataprovider" || entityType == "dataproviders" || entityType == "datasource" || entityType == "datasources") {
      return OpenaireEntities.DATASOURCES_FILE;
    } else if (entityType == "service" || entityType == "services") {
      return OpenaireEntities.SERVICES_FILE;
    }
    return entityType.toLowerCase().replace(" ", "-");
  }

	public static getStakeholderType(stakeholderType: string, plural: boolean = false): string {
		if(stakeholderType == "funder") {
			return plural ? StakeholderConfiguration.ENTITIES.funders : StakeholderConfiguration.ENTITIES.funders;
		} else if(stakeholderType == "ri") {
			return plural ?StakeholderConfiguration.ENTITIES.ris : StakeholderConfiguration.ENTITIES.ri;
		} else if(stakeholderType == "organization") {
			return plural ? StakeholderConfiguration.ENTITIES.organizations : StakeholderConfiguration.ENTITIES.organization;
		} else if(stakeholderType == "project") {
			return plural ? StakeholderConfiguration.ENTITIES.projects: StakeholderConfiguration.ENTITIES.project;
		}
		return stakeholderType;
	}
}
