<?php
require_once dirname(__DIR__) . '/base/hypothesis.php';
require_once dirname(dirname(__DIR__)) . '/das/methods/vendor/autoload.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/basic/describe.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/basic/combination.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/distribution/chisquare.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/distribution/f.php';

class eqvariance extends hypothesis
{
    public $sample; //array
    /* $sample->title array
     * $sample->N
     * $sample->mean
     * $sample->median
     * $sample->sd
     * $sample->data
     * $sample->variance
     */
    public $convert; // like $sample

    /* params */
    public $stack;

    public $factors;

    //public $determine;

    public $confidence;

    public $individualConfidence;

    public $lang;

    public $groupCount;

    /* F Bartlett Levene */
    public $F;

    public $Bartlett;

    public $Levene;

    public $hasError;

    public $dataframe;

    public function __construct($stack, $confidence, $lang)
    {
        $this->sample = array();
        $this->stack      = $stack;
        //$this->determine  = $determine;
        $this->confidence = $confidence;
        $this->lang       = $lang;
        $this->hasError   = false;
    }

    public function setDF($dataframe)
    {
        $this->dataframe = $dataframe;
    }

    public function setF()
    {
        $this->F = new stdclass();

        $first  = $this->sample[0];
        $second = $this->sample[1];
        $this->F->value = $first->sd ** 2 / $second->sd ** 2;

        $f      = new F($first->N - 1, $second->N - 1);
        $pValue = $f->cdf($this->F->value);

        if($first->sd < $second->sd)
        {
            $this->F->p = $pValue;
        }
        else
        {
            $this->F->p = 1 - $pValue;
        }
    }

    public function setBartlett()
    {
        $this->Bartlett = new stdclass();

        $N_Sum                  = 0;
        $N_multi_variance_Sum   = 0;
        $N_multi_lnvariance_Sum = 0;
        $N_1_division_N_Sum     = 0;

        foreach($this->sample as $sample)
        {
            $vi                      = $sample->N - 1;
            $N_Sum                  += $vi;
            $N_multi_variance_Sum   += $vi * $sample->variance;
            $N_multi_lnvariance_Sum += $vi * log($sample->variance);
            $N_1_division_N_Sum     += 1 / $vi;
        }

        $this->Bartlett->value = ($N_Sum * log($N_multi_variance_Sum / $N_Sum) - $N_multi_lnvariance_Sum) / (($N_1_division_N_Sum - 1 / $N_Sum) / (3 * ($this->groupCount - 1)) + 1);

        $chisquare = new ChiSquare($this->groupCount - 1);
        $this->Bartlett->p = 1 - $chisquare->cdf($this->Bartlett->value);
    }

    public function setLevene()
    {
        $N             = 0;
        $allData       = array();
        $this->convert = array();
        $this->Levene  = new stdclass();

        foreach($this->sample as $sample)
        {
            $convert                     = new stdclass();
            $convertData                 = array();
            $convertmmpData = array(); /* mmp means minus_mean_pow */

            foreach($sample->data as $data)
            {
                $convertData[] = abs($data - $sample->median);
            }
            $N      += $sample->N;
            $allData = array_merge($allData, $convertData);

            $convert->N      = $sample->N;
            $convert->data   = $convertData;
            $convert->mean   = Describe::mean($convertData);

            foreach($convert->data as $data)
            {
                $convertmmpData[] = ($data - $convert->mean) ** 2;
            }
            $convert->convertmmpData = $convertmmpData;
            $convert->mmpSum         = Describe::sum($convertmmpData);

            $this->convert[] = $convert;
        }
        $convertMean = Describe::mean($allData);

        $value1 = 0;
        $value2 = 0;
        $freedom1 = $N - $this->groupCount;
        $freedom2 = $this->groupCount - 1;
        foreach($this->convert as $convert)
        {
            $value1 += ($convert->N * (($convert->mean - $convertMean) ** 2));
            $value2 += $convert->mmpSum;
        }
        $value1 *= $freedom1;
        $value2 *= $freedom2;

        $this->Levene->value = $value2 == 0 ? '-' : $value1 / $value2;
        $f = new F($freedom2, $freedom1);
        $this->Levene->p = $value2 == 0 ? '-' : 1 - $f->cdf($this->Levene->value);
    }

