<?php
require_once LIB_ROOT . '/dataframe/hypothesis/onewayanova.php';
require_once LIB_ROOT . '/das/methods/control/spc.php';
require_once LIB_ROOT . '/dataframe/checkdata.php';
require_once LIB_ROOT . '/dataframe/validatedata.php';

/* 单因子方差分析 */
class OneWayAnovaMethod
{
    /* Method name 分析方法内部名称 */
    public static $name = 'OneWayAnova';

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

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

    /* Callback for analysis method 计算回调函数 */
    public static function func($dataframe, $settings)
    {
        global $dasLang;

        $stack           = $settings['stack'];
        $unstackResponse = $settings['unstackResponse'];
        $factor          = $settings['factor'];
        $stackResponse   = $settings['stackResponse'];

        if($stack == 'false')
        {
            $check = new checkData();
            $check->setData($dataframe->colData($unstackResponse), $dataframe->columns[$unstackResponse], self::$settings['unstackResponse']['validate']);
            $check->setData($dataframe->colData($factor), $dataframe->columns[$factor], self::$settings['factor']['validate']);

            $checkRes = $check->check();
            if(is_string($checkRes)) return ValidateData::result($checkRes);
        }
        else
        {
            $check = new checkData();
            $check->setDataList($dataframe->colsData($stackResponse), $dataframe->colsName($stackResponse), self::$settings['stackResponse']['validate']);

            $checkRes = $check->check();
            if(is_string($checkRes)) return ValidateData::result($checkRes);
        }

        $factorName = self::getFactorName($dataframe, $settings);
        $settings   = OneWayAnovaMethod::getDataByStackType($dataframe, $settings);
        if(empty($settings)) return array();

        $stackRes = $settings['stackResponse'];
        $colsData = $dataframe->colsData($stackRes, 'number');

        $anova = new OneWayAnova($dataframe, $colsData);

        $results = array();
        $results[] = OneWayAnovaMethod::buildIntroResult();
        $results[] = OneWayAnovaMethod::buildFactorResult($dataframe, $settings, $factorName);
        $results[] = OneWayAnovaMethod::buildAnovaResult($anova, $factorName);
        $results[] = OneWayAnovaMethod::buildModelSummaryResult($anova);
        $results[] = OneWayAnovaMethod::buildMeansResult($dataframe, $anova);
        $results[] = OneWayAnovaMethod::buildCIChart($dataframe, $anova);

        return $results;
    }

    /**
     * Get data by stack type 根据堆叠和未堆叠获取数据
     * @return array
     */
    public static function getDataByStackType(&$dataframe, &$settings)
    {
        $data = array();
        if($settings['stack'] == 'false')
        {
            $dataframe = $dataframe->sliceDataframe($dataframe, $settings['factor'], 'any');
            $responses = $dataframe->col($settings['unstackResponse'])->data;
            $factors   = $dataframe->col($settings['factor'])->data;

            if($dataframe->col($settings['unstackResponse'])->numCount == 0 || count($responses) != count($factors)) return null;

            foreach($responses as $index => $value)
            {
                if(!isset($factors[$index])) continue;
                $factor = $factors[$index];

                if(!isset($data[$factor])) $data[$factor] = array();
                $data[$factor][] = $value;
            }

            if(count($data) == count($factors) || count($data) < 2) return null;

            $dataframe->data = array();
            $dataframe->columns = array();

            $newCols = array();
            foreach($data as $key => $group)
            {
                $newCols[$key] = $key;
                $dataframe = SPC::addData($dataframe, $key, $key, $group);
            }
            $settings['stackResponse'] = $newCols;
        }
        else
        {
            $responses = $dataframe->cols($settings['stackResponse'], 'number');
            foreach($responses as $response)
            {
                if($response->numCount < 1) return null;
            }
        }

        return $settings;
    }

    /**
     * Build method introduction result 方法结果
     * @return object
     */
    public static function buildIntroResult()
    {
        global $dasLang;

        $result = new stdclass();
        $result->title = $dasLang->onewayanova->method;
        $result->type  = 'text';

        $result->data  = new stdclass();
        $result->data->type     = 'html';
        $result->data->content  = $dasLang->onewayanova->methodIntro;

        return $result;
    }

    public static function getFactorName($dataframe, $settings)
    {
        global $dasLang;
        $stack = $settings['stack'];
        $factor = $settings['factor'];
        return $stack == 'true' ? $dasLang->onewayanova->factor : $dataframe->columns[$factor];
    }

    /**
     * Build factor info result 因子信息结果
     * @return object
     */
    public static function buildFactorResult($dataframe, $settings, $factorName)
    {
        global $dasLang;
        $lang = $dasLang->onewayanova;

        $stackRes = $settings['stackResponse'];
        $value = array();
        foreach($stackRes as $stack)
        {
            $value[] = $dataframe->columns[$stack];
        }

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

        $result->data          = new stdclass();
        $result->data->columns = array($lang->factor, $lang->levelCount, $lang->value);
        $result->data->data    = array();
        $result->data->data[]  = array($factorName, count($stackRes), implode(',', $value));

        return $result;
    }

    /**
     * Build anova result 方差分析结果
     * @return object
     */
    public static function buildAnovaResult($anova, $factorName)
    {
        global $dasLang;
        $lang = $dasLang->onewayanova;

        $tableRes = $anova->table();

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

        $result->data          = new stdclass();
        $result->data->columns = array($lang->source, $lang->df, $lang->adjSS, $lang->adjMS, $lang->fValue, $lang->pValue);
        $result->data->data    = array();
        $result->data->data[]  = array($factorName, $tableRes->factorDF, $tableRes->factorSS, $tableRes->factorMS, $tableRes->F, $tableRes->p);
        $result->data->data[]  = array($lang->error, $tableRes->errorDF, $tableRes->errorSS, $tableRes->errorMS, '');
        $result->data->data[]  = array($lang->total, $tableRes->totalDF, $tableRes->totalSS, '', '');

        return $result;
    }

