/* Copyright CloudLinux Zug GmbH 2010-2017 All Rights Reserved
 *
 * Licensed under CLOUD LINUX LICENSE AGREEMENT
 * http://cloudlinux.com/docs/LICENSE.TXT
 */

import { TranslateClass } from './pipes/translate';
import { Requirements } from './row';
import { ElementRef } from '@angular/core';

declare var jQuery: any;

export interface ValidatorInterface {
    error: boolean;
    validate(value: number | string,
             oldValue: number | string,
             elementRef: ElementRef,
             validatorData: any);
    defineRequirements(requirements: Requirements[]);
}


export class Validator implements ValidatorInterface {

    public static messages = {
        'required':             'Required field',
        'maxLength':            'Length must be not more than {0}',
        'minLength':            'Length must be not less than {0}',
        'numeric':              'Not a valid numeric value',
        'alphanumeric':         'Not a valid alphanumeric value',
        'cpu':                  'Invalid value. Use postfix. For example: 1024[Mhz|Ghz|%]',
        'memory':               'Invalid value. Use postfix. For example: 1024[K|M|G]',
        'speed':                'Invalid value. Use postfix. For example: 1024[KB/s|MB/s|G/s]',
        'speed_io':             'Invalid value. Use postfix. For example: 1024[MB/s|G/s]',
        'inode':                'Two numbers separated by comma required',
        'positive_number':      'Field must be positive number',
        'not_negative_number':  'Field must be positive number or zero',
        'valid_path_to_file':   'Path is not valid',
        'valid_log_file':       'Path to log file is not valid',
        'unknown':              'Unknown error'
    };

    public value: string | number;
    public error: boolean;
    public errorMessages: string[] = [];
    public requirements: Requirements[] = [];

    public get errorMessagesAsString(): string {
        return this.errorMessages.join('. ');
    }

    public static isRequired(val: string): ResultOfValidation {
        return Validator.result('required', String(val).length > 0);
    }

    public static isMaxLength(val: string, length: any): ResultOfValidation {
        return Validator.result('maxLength', String(val).length <= length, [length]);
    }

    public static isMinLength(val: string, length: any): ResultOfValidation {
        return Validator.result('minLength', String(val).length >= length, [length]);
    }

    /**
     * Checks that current value contains only numeric symbols
     * or spaces or points.
     *
     * @returns {boolean}   Result of validation.
     */
    public static isNumeric(val: string): ResultOfValidation {
        return Validator.result('numeric', /^[0-9]+(\.[0-9]+)?$/.test(val) || val === '');
    }

    public static isAlphanumeric(val: string): ResultOfValidation {
        return Validator.result('alphanumeric', /^[\w\s]+$/.test(val) || val === '');
    }

    public static isMemory(val: string): ResultOfValidation {
        return Validator.result('memory', /^\d+(\.\d+)?[KMG]?$/.test(val) || val === '');
    }

    public static isSpeed(val: string): ResultOfValidation {
        return Validator.result(
            'speed', /^(\d+(\.\d+)?(MB\/s|G\/s)|\d+(KB\/s)?)$/i.test(val) || val === ''
        );
    }

    public static isSpeedMysql(val: string): ResultOfValidation {
        return Validator.result(
            'speed_io', /^(\d+(\.\d+)?(MB\/s|G\/s)?)$/i.test(val) || val === ''
        );
    }

    public static isCpuValue(val: string): ResultOfValidation {
        return Validator.result('cpu', /^\d+?((\.\d+)*mhz|(\.\d+)*ghz|%)?$/ig.test(val)
            && parseFloat(val) > 0 || val === '');
    }

    public static isPositiveNumber(val: string): ResultOfValidation {
        return Validator.result('positive_number', /^\d+$/.test(val) && parseInt(val, 10) > 0 );
    }

    public static isNotNegativeNumber (val: string): ResultOfValidation {
        return Validator.result('not_negative_number', /^\d+$/.test(val));
    }

    public static isValidPathToFile(val: string): ResultOfValidation {
        return Validator.result('valid_path_to_file', /^(\/[^/]+)*$/.test(val) || val === '');
    }

    public static isValidLogFile(val: string): ResultOfValidation {
        // /^(\/[^/]+)*\.log$/
        return Validator.result('valid_log_file', /^(\/[^/]+)*$/.test(val) || val === '');
    }

    private static result(
        key: string, condition: boolean, replacer: any[] = []
    ): ResultOfValidation {
        return condition
            ? {'isOK': true}
            : {'isOK': false, 'message': Validator.getErrorMessage(key, replacer)};
    }

    private static getErrorMessage(code: string, replacer: any[] = []) {
        let translate = new TranslateClass();
        return Validator.sprintf(
            translate.transform(
                Validator.messages.hasOwnProperty(code)
                    ?  Validator.messages[code]
                    : Validator.messages['unknown']
            ),
            replacer
        );
    }

    /**
     * Returns formatted string.
     *
     * @param {string} str      String that must be formatted.
     * @param {string[]} args   Arguments for replacing of placeholders.
     *
     * @returns {string}   Formatted string filled with passed arguments.
     */
    private static sprintf(str: string, args: string[]) {
        if (args.length) {
            let regExp;
            for (let i = 0; i < args.length; i++) {
                regExp = new RegExp('{([^{]*)(' + i + ')([^}]*)}');
                str = str.replace(regExp, '$1' + args[i] + '$3');
            }
        } else {
            str = str.replace(/{[^{]*\d+[^}]*}/, '');
        }
        return str;
    }

    public defineRequirements(requirements: Requirements[]) {
        this.requirements = requirements;
        return this;
    }

    public setValue(value: string | number) {
        this.value = value;
        return this;
    }

    public resetErrors() {
        this.errorMessages = [];
        this.error = false;
    }

    public addError(errorMessage: string) {
        this.errorMessages.push(errorMessage);
        this.error = true;
    }

    public get hasRequirements(): boolean {
        return !!this.requirements.length;
    }

    public validate(value: number | string, oldValue: number | string, elementRef: ElementRef,
                    validatorData) {
        if (value === null) return;

        this.value = value;

        this.resetErrors();

        for (let requirement of this.requirements) {
            let requiredParams = requirement.p ? requirement.p : [];
            let params = [value, ...requiredParams];

            let result = requirement.m.apply(null, params);

            if (!result['isOK']) {
                this.addError(result['message']);
            }
        }

        let $tooltip = jQuery(elementRef.nativeElement);

        setTimeout(() => {
            if ($tooltip.tooltip) {
                $tooltip.tooltip(this.error ? 'show' : 'hide');
            }
        }, 100);
    }
}

export interface ResultOfValidation {
    isOK: boolean;
    message?: string;
}
