<?php
require_once LIB_ROOT . '/dataframe/checkdata.php';
require_once LIB_ROOT . '/dataframe/validatedata.php';
require_once LIB_ROOT . '/dataframe/montecarlo/montecarlo.php';
require_once LIB_ROOT . '/dataframe/montecarlo/mathcheck.php';

/* Monte Carlo method 基本量统计 */
class montecarloMethod
{
    /* Method name 分析方法内部名称 */
    public static $name = 'Monte Carlo';

    /* Method settings 方法设置参数定义 */
    public static $settings = array();

    /* Diagram config 图表配置项 */
    public static $config = array();

    public static function func($dataframe, $settings)
    {
        global $lang;
        global $dasLang;

        $params = self::buildParams($settings);

        $simulates     = array();
        $result        = array();
        $decisionParam = array();

        /* 检查公式中是否存在循环引用，并解析公式为纯假设变量和决策变量。*/
        foreach($params->forecast as $yname => $forecast)
        {
            list($check, $formula) = self::checkCycle($yname, $forecast['formula'], $params->forecast);
            if($check) return validateData::result($dasLang->monteCarlo->cycleError);
            $params->forecast[$yname]['pureFormula'] = $formula;
        }

        $exportForecast = $params->forecast[$params->exportForecast];

        /* 构建目标寻优参数，如果开启了决策。*/
        $optQuest = false;
        if($params->decisionStus)
        {
            $optQuest = new stdclass();
            if(!isset($exportForecast['target'])) return validateData::result($dasLang->monteCarlo->targetError);

            $optQuest->objective = $exportForecast;

            /* 处理决策相关参数。*/
            $decisions = array();
            foreach($params->decision as $var => $decision)
            {
                if(strpos($exportForecast['pureFormula'], $var) !== false) $decisions[$var] = $decision['values'];
            }

            $optQuest->decision = $decisions;

            /* 处理要求相关参数。*/
            $requests = array();
            foreach($params->request as $y => $request)
            {
                $requestFormula = $params->forecast[$y]['pureFormula'];
                $conditions = array();

                foreach($request as $item)
                {
                    foreach($decisions as $var => $decision)
                    {
                        if(strpos($requestFormula, $var) !== false)
                        {
                            $conditions[] = $item;
                            break;
                        }
                    }
                }

                if(count($conditions) == 0) continue;

                $requests[$y] = array('formula' => $requestFormula, 'params' => $conditions);
            }

            $optQuest->request = $requests;
        }

        $simulate = new monteCarloCalc($params->hypo, $exportForecast['pureFormula'], $params->runNumber, $optQuest);
        $simulate->yname = $params->exportForecast;

        if($simulate->isError)
        {
            if($simulate->errorCode == '10001')
            {
                list($errFunc, $errLang, $funcUse) = MathCheck::getError();
                if($funcUse) $dasLang->monteCarlo->$errLang = sprintf($dasLang->monteCarlo->$errLang, $funcUse);

                return validateData::result(sprintf($dasLang->monteCarlo->paramsError, $errFunc) . $dasLang->monteCarlo->$errLang);
            }
            if($simulate->errorCode == '10002') return validateData::result($dasLang->monteCarlo->formulaError);
        }

        $describe    = $simulate->describe();
        $exportYName = $params->forecast[$simulate->yname]['name'];

        $result[]   = self::buildDescResult($describe, $exportYName);
        $textResult = $dataframe->getTextResult(sprintf($dasLang->monteCarlo->chartResult, $exportYName));
        $textResult->name     = 'pdfcdf1';
        $textResult->children = 2;

        $result[] = $textResult;
        $result[] = self::buildPDFResult($describe);
        $result[] = self::buildCDFResult($describe);
        if($describe->optQuest)
        {
            $textResult = $dataframe->getTextResult(sprintf($dasLang->monteCarlo->bestTarget, $exportYName));
            $textResult->name     = 'optQuest1';
            $textResult->children = 2;
            if($describe->request)    $textResult->children += 1;
            if($params->displayPlans and $describe->request) $textResult->children += 1;
            $result[] = $textResult;

            if($describe->request) $result[] = self::buildRequestResult($describe, $params);
            $result[] = self::buildTargetResult($describe, $params);
            $result[] = self::buildBestResult($describe, $params);
            if($params->displayPlans and $describe->request) $result[] = self::buildPlansResult($describe, $params);
        }

        if($params->sensitivity)
        {
            if($describe->plan->fixed['export']) return validateData::result($dasLang->monteCarlo->sensitivityError);
            $textResult = $dataframe->getTextResult(sprintf($dasLang->monteCarlo->sensitivity->blockTitle, $exportYName));
            $textResult->name     = 'sensitivity';
            $textResult->children = 1;
            $result[] = $textResult;
            $result[] = self::buildSensitivityResult($simulate, $describe, $params);
        }
        return $result;
    }

