<?php

/**
 * Vvveb
 *
 * Copyright (C) 2020  Ziadin Givan
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 */

namespace Vvveb\System;

define('VALIDATE_IGNORE_NOT_DEFINED','');

class Validator {
	private $rules;

	private $defaultMessages = [];

	/*
		[
			'notEmpty' => '%s is empty', 
			'allowed_values' => '%s is invalid, valid options are %s', 
			'maxLength' => '%s is longer than %d'
		];
	*/
	function __construct($rules) {
		$this->defaultMessages = [
			'notEmpty'            => _('%s is empty'),
			'allowedValues'       => _('%s is invalid, valid options are %s'),
			'maxLength'           => _('%s is longer than %d'),
			'minLength'           => _('%s is shorter than %d'),
			'match'               => _('%s does not match %s'),
			'captcha'             => _('%s is invalid'),
			'session'             => _('%s is invalid'),
			'email'               => _('%s invalid email'),
			'passwordComplexity'  => _('%s not complex enough, include uppercase letters and digits'),
		];

		$this->rules($rules);
	}

	function rules($ruleFiles) {
		$rules = [];

		foreach ($ruleFiles as $rule) {
			$rules = array_merge($rules, include(DIR_APP . 'validate/' . $rule . '.php'));
		}

		$this->rules = $rules;
	}

	//remove keys that are not in the validation list
	function filter($input) {
		$validKeys = array_keys($this->rules);

		return array_filter($input, function ($key) use ($validKeys) {
			return in_array($key,$validKeys);
		}, ARRAY_FILTER_USE_KEY);
	}

	function validate($input) {
		$errors = [];

		foreach ($this->rules as $inputName => $rules) {
			if (isset($input[$inputName])) {
				foreach ($rules as $rule) {
					$ruleName    = key($rule);
					$ruleOptions = $rule[$ruleName];
					$ruleMethod  = 'rule' . $ruleName;

					$message = '';

					if (isset($this->defaultMessages[$ruleName])) {
						$message = $this->defaultMessages[$ruleName];
					}

					if (isset($rule['message'])) {
						$message = $rule['message'];
					}

					$name = ucfirst(str_replace('_', ' ', $inputName));

					if (method_exists($this, $ruleMethod)) {
						$errorMessage = $this->$ruleMethod($input[$inputName], $ruleOptions, $name, $message, $input);
					}

					if ($errorMessage) {
						$errors[$inputName] = $errorMessage;
					}
				}
			} else {
				$errors[$inputName] = sprintf(_('%s is empty'),  ucfirst(str_replace('_', ' ', $inputName)));
			}
		}

		return empty($errors) ? true : $errors;
	}

	function getJSON() {
		foreach ($this->rules as $inputName => &$rules) {
			foreach ($rules as &$rule) {
				$ruleName = key($rule);

				if (! isset($rule['message'])) {
					$rule['message'] = $this->defaultMessages[$ruleName];
				}
			}
		}

		return json_encode($this->rules);
	}

	/* rules */

	function ruleNotEmpty($value, $options, $name, $message, $input) {
		if (empty($value)) {
			return sprintf(_($message), $name);
		}

		return false;
	}

	function ruleEmail($value, $options, $name, $message, $input) {
		if (! filter_var($value, FILTER_VALIDATE_EMAIL)) {
			return sprintf(_($message), $name);
		}

		return false;
	}

	function rule($value, $options, $name, $message, $input) {
		if (! in_array($value, $options)) {
			return sprintf(_($message), $name, implode(', ',$options));
		}

		return false;
	}

	function ruleMaxLength($value, $options, $name, $message, $input) {
		if (strlen($value) > $options) {
			return  sprintf(_($message), $name, $options);
		}

		return false;
	}

	function ruleMinLength($value, $options, $name, $message, $input) {
		if (strlen($value) < $options) {
			return sprintf(_($message), $name, $options);
		}

		return false;
	}

	function ruleAllowedValues($value, $options, $name, $message, $input) {
		if (strlen($value) > $options) {
			return  sprintf(_($message), $name, implode(', ' ,$options));
		}

		return false;
	}

	function ruleSession($value, $options, $name, $message, $input) {
		if (\Vvveb\session($options) != $value) {
			return sprintf(_($message), $name, $options);
		}

		return false;
	}

	function ruleCaptcha($value, $options, $name, $message, $input) {
		if ($input[$options] != $value) {
			return sprintf(_($message), $name, $options);
		}

		return false;
	}

	function ruleMatch($value, $options, $name, $message, $input) {
		if ($input[$options] != $value) {
			return sprintf(_($message), $name, $options);
		}

		return false;
	}

	function rulePasswordComplexity($value, $options, $name, $message, $input) {
		switch ($options) {
			case 'low':
				$regex =
				'/^' . 						//	Start anchor
				'(?=.*[A-Z])' .				//  Ensure string has one uppercase letters.
				'(?=.*[0-9])' .   			//	Ensure string has one digits.
				'(?=.*[a-z].*[a-z].*[a-z])' . // 	Ensure string has three lowercase letters.
				'.*$/';

			break;

			case 'medium':
				$regex =
				'/^' . 						//	Start anchor
				'(?=.*[A-Z].*[A-Z])' .		//  Ensure string has two uppercase letters.
				'(?=.*[!@#$&*%])' .			//  Ensure string has one special case letter.
				'(?=.*[0-9].*[0-9])' .   	//	Ensure string has two digits.
				'(?=.*[a-z].*[a-z].*[a-z])' . // 	Ensure string has three lowercase letters.
				'.*$/';

			case 'high':
				$regex =
				'/^' . 						//	Start anchor
				'(?=.*[A-Z].*[A-Z])' .		//  Ensure string has two uppercase letters.
				'(?=.*[!@#$&*%])' .			//  Ensure string has one special case letter.
				'(?=.*[0-9].*[0-9])' .   	//	Ensure string has two digits.
				'(?=.*[a-z].*[a-z].*[a-z])' . // 	Ensure string has three lowercase letters.
				'.*$/';

			break;
		}

		if (($result = preg_match($regex, $value)) == false) {
			return sprintf(_($message), $name, $options);
		}

		return false;
	}

	function js($form) {
		return 'jQuery(' . $form . ').validate({rules:' . json_encode($this->rules,  JSON_FORCE_OBJECT) . ', invalidHandler: function(form, validator) {var errors = validator.numberOfInvalids();if (errors) {  $("#errorMessage").removeClass("success").html("You have " + errors + " errors. They have been highlighted");$("#errorMessage").show();} else {$("#errorMessage").hide();}}});';
	}
}
