<?php
require_once dirname(__DIR__) . '/base/hypothesis.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/basic/describe.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/distribution/normal.php';
require_once dirname(dirname(__DIR__)) . '/zendasmath/distribution/stdnormal.php';

/**
 * normalTest
 */
class normalTest extends hypothesis
{
    public $data;

    public $n;

    public $mean;

    public $σ²;

    public $σ;

    public $roundMean;

    public $roundVar;

    public $roundSd;

    /**
     * constructor
     *
     * @param object $dataframe
     * @param array $data
     * @access public
     * @return void
     */
    public function __construct($dataframe, $data)
    {
        $this->data = $data;

        parent::__construct($dataframe);

        $this->n    = count($data);
        $this->mean = Describe::mean($data, false);
        $this->σ²   = Describe::variance($data, false);
        $this->σ    = Describe::standard($data, false);

        $this->roundMean = $this->roundDigit($this->mean, true);
        $this->roundVar  = $this->roundDigit($this->σ², true);
        $this->roundSd   = $this->roundDigit($this->σ, true);
    }

    /**
     * Ryan Joiner method.
     * https://support.minitab.com/zh-cn/minitab/21/help-and-how-to/statistics/basic-statistics/how-to/normality-test/methods-and-formulas/methods-and-formulas/#ryan-joiner
     *
     * @param bool $round
     * @access public
     * @return array
     */
    public function RyanJoiner($round = true)
    {
        $points = $this->data;
        sort($points);
        $ranks  = Describe::fractionalRanking($points);
        $n      = $this->n;
        $mean   = $this->mean;
        $σ²     = $this->σ²;
        $σ      = $this->σ;
        $normal = new standardNormal();

        $sum1 = 0;
        $sum2 = 0;
        foreach($points as $i => $point)
        {
            $exp = ($ranks[$i] - (3 / 8)) / ($n + 0.25);
            $b = $normal->inverse($exp);
            $sum1 += ($point - $mean) * $b;
            $sum2 += pow($b, 2);
        }
        $result = $sum1 / sqrt($σ² * ($n - 1) * $sum2);

        return $this->roundDigit($result, $round);
    }

    /**
     * Ryan Joiner p value method.
     * https://zhuanlan.zhihu.com/p/26539771
     *
     * @param float $r
     * @param int $n
     * @access public
     * @return string
     */
    public function RyanJoinerP($r, $n)
    {
        $sqrtn = sqrt($n);
        $pown  = pow($n, 2);

        $cv01  = 1.0071 - (0.1371 / $sqrtn) - (0.3682 / $n) + (0.7780 / $pown);
        $cv005 = 1.0063 - (0.1288 / $sqrtn) - (0.6118 / $n) + (1.3505 / $pown);
        $cv001 = 0.9963 - (0.0211 / $sqrtn) - (1.4106 / $n) + (3.1791 / $pown);
        if($r > $cv01) return '> 0.1';
        if($r > $cv005) return '0.1 ~ 0.05';
        if($r > $cv001) return '0.05 ~ 0.01';
        return '< 0.01';
    }

    /**
     * Kolmogorov Smirnov method.
     * https://support.minitab.com/zh-cn/minitab/21/help-and-how-to/statistics/basic-statistics/how-to/normality-test/methods-and-formulas/methods-and-formulas/#kolmogorov-smirnov
     *
     * @param bool $round
     * @access public
     * @return array
     */
    public function KolmogorovSmirnov($round = true)
    {
        $points = $this->data;
        sort($points);
        $n      = $this->n;
        $mean   = $this->mean;
        $σ      = $this->σ;
        $normal = new normal($mean, $σ);

        $D1 = array();
        foreach($points as $i => $point)
        {
            $z    = $normal->cdf($point);
            $D1[] = ($i + 1) / $n - $z;
            $D2[] = $z - ($i / $n);
        }

        $result = max(max($D1), max($D2));

        return $this->roundDigit($result, $round);
    }

    /**
     * Kolmogorov Smirnov p value method.
     *
     * @param float $d
     * @param int $n
     * @access public
     * @return string
     */
    public function KolmogorovSmirnovP($d, $n)
    {
        $sqrtn = sqrt($n);

        $p02  = 1.07 / $sqrtn;
        $p015 = 1.14 / $sqrtn;
        $p01  = 1.22 / $sqrtn;
        $p005 = 1.36 / $sqrtn;
        $p001 = 1.63 / $sqrtn;

        if($d < $p02)  return '> 0.2';
        if($d < $p015) return '> 0.15';
        if($d < $p01)  return '> 0.1';
        if($d < $p005) return '> 0.05';
        if($d < $p001) return '> 0.01';
        return '< 0.01';
    }
}
