
import { Directive, forwardRef, HostListener, Input, OnChanges, SimpleChanges, ElementRef, Renderer2, ChangeDetectorRef, Output, EventEmitter } from '@angular/core';
import {
  ControlValueAccessor,
  NG_VALIDATORS,
  NG_VALUE_ACCESSOR,
  ValidationErrors,
  Validator,
  AbstractControl,
} from '@angular/forms';
import { ApplyMaskService } from '../../services/apply-mask.service';
import { RbjTooltipService } from '../../services/rbj-tooltip.service';
import { TextareaUtils } from '../../utilities/textarea-utils';
import { UuidUtils } from '../../utilities/uuid-utils';

@Directive({
  selector: 'input[mask], textarea[mask]',
  providers: [
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => MaskDirective),
      multi: true,
    },
    {
      provide: NG_VALIDATORS,
      useExisting: forwardRef(() => MaskDirective),
      multi: true,
    },
    ApplyMaskService,
  ],
})
export class MaskDirective implements ControlValueAccessor, OnChanges, Validator {

  private _maskExpression: string = '';
  public get maskExpression(): string {
    return this._maskExpression;
  }
  @Input('mask')
  public set maskExpression(v: string) {
    if (this._maskExpression != v) {
      this._maskExpression = v;

      let maxInputLength = this.applyMaskService.getMaxInputLength(this.maskExpression);
      if (maxInputLength) {
        this.formElementProperty = ['maxLength', maxInputLength.toString()];
      };
    }
  }

  @Input('multiMask') public maskExpressions: string[];
  @Input('invalidMsg') public invalidMsg: string = 'Invalid format';
  @Input() public allowedPrefix: string = '';

  @Output() maskApplied = new EventEmitter<string>();
  public constructor(
    private applyMaskService: ApplyMaskService,
    private elementRef: ElementRef,
    private renderer: Renderer2,
    private cdRef: ChangeDetectorRef,
    private rbjTooltipService: RbjTooltipService
  ) { }


  private inputValue: string = '';
  private invalid: boolean = false;
  private _required: boolean = false;
  private _msgShown: boolean = false;
  private uuid: string;
  public writingValue: boolean = false;
  public onChange = (_: any) => { };
  public onTouch = () => { };

  @Input()
  public set required(value) {
    this._required = typeof value !== 'undefined' && value !== false;
  }

  ngOnInit() {
    this.uuid = UuidUtils.generate();
  }

  ngOnDestroy() {
    this.rbjTooltipService.destroy(this.uuid);
  }

  validate(control: AbstractControl): ValidationErrors {

    if (!this.maskExpression) {
      return null;
    }

    if (control.value != undefined && this._required && this.displayValue.length == 0) {

      this.invalid = true;
      //if it's empty red border is good enough for notice people
      //this.show();
      return this.createValidationError(this.displayValue)
    }

    if (this.displayValue && this.displayValue.length > 0 &&
      !this.applyMaskService.validate(this.displayValue, this.maskExpression)) {
      this.invalid = true;

      return this.createValidationError(this.displayValue)
    }

    this.invalid = false;
    this.hide();
    return null;
  }

  registerOnValidatorChange?(fn: () => void): void {
  }

  writeValue(obj: any): void {
    this.inputValue = obj;
    this.displayValue = this.inputValue || '';
    this._lastEmitValue = this.displayValue;
  }
  registerOnChange(fn: any): void {
    this.onChange = fn;
  }
  registerOnTouched(fn: any): void {
    this.onTouch = fn;
  }
  setDisabledState?(isDisabled: boolean): void {
    this.formElementProperty = ['disabled', isDisabled];

  }


  public get displayValue(): string {
    return this.elementRef.nativeElement.value;
  }
  public set displayValue(v: string) {
    this.formElementProperty = ['value', v];
  }


  public ngOnChanges(changes: SimpleChanges): void {
    this._applyMask();
  }

  show() {

    let formHasBeenTouched = this.elementRef.nativeElement.form && this.elementRef.nativeElement.form.className.indexOf('ng-touched') == -1;
    if (false == formHasBeenTouched) {
      this._msgShown = true;
      this.rbjTooltipService.show(this.elementRef, this.invalidMsg, this.uuid, 0);
    }
  }

  hide() {
    //this.isOpen = false;
    if (this._msgShown) {
      this._msgShown = false;
      this.rbjTooltipService.hide(this.uuid);
    }
  }

  @HostListener('blur')
  public onBlur(): void {

    this.onTouch();
  }
  private _isReplacing = false;
  private _selectionStart = -1;
  private _lastEmitValue: string;

  @HostListener('keydown', ['$event'])
  public onKeyDown(e: KeyboardEvent): void {
    if (null == this.maskExpression) return;
    if (!e.ctrlKey) {
      const el: HTMLInputElement = e.target as HTMLInputElement;
      this._isReplacing = el.selectionStart != el.selectionEnd;
      this._selectionStart = el.selectionStart;

      if (this.maskExpression && (el.value.length + 1) == this.maskExpression.length) {
        this._isReplacing = true;
      }
    }
  }

  @HostListener('keyup', ['$event'])
  public onKeyUp(e: KeyboardEvent): void {
    if (null == this.maskExpression) return;
    const el: HTMLInputElement = e.target as HTMLInputElement;
    this.inputValue = el.value;

    let regMatch = e.key.match(/.{1}/g);
    if (TextareaUtils.isEditingKeyPress(e)) {

      let focusLocation = this._isReplacing ? this._selectionStart : el.selectionStart;
      this.writingValue = true;
      this.inputValue = this.applyMaskService.applyMask(this.inputValue, this.maskExpression,
        {
          inputKey: this._isReplacing ? '' : e.key,
          inputPosition: focusLocation
        });
      this.writingValue = false;
      this.elementRef.nativeElement.value = this.inputValue;
      this.setCursorPosition(this.applyMaskService.focusIndex);
      this.maskApplied.next(this.inputValue);

    }
    this._isReplacing = false;
    this.formControlResult(this.inputValue);
  }



  @HostListener('focusout')
  onFocusOut() {
    if (this.invalid) {
      this.show();
    }
  }
  setCursorPosition(position) {
    let input = this.elementRef.nativeElement;
    if (input.setSelectionRange) {
      input.focus();
      input.setSelectionRange(position, position);
    } else if (input.createTextRange) {
      var range = input.createTextRange();
      range.collapse(true);
      range.moveEnd('character', position);
      range.moveStart('character', position);
      range.select();
    }
  }





  private _applyMask(): any {
    this.formElementProperty = [
      'value',
      this.applyMaskService.applyMask(this.displayValue, this.maskExpression),
    ];
  }

  private set formElementProperty([name, value]: [string, string | boolean]) {

    this.renderer.setProperty(this.elementRef.nativeElement, name, value);
  }

  private formControlResult(inputValue: string): void {
    if (!this.writingValue &&
      this._lastEmitValue != this.displayValue) {
      this._lastEmitValue = this.displayValue;
      this.onChange(inputValue);
    }
  }
  private createValidationError(actualValue: string): ValidationErrors {
    return {
      mask: {
        requiredMask: this.maskExpression,
        actualValue,
      },
    };
  }
}
