
// Библиотеки
import * as _ from 'lodash';
const utf8 = require('utf8');


// Глобальные сервисы
import MainRequest from '../System/MainRequest';

// Системные сервисы

import { ErrorSys } from './ErrorSys';


/**
 * Системный сервис валидации данных для моделей
 */
export class ModelValidatorSys {
    protected okResult: boolean; // Статус проверки
    protected abValidOK: any; // поля успешно прошедшие проверку
    protected data: any; // Входящие данные
    protected aResult: any; // Отфильтрованные проверенные данные
    protected aResultType: any;
    protected aMsg: string[]; // Сообщения валидации

	/**
	 * Система регистрации ошибок
	 *
	 * @var Sys\ErrorSys
	 */
    protected errorSys: ErrorSys;

    constructor(req: MainRequest) {
        this.errorSys = req.sys.errorSys;
    }

	/**
	 * Валидирует и экранирует строковое значени
	 *
	 * @param string sKey - ключ в базе данных
	 * @param string sTpl - регулярное выражение по которому проверять
	 * @return boolean
	 */
    protected fValidString(sKey: string, sTpl: RegExp): boolean {

        let bSuccess = false;
        let s = String(this.data[sKey]).trim();

        if ( s || s === '' ) {

            s = utf8.encode(s);

            if (sTpl instanceof RegExp) { // Проверка на регулярное выражение

                if (sTpl.exec(s)) {
                    this.aResult[sKey] = s;

                    bSuccess = true;
                }
            } else {
                this.aResult[sKey] = s;
                bSuccess = true;
            }
        }

        return bSuccess;
    }

	/**
	 * Экранирует текст
	 *
	 * @param string sKey
	 * @return boolean
	 */
    protected fValidText(sKey: string): boolean {

        let bSuccess = false;
        let s = String(this.data[sKey]).trim();

        if ( s || s === '' ) {

            this.aResult[sKey] = s;
            bSuccess = true;

		}

        return bSuccess;
    }

	/**
	 * Валидирует булевую переменную
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
    protected fValidBool(sKey: string): boolean {

        let bSuccess = false;
        let i = Number(this.data[sKey]);

        if (!isNaN(i)) {

            if (i == 0 || i == 1) {

                this.aResult[sKey] = i;
                bSuccess = true;
            } else {
                bSuccess = false;
            }

        }

        return bSuccess;
    }

	/**
	 * Проверяет числовые значения
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
    protected fValidInt(sKey: string): boolean {

        let bSuccess = false;
        let i = Math.round(Number(this.data[sKey]));

        if (!isNaN(i)) {

            this.aResult[sKey] = i;
            bSuccess = true;
        }

        return bSuccess;
    }

	/**
	 * Проверяет числовые значения - 2.22
	 *
	 * @param string sKey
	 * @return boolean
	 */
    protected fValidDecimal(sKey: string): boolean {

        let bSuccess = false;
        let i = parseFloat(Number(this.data[sKey]).toFixed(2));

        if (!isNaN(i)) {

            this.aResult[sKey] = i;
            bSuccess = true;

        }

        return bSuccess;
    }
	/**
	 * Проверяет массив чисел
	 *
	 * @param string sKey
	 * @return boolean
	 */
    protected fValidArrayNumbers(sKey: string): boolean {

		let bSuccess = false;
		let checkArray = true;
		
		let array = this.data[sKey];
		if (_.isArray(array)) {
			for(let i = 0; i < array.length; i++){
				if (checkArray) {
					array[i] = Number(array[i]);
					if (!array[i]) {
						checkArray = false;
					}
				}
			}
		} else {
			checkArray = false;
		}

        if (checkArray) {
            this.aResult[sKey] = array;
            bSuccess = true;
        }

        return bSuccess;
    }

