<?php
// vim: set expandtab cindent tabstop=4 shiftwidth=4 fdm=marker:
// +----------------------------------------------------------------------+
// | The Function Inc                                                     |
// +----------------------------------------------------------------------+
// | Copyright (c) 2014, Function Inc. All rights reserved.               |
// +----------------------------------------------------------------------+
// | Authors: The PHP Dev Team, ISRD, Function Inc.                       |
// |                                                                      |
// +----------------------------------------------------------------------+

/**
 * @version  1.0
 * @author   wade
 * @date     2014-04-03
 */

/**
 * $config = array(
 *     'test1' => array('required' => 1, 'type' => 'number', 'min' => 1000, 'max' => 10000),
 *     'test2' => array('required' => 1, 'inArray' => array('sms', 'mno')),
 *     'test3' => array('required' => 1, 'minLength' => 10, 'maxLength' => 20),
 *     'test4' => array('reg' => '/^\d+$/'),
 *     'test5' => array('reg' => '/^\d+$/'),
 *     'test6' => array('type' => 'email'),
 *     'test7' => array('type' => 'url'),
 *     'test8' => array('type' => 'float'),
 *     'test9' => array('type' => 'bool'),
 *     'test10' => array('type' => 'ip'),
 *     'test11' => array('type' => 'wrongType'),
 * );
 *
 * $_POST = array(
 *     'test1' => '1004',
 *     'test2' => 'sms',
 *     'test3' => '111111111111',
 *     'test4' => '1111',
 *     'test5' => array(
 *         0 => array(
 *             0 => '11111',
 *         ),
 *         1 => '11111',
 *     ),
 *     'test6' => 'billfeller@gmail.com',
 *     'test7' => 'http://www.baidu.com/',
 *     'test8' => 11,
 *     'test9' => 1,
 *     'test10' => '127.0.0.1',
 *     'test11' => '111',
 * );
 *
 * $validationForm = new IValidate();
 * $ret = $validationForm->run($config);
 * var_dump($ret);
 * if($ret === FALSE) {
 *     var_dump($validationForm->getErrorString());
 * }
 */

class validate {
    private $validationData;
    private $errorMsg;

    public function __construct(array $data = array()) {
        if(!empty($data)) {
            $this->validationData = $data;
        }
    }

    public function setMsg($message) {
        if(empty($this->errorMsg)) {
            $this->errorMsg = array();
        }

        $this->errorMsg[] = $message;
    }

    public function getMsg() {
        return $this->errorMsg;
    }

    public function getErrorString() {
        if (count($this->errorMsg) === 0) {
            return '';
        }
        return implode("\n", $this->errorMsg);
    }

    public function run(array $config = array(), &$data = array()) {
        $validationArray = empty($this->validationData) ? $_POST : $this->validationData;
        if(count($validationArray) === 0 || count($config) === 0) {
            return TRUE;
        }

        foreach($config as $key => $checkList) {
            if(empty($checkList)) {
                continue;
            }
            $value = @$validationArray[$key];
            $this->_execute($value, $checkList, $key);
            $data[$key] = $value;
        }
        return (count($this->errorMsg) === 0);
    }