    /**
     * Build model summary result 模型汇总结果
     * @return object
     */
    public static function buildModelSummaryResult($anova)
    {
        global $dasLang;
        $lang = $dasLang->onewayanova;

        $modelRes = $anova->model();

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

        $result->data          = new stdclass();
        $result->data->columns = array('S', 'R-Sq', "R-Sq ($lang->adj)", "R-Sq ($lang->pred)");
        $result->data->data    = array();
        $result->data->data[]  = array($modelRes->S, $modelRes->Rsq . '%', $modelRes->RsqAdj . '%', $modelRes->RsqPre . '%');

        return $result;
    }

    /**
     * Build means result 均值结果
     * @return object
     */
    public static function buildMeansResult($dataframe, $anova)
    {
        global $dasLang;
        $lang = $dasLang->onewayanova;

        $meanRes = $anova->meanTable();

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

        $result->data          = new stdclass();
        $result->data->columns = array($lang->factor, 'N', $lang->mean, $lang->stdDev, "95% $lang->ci");
        $result->data->data    = array();
        foreach($meanRes as $index => $res)
        {
            $factor = $dataframe->columns[$index];
            $result->data->data[] = array($factor, $res->n, $res->mean, $res->σ, "({$res->ci[0]}, {$res->ci[1]})");
        }
        return $result;
    }

    /**
     * Build 95% CI plot 95%置信区间图
     * @return object
     */
    public static function buildCIChart($dataframe, $anova)
    {
        global $dasLang;
        $lang = $dasLang->onewayanova;

        $meanRes = $anova->meanTable();

        $errorYArray      = array();
        $errorYArrayminus = array();
        $names            = array();
        $means            = array();
        foreach($meanRes as $index => $res)
        {
            $mean = $res->mean;
            $ci   = $res->ci;
            $col  = $index + 1;

            $errorYArrayminus[] = is_numeric($ci[0]) ? $mean - $ci[0] : 0;
            $errorYArray[]      = is_numeric($ci[1]) ? $ci[1] - $mean : 0;
            $names[]            = "C{$col}" . $dataframe->columns[$index];
            $means[]            = $mean;
        }

        asort($names);
        $means = array_values($means);
        $errorY = array();
        $errorYMin = array();
        $sortMeans = array();
        foreach($names as $index => $name)
        {
            $sortMeans[] = $means[$index];
            $errorY[] = $errorYArray[$index];
            $errorYMin[] = $errorYArrayminus[$index];
        }

        $errorPlog = new stdclass();
        $errorPlog->type       = 'scatter';
        $errorPlog->x          = array_values($names);
        $errorPlog->y          = $sortMeans;
        /** see https://plotly.com/javascript/error-bars/ */
        $errorPlog->error_y    = array('type' => 'data', 'visible' => true, 'array' => $errorY, 'arrayminus' => $errorYMin);

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

        $result->data          = new stdclass();
        $result->data->type    = 'scatter';
        $result->data->data    = json_encode(array($errorPlog));
        $result->data->layout  = array('showlegend' => false);
        return $result;
    }

    /**
     * Get settings.
     *
     * @param object $lang
     * @access public
     * @return object
     */
    public static function getSettings()
    {
        global $dasLang;
        $lang = $dasLang->onewayanova;

        /* Basic settings */
        self::$settings['stack']           = array('name' => 'stack',           'label' => $lang->stack);
        self::$settings['stackResponse']   = array('name' => 'stackResponse',   'label' => $lang->response, 'required' => true);
        self::$settings['unstackResponse'] = array('name' => 'unstackResponse', 'label' => $lang->response, 'required' => true);
        self::$settings['factor']          = array('name' => 'factor',          'label' => $lang->factor,   'required' => true);

        /* Type settings */
        self::$settings['stack']           += array('type' => 'enum', 'enumOptions' => $lang->stackEnum, 'defaultValue' => 'false');
        self::$settings['stackResponse']   += array('type' => 'list', 'listType' => 'column', 'columnType' => 'number');
        self::$settings['unstackResponse'] += array('type' => 'column', 'columnType' => 'number');
        self::$settings['factor']          += array('type' => 'column', 'columnType' => 'any');

        /* Limit settings */
        self::$settings['stackResponse']   += array('conditions' => array('stack' => 'true'), 'listLength' => 2);
        self::$settings['unstackResponse'] += array('conditions' => array('stack' => 'false'));
        self::$settings['factor']          += array('conditions' => array('stack' => 'false'), 'distinct' => 'unstackResponse');

        /* Col grid settings */
        self::$settings['stack']           += array('col' => 6);

        /* Data validate */
        self::$settings['stackResponse']   += array('validate' => array('continuous', 'number', 'N >= 2', 'notCommon'));
        self::$settings['unstackResponse'] += array('validate' => array('continuous', 'number', 'N >= 2', 'notCommon', 'rowEqual'));
        self::$settings['factor']          += array('validate' => array('continuous', 'number', 'columnUnique >=2', 'rowEqual'));

        /* More settings */

        return self::$settings;
    }

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

        return self::$config;
    }

    /**
     * Get Setting column type.
     *
     * @param string $index
     * @access public
     * @return string
     */
    public static function columnType($index)
    {
        return self::$settings[$index]['columnType'];
    }
}
