import * as AngularCommon from '@angular/common';
import { HttpErrorResponse } from '@angular/common/http';
import * as moment_ from 'moment';
import * as Cnst from './consts';


const moment = (<any>moment_).default;

export function guid(): string {
  return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
    // tslint:disable-next-line: no-bitwise
    const r = Math.random() * 16 | 0, v = c === 'x' ? r : (r & 0x3 | 0x8);
    return v.toString(16);
  });
}


export function getMethodName(level: number = 2): string { return /at .*\.(.*)\s/.exec((new Error()).stack.split('\n')[level])[1]; } // we want the 2nd method in the call stack


export function ms2ToTimeString(ms: number, fmt: string) {
  return moment(ms).format(fmt);
}

export function getDecimalSeparator(culture: string): string {
  return AngularCommon.getLocaleNumberSymbol(culture, AngularCommon.NumberSymbol.Decimal);
}


export function getThousandSeparator(culture: string): string {
  return AngularCommon.getLocaleNumberSymbol(culture, AngularCommon.NumberSymbol.Group);
}

export function getLocaleCurrencyCode(culture: string): string {
  return AngularCommon.getLocaleCurrencyCode(culture);
}

export function getCurrencySymbol(culture: string): string {
  return AngularCommon.getCurrencySymbol(getLocaleCurrencyCode(culture), 'narrow', culture);
}

export function getDateTimeFormat(culture: string): string {
  return getDateFormat(culture, Cnst.EN_DateFormats.DateTime);
}

export function getDateFormat(culture: string, dateFormatStringMode: Cnst.EN_DateFormats = Cnst.EN_DateFormats.Date): string {
  let formatString: string;
  switch (dateFormatStringMode) {
    case Cnst.EN_DateFormats.WebAPI:
      formatString = Cnst.DATE_FORMATS.display.date4WebAPI;
      break;
    case Cnst.EN_DateFormats.JSON:
      formatString = Cnst.DATE_FORMATS.display.dateTime4JSON;
      break;
    case Cnst.EN_DateFormats.DateTime:
      // getLocaleDateTimeFormat(culture, FormatWidth.Medium); returns {1}, {0} WTF?
      formatString = stringFormat(AngularCommon.getLocaleDateTimeFormat(culture, AngularCommon.FormatWidth.Short)
        , AngularCommon.getLocaleTimeFormat(culture, AngularCommon.FormatWidth.Short)
        , AngularCommon.getLocaleDateFormat(culture, AngularCommon.FormatWidth.Short)).replace('yy', 'y');
      break;
    default:
      const localDateFormat = AngularCommon.getLocaleDateFormat(culture, AngularCommon.FormatWidth.Short);
      formatString = localDateFormat.replace('yy', 'y'); //localformat "short" returns yy - that shows only the last 2 years in year, not 4
  }
  return formatString;
}


export function toStringConsiderDateFormat(obj: any, dateFormatStringMode: Cnst.EN_DateFormats = Cnst.EN_DateFormats.WebAPI, culture: string = 'en-US' /* does not matter a culture, Cnst.EN_DateFormats.WebAPI uses explicit date format*/): string {
  let ret: string = '';

  if (obj) {
    if (typeof obj === 'string') obj = new Date(obj);
    if (typeof obj === 'object') {
      const formatString: string = getDateFormat(culture, dateFormatStringMode);
      const momentFormatString: string = formatString.toUpperCase().replace(':MM', ':mm').replace(':SS', ':ss');
      if (moment.isMoment(obj) /*'_isAMomentObject' in obj*/) ret = moment(obj).format(momentFormatString);
      else if (moment.isDate(obj) /*obj instanceof Date*/) ret = AngularCommon.formatDate(obj, formatString, culture /* does not matter a culture, we are using explicit date format*/);
    } else ret = obj.toString();
  }
  return ret;
}


export function formatNumbers(locale: string, num: number, digits = 0, prefix = ''): string {
  //    const num = +data.value;
  //    let val = num.toFixed(digits).replace('.', ','); // replace decimal point
  //    val = val.replace(/(\d)(?=(\d{3})+(?!\d))/g, '$1.');
  return prefix + AngularCommon.formatNumber(num, locale, '1.' + digits + '-' + digits);
}


export function isEqual(source: object, target: object): boolean {
  if (target && source) {
    for (const prop in source) {
      if (source.hasOwnProperty(prop)
        && target.hasOwnProperty(prop)
        && (target[prop] !== source[prop])) {
        return false;
      }
    }
    return true;
  } else {
    return !target && !source;
  }
}


function arrayIsContainedInReturnsDiff(arr1: any[], arr2: any[], notExistInArray2: any[]): boolean {
  if (!arr1) return false;
  if (arr1.length === 0) return true;
  if (!arr2 || arr2.length === 0) {
    notExistInArray2.concat(arr1);
    return false;
  } else {
    let result = true;
    arr1.forEach((e1: any) => {
      if (!arr2.some(e2 => {
        if (e1 instanceof Array && e2 instanceof Array) return arraysAreEqual(e1, e2);
        else return e1 === e2;
      })) {
        result = false;
        notExistInArray2.push(e1);
      }
    });
    return result;
  }
}