	/**
	 * Проверка Enum параметров
	 *
	 * @param sKey - ключ значения
	 * @param aEnumList - Список возможных значений
	 */
    protected fValidEnum(sKey: string, aEnumList: any[]) {

        let bSuccess = false;
        let v: any = this.data[sKey];

        if (_.indexOf(aEnumList, v) >= 0) {
            let index = _.indexOf(aEnumList, this.data[sKey]);

            this.aResult[sKey] = aEnumList[index];
            bSuccess = true;
        }

        return bSuccess;
    }

	/**
	 * Экранирует JSON и проверяет
	 * Если это массив конвертирует в JSON
	 *
	 * @param string sKey
	 * @return boolean
	 */
    protected fValidJson(sKey: string): boolean {
        let vJsonValue = this.data[sKey];
        let sJsonValue = '';
        let bSuccess = false;

        if (vJsonValue) {

            // Проверка на массив
            if (_.isObject(vJsonValue)) {
                sJsonValue = JSON.stringify(vJsonValue);
            } else {
                sJsonValue = vJsonValue;
            }

            // Проверка строки на корректный JSON
            try {
                let obj = null;
                obj = JSON.parse(sJsonValue);

                if (obj) {
                    this.aResult[sKey] = sJsonValue;

                    bSuccess = true;
                }
            } catch (e) {
                this.errorSys.errorEx(e, sKey + '_json_parse', sKey + ' - неверный формат json поля');
            }

        }

        return bSuccess;
	}

	/**
	 * Проверяет объект ли это
	 *
	 * @param string sKey
	 * @return boolean
	 */
    protected fValidObject(sKey: string): boolean {

		let bSuccess = false;

        if ( _.isObject(this.data[sKey]) ) {

            this.aResult[sKey] = this.data[sKey];
            bSuccess = true;

        }

        return bSuccess;
    }

	/**
	 * Проверяет массив ли это
	 *
	 * @param string sKey
	 * @return boolean
	 */
    protected fValidArray(sKey: string): boolean {

		let bSuccess = false;

        if ( _.isArray(this.data[sKey]) ) {

            this.aResult[sKey] = this.data[sKey];
            bSuccess = true;

        }

        return bSuccess;
    }

    // ================================================================
    // Логические проверки
    // ================================================================