    public function setStack($response, $factors)
    {
        /* get $responseData, $factorsArr and $groupsArr */
        $this->factors = $factors;
        $factorsArr = array();
        $groupsArr  = array();
        foreach($factors as $factor)
        {
            $column = $this->dataframe->col($factor);

            $factorsArr[] = $column->data;
            $groupsArr[]  = array_keys(array_count_values(Describe::notNull($column->data)));
        }
        $responseData = $this->dataframe->col($response, 'number')->data;

        /* Use $groupArr get all combinations. */
        if(count($groupsArr) > 1)
        {
            $combination  = new Combination($groupsArr);
            $combinations = $combination->getCombination();
        }
        else
        {
            $combinations = array_map(fn($val) => array($val), $groupsArr[0]);
        }

        /* foreach all combinations, if $numCount > 0, add a stdclass to $this->sample. */
        foreach($combinations as $combination)
        {
            $titles = $combination;

            $data  = array();
            $numCount = 0;
            foreach($responseData as $key => $value)
            {
                $judge = true;

                if(!Describe::checkNotNull($value)) $judge = false;
                foreach($titles as $index => $title)
                {
                    if(!Describe::checkNotNull($factorsArr[$index][$key])) $judge = false;
                    if($factorsArr[$index][$key] != $title) $judge = false;
                }

                if($judge)
                {
                    $numCount ++;
                    $data[] = $value;
                }
            }

            if($numCount)
            {
                $result = new stdclass();
                $result->title       = $titles;
                $result->N           = $numCount;
                $result->mean        = Describe::mean($data, false);
                $result->median      = Describe::median($data, false);
                $result->sd          = $numCount == 1 ? '-' : Describe::standard($data, false);
                $result->variance    = $numCount == 1 ? '-' : $result->sd ** 2;
                $result->data        = $data;

                if(!is_numeric($result->sd) || is_nan($result->sd) || $result->sd == 0) $this->hasError = true;
                $this->sample[] = $result;
            }
        }

        /* 由于不一定是所有的组合都会有数据，所以要先遍历完了再对count($this->sample)获得真实的分组数量, 而不是直接count($combinations) */
        $this->groupCount           = count($this->sample);
        $this->individualConfidence = 1 - (1 - $this->confidence / 100) / ($this->groupCount);
        $α= 1 - $this->individualConfidence;

        foreach($this->sample as $key => $result)
        {
            $freedom      = $result->N - 1;
            $chisquare    = new ChiSquare($freedom);
            $chi_halfα   = $chisquare->inverse($α / 2 / $this->groupCount);
            $chi_1_halfα = $chisquare->inverse(1 - $α / 2 / $this->groupCount);

            $this->sample[$key]->upper = ($result->N == 1 || $chi_halfα == 0) ? '-' : sqrt(($freedom * $result->variance) / $chi_halfα);
            $this->sample[$key]->lower = ($result->N == 1 || $chi_1_halfα == 0) ? '-' : sqrt(($freedom * $result->variance) / $chi_1_halfα);

            /*if($this->determine == 'true')
            {
                $this->sample[$key]->upper = $result->N == 1 ? '-' : sqrt(($freedom * $result->variance) / $chi_halfα);
                $this->sample[$key]->lower = $result->N == 1 ? '-' : sqrt(($freedom * $result->variance) / $chi_1_halfα);
            }
            else
            {
                // TODO 非正态分布 upper和lower算的不对
                $this->sample[$key]->upper = '-';
                $this->sample[$key]->lower = '-';
            }
            */
        }

        return $this->checkGroup();
    }

    public function setNotStack($groupedResponse)
    {
        $this->groupCount           = count($groupedResponse);
        $this->individualConfidence = 1 - (1 - $this->confidence / 100) / ($this->groupCount);
        $α= 1 - $this->individualConfidence;
        foreach($groupedResponse as $response)
        {
            $column   = $this->dataframe->col($response, 'number');
            $data     = $column->notNull();
            $numCount = count($data);
            $freedom  = $numCount - 1;

            $chisquare = new ChiSquare($freedom);
            $chi_halfα   = $chisquare->inverse($α/ 2);
            $chi_1_halfα = $chisquare->inverse(1 - $α/ 2);

            $title = array();
            $title[] = $this->dataframe->columns[$response];

            $result = new stdclass();
            $result->title        = $title;
            $result->N            = $numCount;
            $result->mean         = Describe::mean($data, false);
            $result->median       = Describe::median($data, false);
            $result->sd           = $numCount == 1 ? '-' : Describe::standard($data, false);
            $result->variance     = $numCount == 1 ? '-' : $result->sd ** 2;
            $result->data = $data;

            $result->upper = $numCount == 1 ? '-' : sqrt(($freedom * $result->variance) / $chi_halfα);
            $result->lower = $numCount == 1 ? '-' : sqrt(($freedom * $result->variance) / $chi_1_halfα);

            /*if($this->determine == 'true')
            {
                $result->upper = $numCount == 1 ? '-' : sqrt(($freedom * $result->variance) / $chi_halfα);
                $result->lower = $numCount == 1 ? '-' : sqrt(($freedom * $result->variance) / $chi_1_halfα);
            }
            else
            {
                // TODO 非正态分布 upper和lower算的不对
                $this->sample[$key]->upper = '-';
                $this->sample[$key]->lower = '-';
            }
            */

            if(!is_numeric($result->sd) || is_nan($result->sd) || $result->sd == 0) $this->hasError = true;

            $this->sample[] = $result;
        }
    }