    protected function _execute($value, $checkList, $key = '') {
        if(is_array($value)) {
            foreach($value as $childKey => $val) {
                $newKey = sprintf('%s[%s]', $key, $childKey);
                $this->_execute($val, $checkList, $newKey);
            }

            return ;
        }

        // 非必填项 允许为空
        if(empty($checkList['required']) && ($value === NULL || $value === '')) {
            return ;
        }

        // 必填项
        if(!empty($checkList['required']) && ($value === NULL || $value === '')) {
            $message = sprintf('%s=%s: required', $key, $value);
            $this->setMsg($message);
            return ;
        }

        if(!empty($checkList['type'])) {
            $ret = $this->_checkType($checkList['type'], $value);
            if($ret === FALSE) {
                $message = sprintf('%s=%s: the type should be %s', $key, $value, $checkList['type']);
                $this->setMsg($message);
                return ;
            }
        }

        if(isset($checkList['minLength'])) {
            if(!is_numeric($checkList['minLength'])) {
                $message = sprintf('%s=%s: the minLength configuration: %s', $key, $value, $checkList['minLength']);
                $this->setMsg($message);
                return ;
            }

            $minLength = (int)$checkList['minLength'];
            $ret = mb_strlen($value) >= $minLength;
            if($ret === FALSE) {
                $message = sprintf('%s=%s: the string length should be greater than %d', $key, $value, $minLength);
                $this->setMsg($message);
                return ;
            }
        }

        if(isset($checkList['maxLength'])) {
            if(!is_numeric($checkList['maxLength'])) {
                $message = sprintf('%s=%s: the maxLength configuration: %s', $key, $value, $checkList['maxLength']);
                $this->setMsg($message);
                return ;
            }

            $maxLength = (int)$checkList['maxLength'];
            $ret = mb_strlen($value) <= $maxLength;
            if($ret === FALSE) {
                $message = sprintf('%s=%s: the string length should be less than %d', $key, $value, $maxLength);
                $this->setMsg($message);
                return ;
            }
        }

        if(!empty($checkList['type']) && $checkList['type'] === 'number') {
            if(isset($checkList['min'])) {
                if(!is_numeric($checkList['min'])) {
                    $message = sprintf('%s=%s: wrong min configuration: %d', $key, $value, $checkList['min']);
                    $this->setMsg($message);
                    return ;
                }

                $min = (int)$checkList['min'];
                $ret = intval($value) >= $min;
                if($ret === FALSE) {
                    $message = sprintf('%s=%s: the value should be greater than %d', $key, $value, $min);
                    $this->setMsg($message);
                    return ;
                }
            }

            if(isset($checkList['max'])) {
                if(!is_numeric($checkList['max'])) {
                    $message = sprintf('%s=%s: wrong max configuration: %d', $key, $value, $checkList['max']);
                    $this->setMsg($message);
                    return ;
                }

                $max = (int)$checkList['max'];
                $ret = intval($value) <= $max;
                if($ret === FALSE) {
                    $message = sprintf('%s=%s: the value should be less than %d', $key, $value, $max);
                    $this->setMsg($message);
                    return ;
                }
            }
        }

        if(!empty($checkList['inArray'])) {
            if(!is_array($checkList['inArray'])) {
                $message = sprintf('%s=%s: wrong inArray configuration: %s', $key, $value, gettype($checkList['inArray']));
                $this->setMsg($message);
                return ;
            }

            if(!in_array($value, $checkList['inArray'])) {
                $message = sprintf('%s=%s: not inArray %s', $key, $value, json_encode($checkList['inArray']));
                $this->setMsg($message);
                return ;
            }
        }

        if(!empty($checkList['reg'])) {
            $ret = $this->_checkReg($checkList['reg'], $value);
            if($ret === FALSE) {
                $message = sprintf('%s=%s: the type should be like %s', $key, $value, $checkList['reg']);
                $this->setMsg($message);
                return ;
            }
        }

        return ;
    }

    private function _checkType($type, $value) {
        switch($type) {
            case 'number' :
                $ret = is_numeric($value);
                break;
            case 'bool' :
                $ret = (bool)filter_var($value, FILTER_VALIDATE_BOOLEAN);
                break;
            case 'email' :
                $ret = (bool)filter_var($value, FILTER_VALIDATE_EMAIL);
                break;
            case 'float' :
                $ret = (bool)filter_var($value, FILTER_VALIDATE_FLOAT);
                break;
            case 'ip' :
                $ret = (bool)filter_var($value, FILTER_VALIDATE_IP);
                break;
            case 'url' :
                $ret = (bool)filter_var($value, FILTER_VALIDATE_URL);
                break;
            default :
                $ret = FALSE;
                $message = sprintf('unknown type: %s', $type);
                $this->setMsg($message);
                break;
        }

        return $ret;
    }

    protected function _checkReg($regex, $string) {
        return (bool)preg_match($regex, $string);
    }
}