	/**
	 * Проверяет на больше
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
    protected fValidMore(sKey: string, iVal: number): boolean {

        let bSuccess = false;
        let i = Number(this.aResult[sKey])

        if (!_.isNaN(i)) {
            if (i > iVal) { // Если значение больше - все хорошо
                this.aResult[sKey] = i;
                bSuccess = true;
            }
        }

        return bSuccess;
	}

	/**
	 * Проверяет на больше или равно
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
	protected fValidMoreOrEqual(sKey: string, iVal: number): boolean {

		let bSuccess = false;
		let i = Number(this.aResult[sKey])

		if (!isNaN(i)) {
			if (i >= iVal) { // Если значение больше - все хорошо
				this.aResult[sKey] = i;
				bSuccess = true;
			}
		}

		return bSuccess;
	}

	/**
	 * Проверяет на меньше
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
    protected fValidLess(sKey: string, iVal: number): boolean {

        let bSuccess = false;
        let i = Number(this.aResult[sKey]);

        if (!_.isNaN(i)) {
            if (i < iVal) { // Если значение меньше - все хорошо
                this.aResult[sKey] = i;
                bSuccess = true;
            }
        }

        return bSuccess;
	}

	/**
	 * Проверяет на меньше или равно
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
	protected fValidLessOrEqual(sKey: string, iVal: number): boolean {

		let bSuccess = false;
		let i = Number(this.aResult[sKey]);

		if ( !isNaN(i) ) {
			if (i <= iVal) { // Если значение меньше - все хорошо
				this.aResult[sKey] = i;
				bSuccess = true;
			}
		}

		return bSuccess;
	}

	/**
	 * Проверяет на макс количесво символов
	 *
	 * @param string sKey
	 * @param string sTpl
	 * @return boolean
	 */
    protected fValidMaxLen(sKey: string, iLen: number): boolean {

        let bSuccess = false;

        if (this.aResult[sKey] || this.aResult[sKey] == '') {
            let s = String(this.aResult[sKey]);

            if (s.length <= iLen) { // Если значение меньше - все хорошо
                this.aResult[sKey] = s;
                bSuccess = true;
            }
		}

        if (bSuccess) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Проверяет минимальное количество символов
     *
     * @param string stringKey
     * @param number checkValue
     * @return boolean
     */

    protected fValidMinLen(stringKey: string, checkValue: number): boolean {

        if (this.aResult[stringKey]) {
            const preparedInputString = String(this.aResult[stringKey]);

            if (preparedInputString.length >= checkValue) {
                this.aResult[stringKey] = preparedInputString;

                return true;
            }
        }

        return false;
	}

	/**
	 * Проверить существование значения
	 * @param val - Значение
	 */
	private checkExist(val: any): boolean {
		let resp = true;

		if (val == undefined) {
			resp = false;
		}

		if (val == null) {
			resp = false;
		}

		return resp;
	}

    // ================================================================

	/**
	 * Получить проверенные отфильтрованные данные в качестве массива
	 *
	 * @return array|null
	 */
	public getResult(): {[key:string]:any}|any{ // Получить проверенные отфильтрованные данные
		return this.aResult;
	}
	public getStatus(){ // Получиь статус проверки
		return this.okResult;
	}
	public getMsg(): string[]{
		return this.aMsg;
	}

	//ФОРМАТ ПРАВИЛА [
	//	key_field :[0:type, 1:condition, 2:required, 3:depend, 4:msg_error]
	//]
	public fValid(aRules:any, data:{[key:string]:any}){ // Проверка данных

		this.data = data;
		this.okResult = true; // [true/false] - Успешно или нет прошла валидация
		this.abValidOK = {};
		this.aResult = {};
		this.aMsg = [];

		_.forEach(aRules, (v, k) => {

			this.abValidOK[k] = true;

			//Подстановка значений по умолчанию, если значения нет
			if( this.okResult && v['def'] && !this.data[k] ){
				this.data[k] = v['def'];
			}

			if( 'error_key' in v ){ // Если указываем ключ ошибки декларируем ее
				let errorKey:any = {};
				errorKey[v['error_key']['key']] = v['error_key']['msg'];

				this.errorSys.declareEx(errorKey);
			}

			//Проверка существования данных
			let bExist = this.checkExist(this.data[k]);

			//Проверка зависимостей
			let bDpend = true;
			if( v['depend'] ){
				this.errorSys.decl('valid_'+k+'_depend', k+' - поле не прошло проверку зависимостей');

				_.forEach(v['depend'], (vDepend, kDepend) => {
					if( this.okResult && this.abValidOK[kDepend] ){
						if( this.abValidOK[kDepend] && this.data[kDepend] ){
							if( !(this.data[kDepend] == vDepend || vDepend == '*') ){

								bDpend = false;
								this.errorSys.error('valid_'+k+'_depend', k+' - поле не прошло проверку зависимостей');

							}
						}
					}//if
				}); //foreach
			}//if

			//Проверка - обязательного поля
			if( v['require'] ){
				this.errorSys.decl('valid_'+k+'_require', k+' - поле обязательно для заполнения');

				if( !this.checkExist(this.data[k]) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_require', k+' - поле обязательно для заполнения');
				}
			}

			// Обработка [string] значений
			if( bExist && bDpend && v['type'] == 'str' ){
				this.errorSys.decl('valid_'+k+'_str', v['error']+' Ошибка string = '+this.data[k]);

				if( !this.fValidString(k, v['if']) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_str', v['error']+' Ошибка string = '+this.data[k]);
				}
			}

			// Обработка [boolean] значений
			if( bExist && bDpend && v['type'] == 'boolean' ){
				this.errorSys.decl('valid_'+k+'_bool', v['error']+' Ошибка boolean = '+this.data[k]);

				if( !this.fValidBool(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_bool', v['error']+' Ошибка boolean = '+this.data[k]);
				}
			}

			// Обработка [integer] значений
			if( bExist && bDpend && v['type'] == 'int' ){
				this.errorSys.decl('valid_'+k+'_int', v['error']+' Ошибка int = '+this.data[k]);

				if( !this.fValidInt(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_int', v['error']+' Ошибка int = '+this.data[k]);
				}
			}

			// Обработка [enum] значений
			if( bExist && bDpend && v['type'] == 'enum' ){
				this.errorSys.decl('valid_'+k+'_enum', v['error']+' Ошибка enum = '+this.data[k]);

				if( !this.fValidEnum(k, v['if']) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_enum', v['error']+' Ошибка enum = '+this.data[k]);
				}
			}

			// Обработка [text] значений
			if( bExist && bDpend && v['type'] == 'text' ){
				this.errorSys.decl('valid_'+k+'_text', v['error']+' Ошибка text = '+this.data[k]);

				if( !this.fValidText(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_text', v['error']+' Ошибка text = '+this.data[k]);
				}
			}

			// Обработка [json] значений
			if( bExist && bDpend && v['type'] == 'json' ){
				this.errorSys.decl('valid_'+k+'_json', v['error']+' Ошибка json = '+this.data[k]);

				if( !this.fValidJson(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_json', v['error']+' Ошибка json = '+this.data[k]);
				}
			}

			// Обработка [decimal] значений
			if( bExist && bDpend && v['type'] == 'decimal' ){
				this.errorSys.decl('valid_'+k+'_decimal', v['error']+' Ошибка decimal = '+this.data[k]);

				if( !this.fValidDecimal(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_decimal', v['error']+' Ошибка decimal = '+this.data[k]);
				}
			}
            // Обработка [arrayNumbers] значений
            if (bExist && bDpend && v['type'] === 'arrayNumbers') {
                this.errorSys.decl('valid_' + k + '_arrayNumbers', v['error'] + ' Ошибка arrayNumbers = ' + this.data[k]);
                if (!this.fValidArrayNumbers(k)) {
                    this.okResult = false;
                    this.abValidOK[k] = false;
                    this.errorSys.error('valid_' + k + '_arrayNumbers', v['error'] + ' Ошибка arrayNumbers = ' + this.data[k]);
                }
            }
			// Обработка [object] значений
			if( bExist && bDpend && v['type'] == 'object' ){
				this.errorSys.decl('valid_'+k+'_object', v['error']+' Ошибка object = '+this.data[k]);

				if( !this.fValidObject(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_object', v['error']+' Ошибка object = '+this.data[k]);
				}
			}

			// Обработка [array] значений
			if( bExist && bDpend && v['type'] == 'array' ){
				this.errorSys.decl('valid_'+k+'_array', v['error']+' Ошибка array = '+this.data[k]);

				if( !this.fValidArray(k) ){
					this.okResult = false;
					this.abValidOK[k] = false;
					this.errorSys.error('valid_'+k+'_array', v['error']+' Ошибка array = '+this.data[k]);
				}
			}

			// =================================================
			// Логические проверки
			// =================================================

			// Обработка [more] значений - Проверка на больше
			if( bExist && 'more' in v ){
				this.errorSys.decl('valid_'+k+'_more', v['error']+' Число слишком маленькое = '+this.data[k]);

				if( v['type'] == 'int' || v['type'] == 'decimal' ){
					if( !this.fValidMore(k, v['more']) ){
						this.okResult = false;
						this.abValidOK[k] = false;
						this.errorSys.error('valid_'+k+'_more', v['error']+' Число слишком маленькое = '+this.data[k]);
					}
				} else {
					this.errorSys.error('valid_'+k+'_more_no_number', v['error']+' Поле не является числом');
				}
			}

			// Обработка [more_or_equal] значений - Проверка на больше или равно
			if (bExist && 'more_or_equal' in v) {
				this.errorSys.decl('valid_' + k + '_more_or_equal', 'errorValidate');

				if (v['type'] == 'int' || v['type'] == 'decimal') {
					if( !this.fValidMoreOrEqual(k, v['more_or_equal']) ){
						this.abValidOK[k] = false;
						this.okResult = false;
						this.errorSys.error('valid_' + k + '_more_or_equal', v['error'] + ' Число слишком маленькое = ' + this.data[k]);
					}
				} else {
					this.errorSys.error('valid_' + k + '_more_or_equal_no_number', v['error'] + ' Поле не является числом');
				}
			}

			// Обработка [less] значений - Проверка на меньше
			if( bExist && 'less' in v ){
				this.errorSys.decl('valid_'+k+'_less');

				if( v['type'] == 'int' || v['type'] == 'decimal' ){

					if( !this.fValidLess(k, v['less']) ){
						this.okResult = false;
						this.abValidOK[k] = false;
						this.errorSys.error('valid_'+k+'_less', v['error']+' Число слишком большое = '+this.data[k]);
					}
				} else {
					this.errorSys.error('valid_'+k+'_less_no_number', v['error']+' Поле не является числом');
				}
			}

			// Обработка [less_or_equal] значений - Проверка на меньше или равно
			if (bExist && 'less_or_equal' in v) {
				this.errorSys.decl('valid_' + k + '_less_or_equal', 'errorValidate');

				if (v['type'] == 'int' || v['type'] == 'decimal') {

					if ( !this.fValidLessOrEqual(k, v['less_or_equal']) ){
						this.abValidOK[k] = false;
						this.okResult = false;
						this.errorSys.error('valid_' + k + '_less_or_equal', v['error'] + ' Число слишком большое = ' + this.data[k]);
					}
				} else {
					this.errorSys.error('valid_' + k + '_less_or_equal_no_number', v['error'] + ' Поле не является числом');
				}
			}

			// Обработка [max_len] значений - Проверка на большее
			if( bExist && 'max_len' in v ){
				this.errorSys.decl('valid_'+k+'_max_len');
				this.errorSys.decl('valid_'+k+'_max_len_no_string');

				// Проверка является ли поле текстовым
				if( v['type'] == 'text' || v['type'] == 'str' ){
					if( !this.fValidMaxLen(k, v['max_len']) ){

						this.okResult = false;
						this.abValidOK[k] = false;
						this.errorSys.error('valid_'+k+'_max_len', v['error']+' Превышено количество символов = '+this.data[k] );
					}
				} else {
					this.errorSys.error('valid_'+k+'_max_len_no_string', 'Поле не является строкой');
				}
            }

            // Обработка [min_len] значений - Проверка на меньшее
            if (bExist && 'min_len' in v) {
                this.errorSys.decl(`valid_${k}_min_len`);
                this.errorSys.decl(`valid_${k}_min_len_no_string`);

                // Проверка является ли поле текстовым
                if (v.type === 'text' || v.type === 'str') {
                    if (this.fValidMinLen(k, v.min_len)) {
                        this.abValidOK[k] = true;
                    } else {
                        this.okResult = false;
                        this.errorSys.error(
                            `valid_'${k}_min_len`,
                            `${v.error} Количество символов меньше минимального значения = ${this.data[k]}`);
                    }
                } else {
                    this.errorSys.error(`valid_${k}_min_len_no_string`, 'Поле не является строкой');
                }
            }

            // ============================================

			// Кастомная ошибка на поле [error_key]
			if( !this.abValidOK[k] && 'error_key' in v ){ // Вызываем кастомную ошибку, если она произошла и была указана
				this.errorSys.error(v['error_key']['key'], v['error_key']['msg']);
			}



		}); // foreach

		return this.okResult;
	}


}