export function arraysAreEqualReturnsDiff(arr1: any, arr2: any, diffArr1: any, diffArr2: any): boolean {
  let _arr1: any[] = arr1;
  let _arr2: any[] = arr2;
  if (!(arr1 instanceof Array)) _arr1 = [arr1];
  if (!(arr2 instanceof Array)) _arr2 = [arr2];
  if (!diffArr1) diffArr1 = [];
  if (!diffArr2) diffArr2 = [];
  return arrayIsContainedInReturnsDiff(_arr1, _arr2, diffArr1) && arrayIsContainedInReturnsDiff(_arr2, _arr1, diffArr2);
}


function arrayIsContainedIn(arr1: any[], arr2: any[]): boolean {
  return arr1 && arr2 && (arr1.length === 0 || arr1.every((e1, i) => arr2.some(e2 => {
    if (e1 instanceof Array && e2 instanceof Array && e1.length > 1 && e2.length) return arraysAreEqual(e1, e2);
    else return e1 === e2;
  })));
}


export function arraysAreEqual(arr1: any, arr2: any): boolean {
  let _arr1: any[] = arr1;
  let _arr2: any[] = arr2;
  if (!(arr1 instanceof Array)) _arr1 = [arr1];
  if (!(arr2 instanceof Array)) _arr2 = [arr2];
  return arrayIsContainedIn(_arr1, _arr2) && arrayIsContainedIn(_arr2, _arr1);
}


export function compareStrings(str1: string, str2: string): boolean {
  return (str1 ?? '').localeCompare(str2 ?? '') === 0;
}



export function prepareArrayFromList(obj: Object, listField: string, arrayToPprepareField: string): number[] {
  if (obj[arrayToPprepareField]) return obj[arrayToPprepareField];
  obj[arrayToPprepareField] = [];
  if (obj[listField]) {
    const reglist: string[] = obj[listField].split(',');
    reglist.forEach((vl: string, inx: number) => { if (vl && !Number.isNaN(Number(vl))) obj[arrayToPprepareField].push(+vl); });
  }
  return obj[arrayToPprepareField];
}


export function prepareListFromArray(obj: Object, listField: string, arrayToPprepareField: string, newArray: number[]) {
  obj[arrayToPprepareField] = newArray;
  obj[listField] = newArray.join(',');
}


export function getErrorMessageAsText(errorObj: any): string {
  if (!errorObj) return 'unknown error';
  if (errorObj instanceof HttpErrorResponse && errorObj.status === Cnst.cnst_ErrorCode4Unauthorized) return Cnst.cnst_CANCEL_ERROR;
  if (errorObj['errMsg']) return errorObj.errMsg;
  if (errorObj['error']) {
    if (typeof errorObj.error === 'string') return errorObj.error;
    if (errorObj.error['errors'] && errorObj.error.errors instanceof Array && errorObj.error.errors.length > 0) return errorObj.error.errors[0].toString();
    if (errorObj.error['message']) return errorObj.message;
    return errorObj.error.toString();
  }
  if (errorObj['message']) return errorObj.message;
  if (errorObj['statusText']) return errorObj.statusText;
  return errorObj.toString();
}


export function extractError(error: any) {
  // Try to unwrap zone.js error.
  // https://github.com/angular/angular/blob/master/packages/core/src/util/errors.ts
  if (error && error.ngOriginalError) {
    error = error.ngOriginalError;
  }

  // We can handle messages and Error objects directly.
  if (typeof error === 'string' || error instanceof Error) {
    return error;
  }

  // If it's http module error, extract as much information from it as we can.
  if (error instanceof HttpErrorResponse) {
    // The `error` property of http exception can be either an `Error` object, which we can use directly...
    if (error.error instanceof Error) {
      return error.error;
    }

    // ... or an`ErrorEvent`, which can provide us with the message but no stack...
    if (error.error instanceof ErrorEvent && error.error.message) {
      return error.error.message;
    }

    // ...or the request body itself, which we can use as a message instead.
    if (typeof error.error === 'string') {
      return `Server returned code ${error.status} with body "${error.error}"`;
    }

    // if (error['error']) {
    //   return error.error;
    // }

    // If we don't have any detailed information, fallback to the request message itself.
    return error.message;
  }

  // ***** CUSTOM *****
  // The above code doesn't always work since 'instanceof' relies on the object being created with the 'new' keyword
  if (error.error && error.error.message) {
    return error.error.message;
  }
  if (error.message) {
    return error.message;
  }
  // ***** END CUSTOM *****

  // Skip if there's no error, and let user decide what to do with it.
  return null;
}