    /**
     * Check formula exist cycle or not.
     *
     * @param array $settings
     * @access public
     * @return object
     */
    public static function checkCycle($varName, $formula, $vars)
    {
        if(strpos($formula, $varName) !== false) return array(true, null);

        preg_match_all('/y[1-9]/', $formula, $out);
        if(count($out[0]) == 0) return array(false, $formula);

        foreach($out[0] as $item)
        {
            $subFormula = $vars[$item]['formula'];
            $formula = str_replace($item, '(' . $subFormula . ')', $formula);
        }

        return self::checkCycle($varName, $formula, $vars);
    }

    /**
     * Build params form settings.
     *
     * @param array $settings
     * @access public
     * @return object
     */
    public static function buildParams($settings)
    {
        $varName         = $settings['varName'];
        $types           = $settings['type'];
        $dists           = $settings['distribution'];
        $decisionStus    = $settings['decisionStus'];
        $forecastName    = $settings['forecastName'];
        $forecastYname   = $settings['forecastYname'];
        $formulas        = $settings['formula'];
        $forecast        = $settings['forecastYname'];
        $runNumber       = $settings['runNumber'];
        $exportForecast  = $settings['exportY'];
        $sensitivity     = $settings['sensitivity'];
        $displayPlans    = $settings['displayPlans'];

        $params = new stdclass;
        $params->decisionStus    = $decisionStus === 'true' ? true : false;
        $params->runNumber       = $runNumber;
        $params->exportForecast  = $exportForecast;
        $params->hypo            = array();
        $params->sensitivity     = $sensitivity == 'true' ? true : false;
        $params->displayPlans    = $displayPlans == 'true' ? true : false;

        foreach($types as $var => $type)
        {
            $name = $varName[$var];
            $name = empty($name) ? $var : $name;
            $dist = $dists[$var];

            $arr = array('name' => $name, 'type' => $type);

            if($type == 'fixed') $arr['value'] = $dist;
            if($type == 'dist')
            {
                $method     = substr($dist, 0, strpos($dist, ','));
                $distParams = substr($dist, strpos($dist, ',') + 1);
                $distParams = explode(',', $distParams);
                foreach($distParams as $index => $distp)
                {
                    $distParams[$index] = (!is_numeric($distp) and empty($distp)) ? 'null' : "'$distp'";
                }
                $distParams = implode(',', $distParams);
                $dist       = "$method($distParams)";

                $arr['method'] = $dist;
            }

            $params->hypo[$var] = $arr;
        }

        $params->forecast = array();

        foreach($forecastYname as $index => $yname)
        {
            $name    = $forecastName[$index];
            $name    = empty($name) ? $yname : $name;
            $formula = htmlspecialchars_decode($formulas[$index]);

            $arr = array('name' => $name, 'formula' => $formula);

            if($params->decisionStus)
            {
                $targets      = isset($settings['target']) ? $settings['target'] : null;
                $targetYnames = isset($settings['targetYname']) ? $settings['targetYname'] : null;
                $stats        = isset($settings['statistic']) ? $settings['statistic'] : null;

                if(is_array($targetYnames) and in_array($yname, $targetYnames))
                {
                    $index = array_search($yname, $targetYnames);

                    $target = $targets[$index];
                    $stat   = $stats[$index];

                    $arr['target'] = $target;
                    $arr['stat']   = $stat;
                }
            }

            $params->forecast[$yname] = $arr;
        }

        $params->decision = array();

        if($params->decisionStus)
        {
            $decisionName  = $settings['decisionName'];
            $decisionXname = $settings['decisionXname'];
            $decisionVar   = $settings['decisionVar'];
            foreach($decisionXname as $index => $val)
            {
                $name = $decisionName[$index];
                $var  = explode(',', $decisionVar[$index]);

                $params->decision[$val] = array('name' => $name, 'type' => 'disperse', 'values' => $var);
            }
        }

        $params->request = array();
        if($params->decisionStus and isset($settings['reqVar']))
        {
            $reqVar       = $settings['reqVar'];
            $reqStat      = $settings['reqStat'];
            $reqCondition = $settings['reqCondition'];
            $reqValue     = $settings['reqValue'];

            foreach($reqVar as $index => $var)
            {
                if(!isset($params->request[$var])) $params->request[$var] = array();
                $params->request[$var][] = array('stat' => $reqStat[$index], 'condition' => $reqCondition[$index], 'value' => $reqValue[$index]);
            }
        }

        return $params;
    }

