<?php
require_once dirname(dirname(__DIR__)) . '/das/methods/vendor/autoload.php';
require_once __DIR__ . '/simulate.php';

class MathCheck
{
    public $formula;

    public static $forecast;

    public static $funcList = array();

    public static $bracketList = array('[', ']', '{', '}');

    public static $hasError = false;

    public static $errorMsg = array();
    /* e.g.  $errorMsg = array(array('max', 'noParam'), array('pow', 'nanResult', "pow(1,2)")) . */

    /**
     * The construct function.
     *
     * @access public
     * @param  string $formula
     * @return void
     */
    public function __construct()
    {
    }

    public static function initFuncList()
    {
        self::$funcList['min']   = 'MATHCHECK::MYMIN';
        self::$funcList['max']   = 'MATHCHECK::MYMAX';
        self::$funcList['ceil']  = 'MATHCHECK::MYCEIL';
        self::$funcList['floor'] = 'MATHCHECK::MYFLOOR';
        self::$funcList['round'] = 'MATHCHECK::MYROUND';
        self::$funcList['pow']   = 'MATHCHECK::MYPOW';
        self::$funcList['sqrt']  = 'MATHCHECK::MYSQRT';
        self::$funcList['if']    = 'MATHCHECK::JUDGE';
    }

    /**
     * Get error message.
     *
     * @access public
     * @return void
     */
    public static function getError()
    {
        $errMsg = current(self::$errorMsg);
        /* In das/methods/montecarlo.php, use list(a,b,c) = $mathCheck->getError(), If $errMsg only have two item, add one item at least. */
        if(count($errMsg) == 2) $errMsg[] = '';

        return $errMsg;
    }

    /**
     * Replace formula.
     *
     * @access public
     * @param  string $formula
     * @return string
     */
    public static function replaceFormula($forecast)
    {
        foreach(self::$funcList as $before => $after)
        {
            $forecast = str_ireplace($before, $after, $forecast);
        }

        /* Replace equal. */
        $reg      = '/(?<!(=|<|>))=(?!=)/';
        $forecast = preg_replace($reg, '==', $forecast);

        return str_replace(' ', '', $forecast);
    }

    /**
     * Check brackets.
     *
     * @access public
     * @return bool
     */
    public static function checkBrackets($forecast)
    {
        foreach(self::$bracketList as $bracket)
        {
            if(strpos($forecast, $bracket) !== false) return true;
        }

        return false;
    }

    /**
     * Try to eval formula.
     *
     * @access public
     * @param  array $hypos
     * @param  array $decisions
     * @return bool
     * y1-y9 will be replaced by a-z and x1-x9, do not care.
     */
    public function evalFormula($hypos, $decisions)
    {
        /* Support var, a-z. */
        $execStr = '';
        foreach($hypos as $var => $config)
        {
            $value    = $config['type'] == 'dist' ? eval("return simulate::{$config['method']}->rand();") : $config['value'];
            $execStr .= '$' . $var . ' = ' . $value . ';';
            $this->formula  = str_replace($var, '$' . $var, $this->formula);
        }

        /* Support decisions, x1-x9. */
        foreach(array_keys($decisions) as $decision)
        {
            $execStr .= '$' . $decision . ' = 1;';
            $this->formula  = str_replace($decision, '$' . $decision, $this->formula);
        }

        $execStr .= "return $this->formula;";
        eval($execStr);

        return $this->hasError;
    }

    /**
     * MYMAX function.
     *
     * @access public
     * @return int
     */
    public static function myMax()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count < 2) return self::setError(array('max', 'least2Param'));

        foreach($params as $value)
        {
            if(is_nan($value)) return self::setError(array('max', 'nanResult', "max({$value})"));
        }

        return max($params);
    }

    /**
     * MYMIN function.
     *
     * @access public
     * @return int
     */
    public static function myMin()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count < 2) return self::setError(array('min', 'least2Param'));

        foreach($params as $value)
        {
            if(is_nan($value)) return self::setError(array('min', 'nanResult', "min({$value})"));
        }

        return min($params);
    }

    /**
     * MYCEIL function.
     *
     * @access public
     * @return int
     */
    public static function myCeil()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count == 0) return self::setError(array('ceil', 'noParam'));
        if($count == 1)
        {
            $value = ceil($params[0]);
            if(is_nan($value)) return self::setError(array('ceil', 'nanResult', "ceil({$params[0]})"));

            if(!is_finite($value)) return self::setError(array('ceil', 'infResult', "ceil({$params[0]})"));

            return $value;
        }

        return self::setError(array('ceil', 'need1Param'));
    }

    /**
     * MYFLOOR function.
     *
     * @access public
     * @return int
     */
    public static function myFloor()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count == 0) return self::setError(array('floor', 'noParam'));
        if($count == 1)
        {
            $value = floor($params[0]);
            if(is_nan($value)) return self::setError(array('floor', 'nanResult', "floor({$params[0]})"));

            if(!is_finite($value)) return self::setError(array('floor', 'infResult', "floor({$params[0]})"));

            return $value;
        }

        return self::setError(array('floor', 'need1Param'));
    }

    /**
     * MYROUND function.
     *
     * @access public
     * @return int
     */
    public static function myRound()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count == 0) return self::setError(array('round', 'noParam'));
        if($count == 1)
        {
            $value = round($params[0]);
            if(is_nan($value)) return self::setError(array('round', 'nanResult', "round({$params[0]})"));

            if(!is_finite($value)) return self::setError(array('round', 'infResult', "round({$params[0]})"));

            return $value;
        }
        if($count == 2)
        {
            $value = round($params[0], $params[1]);
            if(is_nan($value)) return self::setError(array('round', 'nanResult', "round({$params[0]},{$params[1]})"));

            if(!is_finite($value)) return self::setError(array('round', 'infResult', "round({$params[0]},{$params[1]})"));

            return $value;
        }

        return self::setError(array('round', 'need1to2Param'));
    }

    /**
     * MYPOW function.
     *
     * @access public
     * @return int
     */
    public static function myPow()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count == 0) return self::setError(array('pow', 'noParam'));
        if($count == 2)
        {
            $value = pow($params[0], $params[1]);
            if(is_nan($value)) return self::setError(array('pow', 'nanResult', "pow({$params[0]},{$params[1]})"));

            if(!is_finite($value)) return self::setError(array('pow', 'infResult', "pow({$params[0]},{$params[1]})"));

            return $value;
        }
        return self::setError(array('pow', 'need2Param'));
    }

    /**
     * MYSQRT function.
     *
     * @access public
     * @return int
     */
    public static function mySqrt()
    {
        $params = func_get_args();
        $count  = func_num_args();

        if($count == 0) return self::setError(array('sqrt', 'noParam'));

        if($count == 1)
        {
            $value = sqrt($params[0]);
            if(is_nan($value)) return self::setError(array('sqrt', 'nanResult', "sqrt({$params[0]})"));

            if(!is_finite($value)) return self::setError(array('sqrt', 'infResult', "sqrt({$params[0]})"));

            return $value;
        }

        return self::setError(array('sqrt', 'need1Param'));
    }

    public static function judge($condition, $right, $wrong)
    {
        $count = func_num_args();
        if($count != 3) return self::setError(array('sqrt', 'need1Param'));

        return $condition ? $right : $wrong;
    }

    public static function setError($error)
    {
        self::$hasError   = true;
        self::$errorMsg[] = $error;
        return 1;
    }
}