export function getNextDate(dt: Date, hours: number = 0, offset: number = 1): Date {
  if (!dt) return null;
  const date = new Date(dt);
  date.setHours(hours, 0, 0, 0);
  date.setDate(date.getDate() + offset);
  return date;
}


export function today(): Date {
  const date = new Date();
  date.setHours(0, 0, 0, 0);
  return date;
}


export function setIndexIcon(iconpath: string, icon2replace: string = '#favIcon') {
  const favIcon: HTMLLinkElement = document.querySelector(icon2replace);
  if (favIcon) favIcon.href = iconpath;
}


export function setIndexTitle(title: string) {
  const tileEl: HTMLLinkElement = document.querySelector('#appTitle');
  if (tileEl) tileEl.innerText = title;
}


export function removeSpecialChars(str: string): string {
  return str
    .replace(new RegExp('/ ', 'g'), '_')
    .replace(new RegExp(' /', 'g'), '_')
    .replace(new RegExp(' / ', 'g'), '_')
    .replace(new RegExp(' ', 'g'), '_')
    .replace(new RegExp('/', 'g'), '_')
    .replace(new RegExp(':', 'g'), '_')
    .replace(new RegExp('>', 'g'), '_')
    .replace(new RegExp('<', 'g'), '_')
    .replace(new RegExp('___', 'g'), '_')
    .replace(new RegExp('__', 'g'), '_');
}


export function dowloadFile(filename: string, data: any, type: string) {
  // const blob = new Blob([response.body], { type: response.body.type });
  const blob = new Blob([JSON.stringify(data)], { type: type });


  // window.open(window.URL.createObjectURL(blob), filename);
  const link = document.createElement('a');
  const url = URL.createObjectURL(blob);
  link.setAttribute('href', url);
  link.setAttribute('download', filename);
  document.body.appendChild(link);
  link.click();
  document.body.removeChild(link);
}


export function cleanTextContent(text: string): string {
  // strips off all non-ASCII characters
  text = text.replace(new RegExp('[^\\x00-\\x7F]', 'g'), '');

  // erases all the ASCII control characters
  text = text.replace(new RegExp('[\\p{Cntrl}&&[^\r\n\t]]', 'g'), '');

  // removes non-printable characters from Unicode
  text = text.replace(new RegExp('\\p{C}', 'g'), '');

  return text.trim();
}


export function isWorkDay(dt: Date) {
  return dt.getDate() === nextWorkDay(dt, -1000 * 60 * 60 * 24 ).getDate();
}


export function nextWorkDay(dt: Date, shift: number = 0) {
  const newdt = new Date(dt.getTime() + shift);
  const day = newdt.getDay();
  newdt.setDate(newdt.getDate() + 1 + (day === 5 ? 2 : (day === 6 ? 1 : 0)));
  return newdt;
}


export function stringFormat(str: string, ...opt_values: string[]) {
  if (opt_values) {
    str = str.replace(/\{([^}]+)}/g, function (match, key) {
      return (opt_values != null && key in opt_values) ? opt_values[key] : match;
    });
  }
  return str;
}


export function convertTZ(date: Date | string, tzString: string) {
  return new Date((typeof date === 'string' ? new Date(date) : date).toLocaleString('en-US', {timeZone: tzString}));
}


export function now() {
  return moment.now();
}


export function isBefore(dt: Date) {
  return moment().isBefore(dt);
}


export function isLanguageSupported(lang: string, appLanguages?: string[], supportedLanguage?: Cnst.SupportedLanguage[]): boolean {
  return (!appLanguages || appLanguages.indexOf(lang) > -1)
      && (!supportedLanguage || supportedLanguage.findIndex((lng: Cnst.SupportedLanguage) => lng.langCode === lang) > -1);
}


export function isCultureSupported(culture: string, appCultures: string[], supportedCulture?: Cnst.SupportedCulture[]): boolean {
  return (!appCultures || appCultures.indexOf(culture) > -1)
    && (!supportedCulture || supportedCulture.findIndex((cltr: Cnst.SupportedCulture) => cltr.culture === culture) > -1);
}



export function callProtocol(protocol: string, filePath: string, replaceSlash: boolean = true) {
  if (replaceSlash) {
    filePath = filePath.replace(new RegExp('"', 'g'), '').split('\\\\').join('\\');
  }
  window.open(protocol + ':' + filePath, '_parent');
}



// export function translateString(stringToTranslate: string, translate: HenselTranslateService) {
//   let res = stringToTranslate?.replace(/\${(.*?)}/g, (found: string, translationKey: string) => translate.instant(translationKey));
//   if (res === stringToTranslate) res = translate.instant(res); // source string does not contain placeholder "${}" - try to translate directly
//   return res;
// }

export function isEmpty(value: any): boolean {
  return (value == null || (typeof value === 'string' && value.trim().length === 0));
}

export function inputNumberChanged(target: number, value: any): boolean {
  return isEmpty(value) && !isEmpty(target) || !isEmpty(value) && isEmpty(target) || (target ?? 0) !== +value;
}
