<?php
require_once LIB_ROOT . '/dataframe/hypothesis/twoanova.php';
require_once LIB_ROOT . '/dataframe/checkdata.php';
require_once LIB_ROOT . '/dataframe/validatedata.php';
require_once LIB_ROOT . '/dataframe/plotly.php';

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

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

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

    /* Callback for basic statistic method 基本量统计计算回调函数 */
    public static function func($dataframe, $settings)
    {
        global $dasLang;
        $lang = $dasLang->twoAnova;

        Describe::setDecimal($dataframe->decimals);

        $dataType = $settings['dataType'];

        $sampleCol = $settings['sampleCol'];
        $groupCol  = $settings['groupCol'];

        $dataSample1 = $settings['dataSample1'];
        $dataSample2 = $settings['dataSample2'];

        $nSample1 = $settings['nSample1'];
        $nSample2 = $settings['nSample2'];

        $varSample1 = $settings['varSample1'];
        $varSample2 = $settings['varSample2'];

        $stdSample1 = $settings['stdSample1'];
        $stdSample2 = $settings['stdSample2'];

        $sampleRatio = $settings['sampleRatio'];
        $guessRatio  = $settings['guessRatio'];
        $cl          = $settings['cl'];
        $alternative = $settings['alternative'];

        if($dataType == 'oneCol')
        {
            $check = new checkData();
            $check->setData($dataframe->colData($sampleCol), $dataframe->columns[$sampleCol], self::$settings['sampleCol']['validate']);
            $check->setData($dataframe->colData($groupCol), $dataframe->columns[$groupCol], self::$settings['groupCol']['validate']);

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

            $sliceDF = $dataframe->sliceDataframe($dataframe, array($sampleCol, $groupCol), 'any');

            $sample = $sliceDF->col($sampleCol, 'number');
            $group  = $sliceDF->col($groupCol, 'any');

            $sampleName = $sliceDF->columns[$sampleCol];
            $groupName  = $sliceDF->columns[$groupCol];

            $sampleData = $sample->notNull();
            $groupData  = $group->notNull();

            $typeCount = array_count_values($groupData);

            $groupKeys = array_keys($typeCount);

            $sampleGroup = array();
            foreach($sampleData as $index => $value)
            {
                $key = $groupName . ' = ' . $groupData[$index] . ' ' . $sampleName;
                if(!isset($sampleGroup[$key])) $sampleGroup[$key] = array();
                $sampleGroup[$key][] = $value;
            }

            $sampleValues = array_values($sampleGroup);
            $sampleValue1 = $sampleValues[0];
            $sampleValue2 = $sampleValues[1];

            $sampleValueCount1 = array_unique($sampleValue1);
            $sampleValueCount2 = array_unique($sampleValue2);

            $groupKeys = array_keys($sampleGroup);
            $sampleName1 = $groupKeys[0];
            $sampleName2 = $groupKeys[1];

            $descResult = self::getDescResult($sliceDF, $sampleGroup, $alternative, $cl);
        }

        if($dataType == 'twoCol')
        {
            $check = new checkData();
            $check->setData($dataframe->colData($dataSample1), $dataframe->columns[$dataSample1], self::$settings['dataSample1']['validate']);
            $check->setData($dataframe->colData($dataSample2), $dataframe->columns[$dataSample2], self::$settings['dataSample2']['validate']);

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

            $sliceDF1 = $dataframe->sliceDataframe($dataframe, $dataSample1, 'any');
            $sliceDF2 = $dataframe->sliceDataframe($dataframe, $dataSample2, 'any');

            $sample1 = $sliceDF1->col($dataSample1, 'number');
            $sample2 = $sliceDF2->col($dataSample2, 'number');

            $sampleName1 = $dataframe->columns[$dataSample1];
            $sampleName2 = $dataframe->columns[$dataSample2];

            $sampleGroup = array();
            $sampleGroup[$sampleName1] = $sample1->notNull();
            $sampleGroup[$sampleName2] = $sample2->notNull();

            $descResult = self::getDescResult($dataframe, $sampleGroup, $alternative, $cl);
        }

        if($dataType == 'variance')
        {
            $sampleName1 = $lang->dataSample1;
            $sampleName2 = $lang->dataSample2;

            $param1     = new stdclass();
            $param1->N  = $nSample1;
            $param1->σ² = $varSample1;
            $param1->cl = $cl;
            $twoAnova1  = new TwoAnova($dataframe, $param1);

            $param2     = new stdclass();
            $param2->N  = $nSample2;
            $param2->σ² = $varSample2;
            $param2->cl = $cl;
            $twoAnova2  = new TwoAnova($dataframe, $param2);

            $descResult = array();
            $descResult[$sampleName1] = $twoAnova1->describe($alternative);
            $descResult[$sampleName2] = $twoAnova2->describe($alternative);
        }

        if($dataType == 'standard')
        {
            $sampleName1 = $lang->dataSample1;
            $sampleName2 = $lang->dataSample2;

            $param1     = new stdclass();
            $param1->N  = $nSample1;
            $param1->σ  = $stdSample1;
            $param1->cl = $cl;
            $twoAnova1  = new TwoAnova($dataframe, $param1);

            $param2     = new stdclass();
            $param2->N  = $nSample2;
            $param2->σ  = $stdSample2;
            $param2->cl = $cl;
            $twoAnova2  = new TwoAnova($dataframe, $param2);

            $descResult = array();
            $descResult[$sampleName1] = $twoAnova1->describe($alternative);
            $descResult[$sampleName2] = $twoAnova2->describe($alternative);
        }

        TwoAnova::$decimal = 4;
        if($sampleRatio == 'standard') $ratioTitle = $lang->stdCompare;
        if($sampleRatio == 'variance') $ratioTitle = $lang->varCompare;

        $twoAnova = new TwoAnova($dataframe, new stdclass());

        $fCI   = $twoAnova->fCI($sampleRatio, $alternative, $descResult, $cl);
        $fTest = $twoAnova->fTest($guessRatio, $alternative, $descResult, $cl);

        $sign = '';
        if($sampleRatio == 'variance') $sign = '²';

        $result = array();

        $result[] = self::buildInfoResult($sampleName1, $sampleName2);
        $result[] = self::buildDescResult($descResult, $sign, $cl);
        $result[] = self::buildRatioResult($fCI, $ratioTitle, $cl);
        $result[] = self::buildTestResult($fTest, $sign, $cl, $guessRatio);
        $result[] = self::buildSigmaChart($descResult, $sign, $cl);
        $result[] = self::buildChisqChart($fCI, $sign, $cl);

        if($dataType == 'oneCol') $result[] = self::buildBoxChart($sampleGroup, $sampleData);
        if($dataType == 'twoCol') $result[] = self::buildBoxChart($sampleGroup);

        return $result;
    }

    public static function getDescResult($dataframe, $sampleGroup, $alternative, $cl, $round = true)
    {
        $result = array();
        foreach($sampleGroup as $key => $data)
        {
            $param       = new stdclass();
            $param->data = $data;
            $param->cl   = $cl;
            $twoAnova    = new TwoAnova($dataframe, $param);

            $result[$key] = $twoAnova->describe($alternative, $round);
        }

        return $result;
    }

    public static function buildInfoResult($sample1, $sample2)
    {
        global $dasLang;

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

        $result->data = new stdclass();
        $result->data->type = 'html';
        $result->data->content = sprintf($dasLang->twoAnova->methodInfo, $sample1, $sample2);
        return $result;
    }

    public static function buildDescResult($data, $sign, $cl)
    {
        global $dasLang;
        $lang = $dasLang->twoAnova;

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

        $result->data = new stdclass();
        $result->data->columns = array($lang->variable, $lang->N, $lang->stdDiviation, $lang->variance, sprintf($lang->CI, $sign, $cl));
        $result->data->data    = array();

        foreach($data as $key => $value)
        {
            if($sign == '²')
            {
                $result->data->data[] = array($key, $value['n'], $value['σ'], $value['σ²'], sprintf("(%s, %s)", $value['ciVar'][0], $value['ciVar'][1]));
                continue;
            }
            $result->data->data[] = array($key, $value['n'], $value['σ'], $value['σ²'], sprintf("(%s, %s)", $value['ci'][0], $value['ci'][1]));
        }

        return $result;
    }

    public static function buildRatioResult($fCI, $title, $cl)
    {
        global $dasLang;
        $lang = $dasLang->twoAnova;

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

        $result->data = new stdclass();
        $result->data->columns = array($lang->estimateRatio, sprintf($lang->fCI, $cl));
        $result->data->data = array(array($fCI['ratio'], sprintf("(%s, %s)", $fCI['ci'][0], $fCI['ci'][1])));
        return $result;
    }

    public static function buildTestResult($fTest, $sign, $cl, $guessRatio)
    {
        global $dasLang;
        $lang = $dasLang->twoAnova;

        $result = new stdclass();
        $result->title = $lang->test;
        $result->type  = 'table';
        $result->desc  = '<table class="table table-auto table-bordered bordered table-condensed"><tbody>';
        $result->desc .= sprintf($lang->originHypo, $sign, $sign, $guessRatio);
        $result->desc .= sprintf($lang->alterHypo, $sign, $sign, $fTest['sign'], $guessRatio);
        $result->desc .= '</tbody></table>';
        $result->desc .= sprintf($lang->signLevel, 1 - $cl / 100);

        $result->data = new stdclass();
        $result->data->columns = array($lang->method, $lang->testStat, 'DF1', 'DF2', $lang->pValue);
        $result->data->data = array(array($fTest['method'], $fTest['test'], $fTest['DF1'], $fTest['DF2'], $fTest['p']));
        return $result;
    }

    public static function buildSigmaChart($desc, $sign, $cl)
    {
        global $dasLang;
        $σs        = array();
        $errorY    = array();
        $errorMin  = array();
        $errorYMin = array();
        foreach($desc as $value)
        {
            if(!$value['isComplete']) continue;

            $σ = $value['σ'];
            if(!is_numeric($σ)) continue;
            $low = $value['ci'][0];
            $up  = $value['ci'][1];
            if(!is_numeric($low)) $low = $σ;
            if(!is_numeric($up)) $up = $σ;

            $σs[]        = $σ;
            $errorY[]    = $up - $σ;
            $errorYMin[] = $σ - $low;
        }
        $errorPlog = new stdclass();
        $errorPlog->type       = 'scatter';
        $errorPlog->x          = array_keys($desc);
        $errorPlog->y          = $σs;
        /** see https://plotly.com/javascript/error-bars/ */
        $errorPlog->error_y    = array('type' => 'data', 'visible' => true, 'array' => $errorY, 'arrayminus' => $errorYMin);
        $result = new stdclass();
        $result->title = sprintf($dasLang->twoAnova->CI, $sign, $cl);
        $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;
    }

    public static function buildChisqChart($fCI, $sign, $cl)
    {
        global $dasLang;
        $ratio = $fCI['ratio'];
        $low   = $fCI['ci'][0];
        $up    = $fCI['ci'][1];

        if(!is_numeric($ratio) or !is_numeric($low) or !is_numeric($up))
        {
            $result = new stdclass();
            $result->title = sprintf($dasLang->twoAnova->ratioCI, $sign, $sign, $cl);
            $result->type  = 'text';
            $result->data  = new stdclass();

            $result->data->type    = 'html';
            $result->data->content = sprintf($dasLang->twoAnova->ratioCIError, $sign, $sign, $cl);

            return $result;
        }

        if(!is_numeric($low)) $low = $ratio;
        if(!is_numeric($up)) $up = $ratio;

        $errorPlog = new stdclass();
        $errorPlog->type       = 'scatter';
        $errorPlog->x          = array('σ₁/σ₂');
        $errorPlog->y          = array($ratio);
        /** see https://plotly.com/javascript/error-bars/ */
        $errorPlog->error_y    = array('type' => 'data', 'visible' => true, 'array' => array($up - $ratio), 'arrayminus' => array($ratio - $low));
        $result = new stdclass();
        $result->title = sprintf($dasLang->twoAnova->ratioCI, $sign, $sign, $cl);
        $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;
    }

    public static function buildBoxChart($sampleGroup, $sampleData = array())
    {
        global $dasLang;
        $boxes = array();
        foreach($sampleGroup as $name => $data)
        {
            $box = Describe::box($data, $name);

            $boxes[] = array('hoverinfo' => 'y', 'x' => array($name), 'q1'=> array($box->q1), 'q3' => array($box->q3), 'median' => array($box->q2), 'lowerfence' => array($box->lowerfence), 'upperfence' => array($box->upperfence), 'name' => $name, 'type' => 'box', 'marker' => array('symbol' => 'asterisk-open'));

            $unsortData = !empty($sampleData) ? $sampleData : $box->data;
            $outlier    = array('name' => $dasLang->box->outlier, 'x' => $box->outliers['x'], 'y' => $box->outliers['y'], 'mode' => 'markers', 'type' => 'scatter', 'marker' => array('symbol' => 'asterisk-open'));
            $outlier    = getHoverTemplateY($outlier, array('yname' => $box->name, 'custom' => getDisOrderCustom($box->outliers['y'], $unsortData)));

            $boxes[] = $outlier;
        }

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

        $result->data          = new stdclass();
        $result->data->type    = 'box';
        $result->data->data    = json_encode($boxes);
        $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->twoAnova;

        /* Basic settings */
        self::$settings['dataType']    = array('name' => 'dataType',    'label' => $lang->dataType);
        self::$settings['sampleCol']   = array('name' => 'sampleCol',   'label' => $lang->sampleCol,   'required' => true);
        self::$settings['groupCol']    = array('name' => 'groupCol',    'label' => $lang->groupCol,    'required' => true);
        self::$settings['dataSample1'] = array('name' => 'dataSample1', 'label' => $lang->dataSample1, 'required' => true);
        self::$settings['dataSample2'] = array('name' => 'dataSample2', 'label' => $lang->dataSample2, 'required' => true);
        self::$settings['nSample1']    = array('name' => 'nSample1',    'label' => $lang->nSample1,    'required' => true);
        self::$settings['nSample2']    = array('name' => 'nSample2',    'label' => $lang->nSample2,    'required' => true);
        self::$settings['varSample1']  = array('name' => 'varSample1',  'label' => $lang->varSample1,  'required' => true);
        self::$settings['varSample2']  = array('name' => 'varSample2',  'label' => $lang->varSample2,  'required' => true);
        self::$settings['stdSample1']  = array('name' => 'stdSample1',  'label' => $lang->stdSample1,  'required' => true);
        self::$settings['stdSample2']  = array('name' => 'stdSample2',  'label' => $lang->stdSample2,  'required' => true);
        self::$settings['sampleRatio'] = array('name' => 'sampleRatio', 'label' => $lang->sampleRatio);
        self::$settings['guessRatio']  = array('name' => 'guessRatio',  'label' => $lang->guessRatio,  'required' => true);
        self::$settings['cl']          = array('name' => 'cl',          'label' => $lang->cl,          'required' => true);
        self::$settings['alternative'] = array('name' => 'alternative', 'label' => $lang->alternative);

        /* Type settings */
        self::$settings['dataType']    += array('type' => 'enum', 'enumOptions' => $lang->dataTypeEnum, 'defaultValue' => 'oneCol');
        self::$settings['sampleCol']   += array('type' => 'column', 'columnType' => 'number');
        self::$settings['groupCol']    += array('type' => 'column', 'columnType' => 'number');
        self::$settings['dataSample1'] += array('type' => 'column', 'columnType' => 'number');
        self::$settings['dataSample2'] += array('type' => 'column', 'columnType' => 'number');
        self::$settings['nSample1']    += array('type' => 'number');
        self::$settings['nSample2']    += array('type' => 'number');
        self::$settings['varSample1']  += array('type' => 'number');
        self::$settings['varSample2']  += array('type' => 'number');
        self::$settings['stdSample1']  += array('type' => 'number');
        self::$settings['stdSample2']  += array('type' => 'number');
        self::$settings['sampleRatio'] += array('type' => 'enum', 'enumOptions' => $lang->sampleRatioEnum, 'defaultValue' => 'standard');
        self::$settings['guessRatio']  += array('type' => 'number', 'defaultValue' => 1);
        self::$settings['cl']          += array('type' => 'number', 'defaultValue' => 95);
        self::$settings['alternative'] += array('type' => 'enum', 'ui' => 'select', 'enumOptions' => $lang->alternativeEnum, 'defaultValue' => 'noEqual');

        /* Limit settings */
        self::$settings['sampleCol']   += array('conditions' => array('dataType' => 'oneCol'));
        self::$settings['groupCol']    += array('conditions' => array('dataType' => 'oneCol'), 'distinct' => 'sampleCol');
        self::$settings['dataSample1'] += array('conditions' => array('dataType' => 'twoCol'));
        self::$settings['dataSample2'] += array('conditions' => array('dataType' => 'twoCol'), 'distinct' => 'dataSample1');
        self::$settings['nSample1']    += array('conditions' => array(array('dataType' => 'variance'), array('dataType' => 'standard')), 'min' => 2, 'max' => 99999999);
        self::$settings['nSample2']    += array('conditions' => array(array('dataType' => 'variance'), array('dataType' => 'standard')), 'min' => 2, 'max' => 99999999);
        self::$settings['varSample1']  += array('conditions' => array('dataType' => 'variance'), 'min' => 0.000001, 'max' => 99999999);
        self::$settings['varSample2']  += array('conditions' => array('dataType' => 'variance'), 'min' => 0.000001, 'max' => 99999999);
        self::$settings['stdSample1']  += array('conditions' => array('dataType' => 'standard'), 'min' => 0.000001, 'max' => 99999999);
        self::$settings['stdSample2']  += array('conditions' => array('dataType' => 'standard'), 'min' => 0.000001, 'max' => 99999999);
        self::$settings['cl']          += array('min' => 0.000001, 'max' => 99.999999);

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

        /* More settings */
        self::$settings['sampleRatio'] += array('paramType' => 'more');
        self::$settings['guessRatio']  += array('paramType' => 'more');
        self::$settings['cl']          += array('paramType' => 'more');
        self::$settings['alternative'] += array('paramType' => 'more');

        /* Data validate */
        self::$settings['sampleCol']   += array('validate' => array('continuous', 'number', 'notCommon', 'rowEqual'));
        self::$settings['groupCol']    += array('validate' => array('continuous', 'columnUnique == 2', 'rowEqual'));
        self::$settings['dataSample1'] += array('validate' => array('continuous', 'number', 'notCommon'));
        self::$settings['dataSample2'] += array('validate' => array('continuous', 'number', 'notCommon'));

        return self::$settings;
    }

    /**
     * Get config.
     *
     * @access public
     * @return object
     */
    public static function getConfig()
    {
        global $dasLang;
        $lang = $dasLang->config;

        //return self::$config;
    }

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