    /**
     * Build describe result.
     *
     * @param object $desc
     * @param int    $index
     * @access public
     * @return object
     */
    public static function buildDescResult($desc, $index)
    {
        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type = 'table';
        $result->name = 'basic' . $index;
        $result->title = sprintf($lang->basicDesc, $index);

        $result->data = array();
        $result->data['columns'] = array($lang->statistic, $lang->predicted);

        $plan = $desc->plan;

        $data = array();
        $data[] = array($lang->times   , $desc->times);
        $data[] = array($lang->mean    , $plan->mean());
        $data[] = array($lang->median  , $plan->median());
        $data[] = array($lang->variance, $plan->variance());
        $data[] = array($lang->standard, $plan->standard());
        $data[] = array($lang->skewness, $plan->skewness());
        $data[] = array($lang->kurtosis, $plan->kurtosis());
        $data[] = array($lang->max     , $plan->max());
        $data[] = array($lang->min     , $plan->min());

        $result->data['data'] = $data;

        return $result;
    }

    /**
     * Build request result.
     *
     * @param object $simulate
     * @access public
     * @return object
     */
    public static function buildRequestResult($describe, $params)
    {
        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type  = 'table';
        $result->title = '';
        if(!$describe->satisfyRequest) $result->desc  = "<span style='color: red;'>$lang->requestTip</span>";

        $result->data = array();
        $result->data['columns'] = array($lang->request, $lang->requestValue);

        $data = array();

        foreach($describe->requestResult as $requestResult)
        {
            list($key, $stat, $sign, $rightValue, $satisfied, $leftValue) = $requestResult;

            $stat         = $lang->statisticList[$stat];
            $forecastName = $params->forecast[$key]['name'];
            $requestText  = "$forecastName $stat $sign $rightValue";
            $valueText    = $satisfied ? $leftValue : "<span style='color: red;'>{$leftValue}</span>";
            $data[]       = array($requestText, $valueText);
        }

        $result->data['data'] = $data;

        return $result;
    }

    /**
     * buildTargetResult
     *
     * @param  object $describe
     * @param  object $params
     * @static
     * @access public
     * @return array
     */
    public static function buildTargetResult($describe, $params)
    {
        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type  = 'table';
        $result->title = '';

        $result->data = array();
        $result->data['columns'] = array($lang->decisionVar, $lang->plan);

        $data = array();

        $bestPlan = $describe->plan;

        foreach($bestPlan->plan as $decisionVar => $decision)
        {
            $decisionName = $params->decision[$decisionVar]['name'];
            $decisionName = empty($decisionName) ? $lang->decisionVar . $decisionVar : $decisionName;

            $data[] = array($decisionName, $decision);
        }

        $result->data['data'] = $data;

        return $result;
    }

