public static function check(array $values, array $rules, array $options = []) {
		$defaults = [
			'notEmpty',
			'message' => null,
			'required' => true,
			'skipEmpty' => false,
			'format' => 'any',
			'on' => null,
			'last' => false
		];

		$options += $defaults;
		$params = compact('values', 'rules', 'options');

		return Filters::run(get_called_class(), __FUNCTION__, $params, function($params) {
			$values = $params['values'];
			$rules = $params['rules'];
			$options = $params['options'];

			$errors = [];
			$events = (array) (isset($options['events']) ? $options['events'] : null);
			$values = array_merge($values, Set::flatten($values));

			foreach ($rules as $field => $rules) {
				$rules = is_string($rules) ? ['message' => $rules] : $rules;
				$rules = is_array(current($rules)) ? $rules : [$rules];
				$errors[$field] = [];
				$options['field'] = $field;

				foreach ($rules as $key => $rule) {
					if (array_key_exists('required', $rule) && $rule['required'] === null) {
						unset($rule['required']);
					}
					$rule += $options + compact('values');
					list($name) = $rule;

					if ($events && $rule['on'] && !array_intersect($events, (array) $rule['on'])) {
						continue;
					}
					if (!array_key_exists($field, $values)) {
						if ($rule['required']) {
							$errors[$field][$key] = $rule['message'] ?: $key;
						}
						if ($rule['last']) {
							break;
						}
						continue;
					}
					if (empty($values[$field]) && $rule['skipEmpty']) {
						continue;
					}

					if (!static::rule($name, $values[$field], $rule['format'], $rule + $options)) {
						$errors[$field][$key] = $rule['message'] ?: $key;

						if ($rule['last']) {
							break;
						}
					}
				}
			}
			return array_filter($errors);
		});
	}