/*!
 * @license
 * Copyright 2016 Alfresco Software, Ltd.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

 /* tslint:disable: component-selector no-use-before-declare no-input-rename  */

import {
    Directive,
    ElementRef,
    forwardRef,
    HostListener,
    Input,
    OnChanges,
    Renderer,
    SimpleChanges
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => InputMaskDirective),
    multi: true
};

@Directive({
    selector: '[textMask]',
    providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class InputMaskDirective implements OnChanges, ControlValueAccessor {

    @Input('textMask') inputMask: {
        mask: '',
        isReversed: false
    };

    private translationMask = {
        '0': { pattern: /\d/ },
        '9': { pattern: /\d/, optional: true },
        '#': { pattern: /\d/, recursive: true },
        'A': { pattern: /[a-zA-Z0-9]/ },
        'S': { pattern: /[a-zA-Z]/ }
    };

    private byPassKeys = [9, 16, 17, 18, 36, 37, 38, 39, 40, 91];
    private value;
    private invalidCharacters = [];

    constructor(private el: ElementRef, private render: Renderer) {
    }

    _onChange = (_: any) => {
    }

    _onTouched = () => {
    }

    @HostListener('input', ['$event'])
    @HostListener('keyup', ['$event']) onTextInput(event: KeyboardEvent) {
        if (this.inputMask && this.inputMask.mask) {
            this.maskValue(this.el.nativeElement.value, this.el.nativeElement.selectionStart,
                this.inputMask.mask, this.inputMask.isReversed, event.keyCode);
        } else {
            this._onChange(this.el.nativeElement.value);
        }
    }

    ngOnChanges(changes: SimpleChanges) {
        if (changes['inputMask'] && changes['inputMask'].currentValue['mask']) {
            this.inputMask = changes['inputMask'].currentValue;
        }
    }

    writeValue(value: any) {
        this.el.nativeElement.value = value;
    }

    registerOnChange(fn: any) {
        this._onChange = fn;
    }

    registerOnTouched(fn: () => any): void {
        this._onTouched = fn;
    }

    private maskValue(actualValue, startCaret, maskToApply, isMaskReversed, keyCode) {
        if (this.byPassKeys.indexOf(keyCode) === -1) {
            let value = this.getMasked(false, actualValue, maskToApply, isMaskReversed);
            let calculatedCaret = this.calculateCaretPosition(startCaret, actualValue, keyCode);
            this.render.setElementAttribute(this.el.nativeElement, 'value', value);
            this.el.nativeElement.value = value;
            this.setValue(value);
            this._onChange(value);
            this.setCaretPosition(calculatedCaret);
        }
    }

    private setCaretPosition(caretPosition) {
        this.el.nativeElement.moveStart = caretPosition;
        this.el.nativeElement.moveEnd = caretPosition;
    }

    calculateCaretPosition(caretPosition, newValue, keyCode) {
        let newValueLength = newValue.length;
        let oldValue = this.getValue() || '';
        let oldValueLength = oldValue.length;

        if (keyCode === 8 && oldValue !== newValue) {
            caretPosition = caretPosition - (newValue.slice(0, caretPosition).length - oldValue.slice(0, caretPosition).length);
        } else if (oldValue !== newValue) {
            if (caretPosition >= oldValueLength) {
                caretPosition = newValueLength;
            } else {
                caretPosition = caretPosition + (newValue.slice(0, caretPosition).length - oldValue.slice(0, caretPosition).length);
            }
        }
        return caretPosition;
    }

    getMasked(skipMaskChars, val, mask, isReversed = false) {
        let buf = [],
            value = val,
            maskIndex = 0,
            maskLen = mask.length,
            valueIndex = 0,
            valueLength = value.length,
            offset = 1,
            addMethod = 'push',
            resetPos = -1,
            lastMaskChar,
            lastUntranslatedMaskChar,
            check;

        if (isReversed) {
            addMethod = 'unshift';
            offset = -1;
            lastMaskChar = 0;
            maskIndex = maskLen - 1;
            valueIndex = valueLength - 1;
        } else {
            lastMaskChar = maskLen - 1;
        }
        check = this.isToCheck(isReversed, maskIndex, maskLen, valueIndex, valueLength);
        while (check) {
            let maskDigit = mask.charAt(maskIndex),
                valDigit = value.charAt(valueIndex),
                translation = this.translationMask[maskDigit];

            if (translation) {
                if (valDigit.match(translation.pattern)) {
                    buf[addMethod](valDigit);
                    if (translation.recursive) {
                        if (resetPos === -1) {
                            resetPos = maskIndex;
                        } else if (maskIndex === lastMaskChar) {
                            maskIndex = resetPos - offset;
                        }
                        if (lastMaskChar === resetPos) {
                            maskIndex -= offset;
                        }
                    }
                    maskIndex += offset;
                } else if (valDigit === lastUntranslatedMaskChar) {
                    lastUntranslatedMaskChar = undefined;
                } else if (translation.optional) {
                    maskIndex += offset;
                    valueIndex -= offset;
                } else {
                    this.invalidCharacters.push({
                        index: valueIndex,
                        digit: valDigit,
                        translated: translation.pattern
                    });
                }
                valueIndex += offset;
            } else {
                if (!skipMaskChars) {
                    buf[addMethod](maskDigit);
                }
                if (valDigit === maskDigit) {
                    valueIndex += offset;
                } else {
                    lastUntranslatedMaskChar = maskDigit;
                }
                maskIndex += offset;
            }
            check = this.isToCheck(isReversed, maskIndex, maskLen, valueIndex, valueLength);
        }

        let lastMaskCharDigit = mask.charAt(lastMaskChar);
        if (maskLen === valueLength + 1 && !this.translationMask[lastMaskCharDigit]) {
            buf.push(lastMaskCharDigit);
        }

        return buf.join('');
    }

    private isToCheck(isReversed, maskIndex, maskLen, valueIndex, valueLength) {
        let check = false;
        if (isReversed) {
            check = (maskIndex > -1) && (valueIndex > -1);
        } else {
            check = (maskIndex < maskLen) && (valueIndex < valueLength);
        }
        return check;
    }

    private setValue(value) {
        this.value = value;
    }

    private getValue() {
        return this.value;
    }
}