    /**
     * buildBestResult
     *
     * @param  object $describe
     * @param  object $params
     * @static
     * @access public
     * @return array
     */
    public static function buildBestResult($describe, $params)
    {
        global $lang;
        global $dasLang;

        $objective = $describe->objective;

        $type = $objective['target'];
        $stat = $objective['stat'];

        $statValue = $describe->plan->$stat();

        $type = $lang->montecarlo->targetList[$type];
        $stat = $lang->montecarlo->statisticList[$stat];

        $result = new stdclass();
        $result->type  = 'text';
        $result->title = '';
        $result->data = array();
        $result->data['content'] = "{$dasLang->monteCarlo->bestResult} : $type  $stat = $statValue";

        return $result;
    }

    /**
     * Build probability density function result.
     *
     * @param array $describe
     * @access public
     * @return object
     */
    public static function buildPDFResult($describe)
    {
        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type  = 'chart';
        $result->title = '';
        //$result->id    = 'pdf';

        $result->data = array();
        $result->data['type'] = 'bar';

        $describe->plan->handleProbabilityDensityParams();
        $values = $describe->plan->probabilityDensity->pd;
        $data = array();
        $xData = array_keys($values);
        $color = array_fill(0, count($xData), '#1f77b4');
        $max = max($xData);
        $min = min($xData);
        $data[] = array('name' => $lang->pdf, 'x' => array_keys($values), 'y' => array_values($values), 'type' => 'bar', 'marker' => array('color' => $color));
        if($max == $min)
        {
            $max += 1;
            $min -= 1;
            $data[0]['width'] = 0.2;
        }

        $result->data['data'] = json_encode($data);

        $layout = array();
        $layout['title'] = array('text' => $lang->pdf, 'yanchor' => 'top', 'y' => 0.86);
        $layout['xaxis'] = array('title' => $lang->predValue);
        $layout['yaxis'] = array('title' => $lang->probability);

        $result->data['layout'] = $layout;
        $grid   = $dasLang->config->gridConfig;
        $title  = $dasLang->config->titleConfig;
        $legend = $dasLang->config->legendConfig;
        $legend['defaultValue'] = 'false';
        $result->data['config'] = array('grid' => $grid, 'title' => $title, 'legend' => $legend);

        $range = array();
        $range['from']          = round($min, 2);
        $range['to']            = round($max, 2);
        $range['pType']         = 'sum';
        $range['step']          = 0.01;
        $range['format']        = '%s';
        $range['width']         = '100%';
        $range['showScale']     = false;
        $range['showLabels']    = false;
        $range['isRange']       = true;
        $range['selectColor']   = '#1f77b4';
        $range['unselectColor'] = 'rgba(204,204,204,1)';
        $range['lang']          = $dasLang->monteCarlo->range;

        $result->data['range'] = $range;

        return $result;
    }

    /**
     * Build cumulative distribution function result.
     *
     * @param array $describe
     * @access public
     * @return object
     */
    public static function buildCDFResult($describe)
    {
        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type  = 'chart';
        $result->title = '';
        //$result->id    = 'cdf';

        $result->data = array();
        $result->data['type'] = 'bar';

        $describe->plan->handleCumulativeParams();
        $values = $describe->plan->cumulative->cd;
        $data = array();
        $xData = array_keys($values);
        $color = array_fill(0, count($xData), '#1f77b4');
        $max = max($xData);
        $min = min($xData);
        $data[] = array('name' => $lang->cdf, 'x' => array_keys($values), 'y' => array_values($values), 'type' => 'bar', 'marker' => array('color' => $color));
        if($max == $min)
        {
            $max += 1;
            $min -= 1;
            $data[0]['width'] = 0.2;
        }

        $result->data['data'] = json_encode($data);

        $layout = array();
        $layout['title'] = array('text' => $lang->cdf, 'yanchor' => 'top', 'y' => 0.86);
        $layout['xaxis'] = array('title' => $lang->predValue);
        $layout['yaxis'] = array('title' => $lang->probability);

        $result->data['layout'] = $layout;
        $grid   = $dasLang->config->gridConfig;
        $title  = $dasLang->config->titleConfig;
        $legend = $dasLang->config->legendConfig;
        $legend['defaultValue'] = 'false';
        $result->data['config'] = array('grid' => $grid, 'title' => $title, 'legend' => $legend);

        $range = array();
        $range['from']          = round($min, 2);
        $range['to']            = round($max, 2);
        $range['pType']         = 'range';
        $range['step']          = 0.01;
        $range['format']        = '%s';
        $range['width']         = '100%';
        $range['showScale']     = false;
        $range['showLabels']    = false;
        $range['isRange']       = true;
        $range['selectColor']   = '#1f77b4';
        $range['unselectColor'] = 'rgba(204,204,204,1)';
        $range['lang']          = $dasLang->monteCarlo->range;

        $result->data['range'] = $range;

        return $result;
    }

