import { Injectable } from '@angular/core';
import { StringUtils } from '../utilities/string-utils';
const MONTH_DAY_MIN = 1;
const MONTH_MAX = 12;
const DAYS_MAX = 31;
const patterns = {
  '0': {
    pattern: new RegExp('\\d'),
  },
  '9': {
    pattern: new RegExp('\\d'),
    optional: true,
  },
  '?': {
    pattern: new RegExp('.'),
    optional: true,
  },
  A: {
    pattern: new RegExp('[a-zA-Z0-9]'),
  },
  C: {
    pattern: new RegExp('[a-zA-Z]'),
  },
  U: {
    pattern: new RegExp('[a-zA-Z]'),
  },
  L: {
    pattern: new RegExp('[a-zA-Z]'),
  },
  d: {
    pattern: new RegExp('\\d'),
  },
  M: {
    pattern: new RegExp('\\d'),
  },
}
const escapeRegex = new RegExp(/\\{1}[09ACMULd?]{1}/g);
const patternRegex = new RegExp(/[09ACMULd?]/g);
@Injectable({
  providedIn: 'root'
})
export class ApplyMaskService {


  private _formElement: HTMLInputElement;

  public onChange = (_: any) => { };
  public maskExpression: string = '';
  public actualValue: string = '';
  public focusIndex: number;
  constructor() { }
  applyMask(value: string, maskExpression: string,
    userConfig: Partial<ApplyMaskConfig> = {}
  ) {

    const config = new ApplyMaskConfig({ ...userConfig });
    let result = '';
    let cursor = 0;
    this.focusIndex = config.inputPosition || 0;

    if (!maskExpression || !value) {
      return value;
    }

    let isEdit = value.length >= maskExpression.length;

    if (isEdit && config.inputKey) {
      let maskIndex = this.focusIndex;
      let maskSymbol = maskExpression[maskIndex];

      while (!config.overwriteWithMask &&
        maskIndex < maskExpression.length &&
        !patterns[maskSymbol] && maskExpression[maskIndex - 1] != '\\') {
        maskIndex++;
        maskSymbol = maskExpression[cursor];
      }

      if (this.isValidInput(maskSymbol, config.inputKey)) {

        var replaceArr = value.split("");
        replaceArr[maskIndex] = config.inputKey;
        value = replaceArr.join("");
      } else {
        config.inputKey = null;
      }

    }


    //Prefix
    if (config.prefix) {
      let hasPrefix = false;
      if (value.indexOf(config.prefix) == 0) {
        hasPrefix = true;
        value = value.substr(0, config.prefix.length);
      }
      //Add prefix to result if user typed or prefix is not optional
      if (hasPrefix || false == config.prefixIsOptional) {
        result = config.prefix;
      }
    }

    const inputArray: string[] = value.split('');

    for (
      let i: number = 0, inputSymbol: string = inputArray[0];
      i < inputArray.length;
      i++, inputSymbol = inputArray[i]
    ) {

      if (cursor === maskExpression.length) {
        break;
      }
      let maskSymbol = maskExpression[cursor];
      while (!config.overwriteWithMask && !patterns[maskSymbol] && maskExpression[cursor - 1] != '\\') {
        result += maskSymbol;
        if (this.focusIndex == cursor) this.focusIndex++;

        if (i == cursor && inputSymbol == maskSymbol) {
          i++;
          inputSymbol = inputArray[i] || '';
        }
        cursor++;
        maskSymbol = maskExpression[cursor];

      }

      //replace inputSymbol with valid input key when editing, other wise validating inputSymbol
      if (!this.isValidInput(maskSymbol, inputSymbol)) {
        if (config.overwriteWithMask) {
          inputSymbol = maskSymbol;
        } else {

          continue;
        }
      }

      switch (maskSymbol) {
        case '0':
          break;
        case 'M':
        case 'd':
          let max = maskSymbol == 'M' ? MONTH_MAX : DAYS_MAX;
          let min = MONTH_DAY_MIN;
          let focusOffset = 0;
          let inputNum = 0;
          if (isEdit && (this.focusIndex == i || this.focusIndex == (i + 1))) {
            focusOffset = this.focusIndex == i ? 2 : 1;
          }

          inputNum = Number(inputArray[i] + (inputArray.length > (i + 1) ? inputArray[i + 1] : ''));
          let offsetFocus = false;
          if (inputNum > max || inputNum < min) {
            inputNum = inputNum > max ? max : min;
            offsetFocus = true;
            result += inputNum.toString().padStart(2, '0')
            cursor += 2;
            i++;
            //this.focusIndex += focusOffset;
            continue;
          }
          break;
        case 'A':
          break;
        case 'U':
          inputSymbol = inputSymbol.toUpperCase();
          break;
        case 'L':
          inputSymbol = inputSymbol.toLowerCase();
          break;
        default:
          break;
      }



      result += inputSymbol;
      cursor++;
    }
    this.focusIndex++;
    return result;
  }