    /**
     * Get method result.
     *
     * @access public
     * @return object
     */
    public function getMethodResult()
    {
        $methodResult = new stdclass();
        $methodResult->type  = 'text';
        $methodResult->title = $this->lang->method;
        $methodResult->data = array();

        $methodDesc = sprintf($this->lang->methodDesc, 1 - $this->confidence / 100);
        /*if($this->determine == 'true')
        {
            if($this->groupCount == 2) $methodDesc .= sprintf($this->lang->useMethodDesc, 'F');
            else                       $methodDesc .= sprintf($this->lang->useMethodDesc, 'Bartlett');
        }
         */

        $methodResult->data['content'] = $methodDesc;

        return $methodResult;
    }

    /**
     * Get bonferroni result.
     *
     * @access public
     * @return object
     */
    public function getBonferroniResult()
    {
        $data = array();
        foreach($this->sample as $index => $sample)
        {
            foreach($sample->title as $title)
            {
                $data[$index][] = $title;
            }
            $data[$index][] = $sample->N;
            $data[$index][] = $this->dataframe->roundDigit($sample->sd);
            $data[$index][] = '(' . $this->dataframe->roundDigit($sample->lower) . ', ' . $this->dataframe->roundDigit($sample->upper) . ')';
        }

        $result = new stdclass();
        $result->type  = 'table';
        $result->title = sprintf($this->lang->bonferroniTitle, $this->confidence . '%');

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

        $columns = array();
        if($this->stack == 'false')
        {
            $columns[] = array('label' => $this->lang->sample, 'type' => 'text');
        }
        if($this->stack == 'true')
        {
            foreach($this->factors as $factor)
            {
                $columns[] = array('label' => $this->dataframe->columns[$factor], 'type' => 'text');
            }
        }
        $columns[] = array('label' => $this->lang->n, 'type' => 'number');
        $columns[] = array('label' => $this->lang->sd, 'type' => 'number');
        $columns[] = array('label' => $this->lang->CI, 'type' => 'number');
        $result->data['columns'] = $columns;

        return $result;
    }

    /**
     * Get test result.
     *
     * @access public
     * @return object
     */
    public function getTest()
    {
        $testData   = array();
        $testColumns = array();

        /*
        if($this->determine == 'true')
        {
            if($this->groupCount == 2)
            {
                $this->setF();

                $testData[0][] = $this->lang->f;
                $testData[0][] = $this->dataframe->roundDigit($this->F->value);
                $testData[0][] = $this->dataframe->roundDigit($this->F->p);
            }
            else
            {
                $this->setBartlett();
                $testData[0][] = $this->lang->bartlett;
                $testData[0][] = $this->dataframe->roundDigit($this->Bartlett->value);
                $testData[0][] = $this->dataframe->roundDigit($this->Bartlett->p);
            }
        }
        else
        {
            $this->setLevene();
            $testData[0][] = $this->lang->levene;
            $testData[0][] = $this->dataframe->roundDigit($this->Levene->value);
            $testData[0][] = $this->dataframe->roundDigit($this->Levene->p);
        }
         */

        if($this->groupCount == 2)
        {
            $this->setF();

            $testData[0][] = $this->lang->f;
            $testData[0][] = $this->dataframe->roundDigit($this->F->value);
            $testData[0][] = $this->dataframe->roundDigit($this->F->p);
        }
        else
        {
            $this->setBartlett();
            $testData[0][] = $this->lang->bartlett;
            $testData[0][] = $this->dataframe->roundDigit($this->Bartlett->value);
            $testData[0][] = $this->dataframe->roundDigit($this->Bartlett->p);
        }
        $this->setLevene();
        $testData[1][] = $this->lang->levene;
        $testData[1][] = $this->dataframe->roundDigit($this->Levene->value);
        $testData[1][] = $this->dataframe->roundDigit($this->Levene->p);

        $testResult = new stdclass();
        $testResult->type  = 'table';
        $testResult->title = $this->lang->test;

        $testResult->data = array();
        $testResult->data['data'] = $testData;

        $testColumns[] = array('label' => $this->lang->method, 'type' => 'text');
        $testColumns[] = array('label' => $this->lang->testStatistics, 'type' => 'text');
        $testColumns[] = array('label' => $this->lang->pValue, 'type' => 'number');

        $testResult->data['columns'] = $testColumns;

        return $testResult;
    }

    /* Throw an error if a certain group has only one data after grouping. */
    public function checkGroup()
    {
        if($this->groupCount == 1) return array('result' => 'fail', 'message' => $this->lang->errorGroupCount);

        $errorGroups = array();
        $titles      = array();
        foreach($this->factors as $factor)
        {
            $titles[] = $this->dataframe->columns[$factor];
        }

        foreach($this->sample as $sample)
        {
            if(count($sample->data) == 1)
            {
                $groupName = array();
                foreach($titles as $key => $title)
                {
                    $groupName[] = $title . ' = ' . $sample->title[$key];
                }
                $errorGroups[] = implode(',', $groupName);
            }
        }
        if($errorGroups)
        {
            $message = '';
            foreach($errorGroups as $group)
            {
                $message .= '(' . $group . ') ';
            }
            return array('result' => 'fail', 'message' => sprintf($this->lang->errorDataCount, count($errorGroups), $message));
        }
        else
        {
            return array('result' => 'success');
        }
    }

}