    /**
     * Build sensitivity anaylsis result.
     *
     * @param  object    $simulate
     * @param  object    $describe
     * @param  object    $params
     * @static
     * @access public
     * @return void
     */
    public static function buildSensitivityResult($simulate, $describe, $params)
    {
        $sens = $simulate->sensitivity($describe->plan->forecastValue);

        $y = array();
        $colors = array('#2a6aca', '#33766d', '#bd4848', '#664f99');

        $r      = array();
        $colorR = array();
        $maxR   = abs(current($sens)['r']);
        $p      = array();
        $colorP = array();
        $maxP   = abs(current($sens)['percent']);

        $count = 0;
        foreach($sens as $var => $value)
        {
            $rValue       = $value['r'];
            $percentValue = $value['percent'];

            $r[]       = $rValue;
            $colorR[]  = $colors[$count % 4];
            $maxR      = max($maxR, abs($rValue));
            $p[]       = $percentValue;
            $colorP[]  = $colors[$count % 4];
            $y[]       = $params->hypo[$var]['name'];
            $maxP      = max($maxP, abs($percentValue));

            $count += 1;
        }

        $yname = $params->forecast[$params->exportForecast]['name'];

        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type  = 'chart';
        $result->title = '';

        $result->data = array();
        $result->data['type'] = 'subplot';

        $common = array();
        $common['type']        = 'bar';
        $common['base']        = 0;
        $common['orientation'] = 'h';
        $common['hoverinfo']   = 'none';
        $common['width']       = count($sens) == 2 ? 0.2 : 0.5;

        $data = array();
        $data[] = array('x' => $p, 'y' => $y, 'text' => $p, 'texttemplate' => '%{x:.2f}%', 'marker' => array('color' => $colorP)) + $common;
        $data[] = array('x' => $r, 'y' => $y, 'text' => $r, 'marker' => array('color' => $colorR), 'xaxis' => 'x2', 'yaxis' => 'y2') + $common;

        $result->data['data'] = json_encode($data);

        $xName = sprintf($lang->sensitivity->chartTitle, $yname);
        $plot1Name = $lang->sensitivity->pTitle;
        $plot2Name = $lang->sensitivity->rTitle;

        $result->data['layout'] = array();
        $result->data['layout']['xaxis']  = array('title' => $xName, 'domain' => array(0, 0.48), 'anchor' => 'y1', 'range' => array(-$maxP, $maxP), 'ticksuffix' => '%');
        $result->data['layout']['yaxis']  = array('domain' => array(0.01, 0.9), 'anchor' => 'x1', 'automargin' => true);
        $result->data['layout']['xaxis2'] = array('title' => array('text' => $xName, 'font' => array('size' => 12)), 'domain' => array(0.52, 1), 'anchor' => 'y2', 'showline' => true, 'zerolinecolor' => '#ddd', 'showgrid' => false, 'range' => array(-$maxR, $maxR));
        $result->data['layout']['yaxis2'] = array('domain' => array(0.01, 0.9), 'anchor' => 'x2', 'showticklabels' => false, 'showline' => true);
        $result->data['layout']['annotations'] = array();
        $result->data['layout']['annotations'][] = self::getsubplottitle($plot1Name, 0.175);
        $result->data['layout']['annotations'][] = self::getsubplottitle($plot2Name, 0.825);

        $grid   = $dasLang->config->gridConfig;
        $legend = $dasLang->config->legendConfig;
        $legend['defaultValue'] = 'false';
        $result->data['config'] = array('grid' => $grid, 'legend' => $legend);

        return $result;
    }