  private isValidInput(maskSymbol: string, inputKey: string): boolean {

    if (patterns[maskSymbol] && patterns[maskSymbol].pattern) {
      if (!inputKey) {
        return patterns[maskSymbol].optional;
      }
      return patterns[maskSymbol].pattern.test(inputKey);
    }
    return maskSymbol == inputKey;
  }




  validate(value: string, maskExpression: string,
    config?: Partial<ApplyMaskConfig>): boolean {
    if (StringUtils.isNullOrWhitespace(maskExpression) || StringUtils.isNullOrWhitespace(value)) {
      return true;
    }
    // let optionalCount = 0;
    // for (let i = 0; i < maskExpression.length; i++) {
    //   const maskSymbol = maskExpression[i];
    //   optionalCount += (
    //     patterns[maskSymbol] &&
    //     patterns[maskSymbol].optional &&
    //     patterns[maskSymbol].optional) ? 1 : 0;

    // }

    // if (value.length < (maskExpression.length - optionalCount)) {
    //   return false;
    // }

    let cursor = 0;
    let inputSymbol = '';
    const maskArray: string[] = maskExpression.split('');
    const inputArray: string[] = value.split('');
    for (
      let i: number = 0, maskSymbol: string = maskArray[0];
      i < maskExpression.length;
      i++, maskSymbol = maskExpression[i]
    ) {

      inputSymbol = cursor >= inputArray.length ? null : inputArray[cursor];
      if (!patterns[maskSymbol]) {

        //detect escape \09ACMULd?
        if (inputSymbol == '\\' && cursor + 1 < inputArray.length && (inputSymbol + inputArray[cursor + 1]).match(escapeRegex)) {
          cursor++;
          inputSymbol = inputArray[cursor];
        }

        if (inputSymbol && inputSymbol != maskSymbol) return false;

        cursor++;
        continue;
      }


      if (!this.isValidInput(maskSymbol, inputSymbol)) {
        return false;
      }
      switch (maskSymbol) {
        case 'M':
        case 'd':
          let max = maskSymbol == 'M' ? MONTH_MAX : DAYS_MAX;
          let min = MONTH_DAY_MIN;
          let inputNum = Number(inputArray[i] + (inputArray.length > (i + 1) ? inputArray[i + 1] : ''));

          if (inputNum > max || inputNum < min) {
            return false;
          }
          break;
        default:
          break;
      }

      cursor++;

    }
    return true;
  }

  //Should return length without escape symbol
  getMaxInputLength(maskExpression: string) {
    if (StringUtils.isNullOrWhitespace(maskExpression)) return 0

    var length = maskExpression.length;

    //escape symbol length
    let escapeMatch = maskExpression.match(escapeRegex);
    length -= escapeMatch ? escapeMatch.length : 0;
    return length;

  }
}



/**
 * Describes all available options that may be passed to the ApplyMaskService.
 * */
export class ApplyMaskConfig {
  /**
   * Current input character, default:null
   * */
  inputKey: string = null;
  /**
   * Current input index, default:null
   * */
  inputPosition: number = null;

  /**
   * Overwrite character with mask symbol ex: value:'12-1234', mask:'**-9999' => '**-1234' default:false
   * */
  overwriteWithMask: boolean = false;

  /**
   * Prefix of mask
   * */
  prefix: string = '';

  /**
   * Optional prefix default:true
   * */
  prefixIsOptional: boolean = true;

  constructor(config: Partial<ApplyMaskConfig>) {
    Object.assign(this, config);
  }
}