    public static function buildPlansResult($describe, $params)
    {
        global $dasLang;
        $lang = $dasLang->monteCarlo;

        $result = new stdclass();
        $result->type  = 'table';
        $result->title = '';

        $result->data = array();
        $result->data['columns'] = array();

        $result->data['columns'][] = array('label' => $lang->decisionVar, 'colspan' => count($describe->decision));
        $result->data['columns'][] = array('label' => $lang->objective);
        $result->data['columns'][] = array('label' => $lang->request, 'colspan' => count($describe->requestResult));

        $firstRow = array();
        foreach($describe->decision as $decisionVar => $decision)
        {
            $decisionName = $params->decision[$decisionVar]['name'];
            $decisionName = empty($decisionName) ? $decisionVar : $decisionName;
            $firstRow[] = $decisionName;
        }
        $firstRow[] = $lang->objective;

        foreach($describe->requestResult as $requestResult)
        {
            list($key, $stat, $sign, $rightValue, $satisfied, $leftValue) = $requestResult;

            $stat         = $lang->statisticList[$stat];
            $forecastName = $params->forecast[$key]['name'];
            $requestText  = "$forecastName $stat $sign $rightValue";
            $firstRow[]   = $requestText;
        }

        $records = array();
        $y = $params->exportForecast;
        $targetStat = $params->forecast[$y]['stat'];
        $targetType = $params->forecast[$y]['target'];

        foreach($describe->planList as $plan)
        {
            $satisfies = 0;
            foreach($plan->requestResult as $requestResult)
            {
                list($key, $stat, $sign, $rightValue, $satisfied, $leftValue) = $requestResult;
                $satisfies += $satisfied ? 1 : 0;
            }
            if(!isset($plan->basicStatistic->$targetStat)) continue;

            $records[] = array('plan' => $plan->plan, 'target' => $plan->basicStatistic->$targetStat, 'targetType' => $targetType, 'request' => $plan->requestResult, 'satisfies' => $satisfies);
        }

        usort($records, function($a, $b) {
            $cmp = strcmp($b['satisfies'], $a['satisfies']);
            if($cmp !== 0) return $cmp;

            $multiplier = 100000; // 倍数

            $int1 = intval($a['target'] * $multiplier);
            $int2 = intval($b['target'] * $multiplier);

            $targetType = $a['targetType'];

            if ($int1 == $int2) return 0;
            if($targetType == 'max') return $int1 > $int2 ? -1 : 1;
            return $int1 < $int2 ? -1 : 1;
        });

        $data = array($firstRow);

        foreach($records as $record)
        {
            $row = $record['plan'];
            $row[] = round($record['target'], 4);
            foreach($record['request'] as $requestResult)
            {
                list($key, $stat, $sign, $rightValue, $satisfied, $leftValue) = $requestResult;
                $row[] = $satisfied ? $leftValue : "<span style='color: red;'>{$leftValue}</span>";
            }
            $data[] = $row;
        }

        $result->data['data'] = $data;

        return $result;
    }

    /**
     * Get sub plot title config.
     *
     * @param  string $text
     * @param  int    $pos
     * @static
     * @access public
     * @return array
     */
    public static function getSubPlotTitle($text, $pos)
    {
        $annotation = array();
        $annotation['showarrow'] = false;
        $annotation['align']     = 'center';
        $annotation['x']         = $pos;
        $annotation['y']         = 1;
        $annotation['xref']      = 'paper';
        $annotation['yref']      = 'paper';
        $annotation['text']      = $text;
        $annotation['font']      = array('size' => 16);

        return $annotation;
    }

    /**
     * Get settings.
     *
     * @access public
     * @return object
     */
    public static function getSettings()
    {
        return self::$settings;
    }

    /**
     * Get config.
     *
     * @access public
     * @return object
     */
    public static function getConfig()
    {
        global $dasLang;
        $lang = $dasLang->config;
        self::$config['grid']   = $lang->gridConfig;

        return self::$config;
    }
}
