<?php
/**
 * This class is for test of performance with PHPEF
 * @author Sebastian Peters
 */
class pw_benchmark_fibonacci_phpef
{
    private function fibonacci_recursive(int $n): int
    {
        if ($n <= 1) {
            return $n;
        }

        return $this->fibonacci_recursive($n - 1) + $this->fibonacci_recursive($n - 2);
    }

    private function fibonacci(int $n): int
    {
        if ($n <= 1) {
            return $n;
        }

        $n2 = 0;
        $n1 = 1;

        for ($i = 2; $i < $n; $i++) {
            $n2_ = $n2;
            $n2 = $n1;
            $n1 = ($n1 + $n2_);
        }

        return $n2 + $n1;
    }

    private function fibonacci_big(int $n): float
    {
        if ($n <= 1) {
            return $n;
        }

        $n2 = 0;
        $n1 = 1;

        for ($i = 2; $i < $n; $i++) {
            $n2_ = $n2;
            $n2 = $n1;
            $n1 = ($n1 + $n2_);
        }
        return $n2 + $n1;
    }

    private function test_fibonacci_recursive()
    {
        $data = [
            [0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5], [6, 8], [10, 55]//, [42 , 267914296]
        ];

        foreach ($data as $test) {
            $result = $this->fibonacci_recursive($test[0]);
            if ($result !== $test[1]) {
                throw new \UnexpectedValueException("Error Processing Request. N: {$test[0]}, got: {$result}, expected: {$test[1]}", 1);
            }
        }

        echo "Tests - Success." . PHP_EOL;
    }

    private function test_fibonacci()
    {
        $data = [
            [0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5], [6, 8], [10, 55]//, [42, 267914296]
        ];

        foreach ($data as $test) {
            $result = $this->fibonacci($test[0]);
            if ($result !== $test[1]) {
                throw new \UnexpectedValueException("Error Processing Request. N: {$test[0]}, got: {$result}, expected: {$float_test_value}", 1);
            }
        }

        echo "Tests - Success." . PHP_EOL;
    }

    private function test_fibonacci_big()
    {
        $data = [
            [0, 0], [1, 1], [2, 1], [3, 2], [4, 3], [5, 5], [6, 8], [10, 55]//, [42, 267914296]
        ];

        foreach ($data as $test) {
            $result = $this->fibonacci_big($test[0]);
            $float_test_value = (float)$test[1];
            if ($result !== $float_test_value) {
                throw new \UnexpectedValueException("Error Processing Request. N: {$test[0]}, got: {$result}, expected: {$float_test_value}", 1);
            }
        }

        echo "Tests - Success." . PHP_EOL;
    }

    /**
     * From https://gist.github.com/blongden/2352583
     */
    private function benchmark($x)
    {
        $start = $t = microtime(true);
        $total = $c = $loop = 0;
        while (true) {
            $this->$x();
            $c++;
            $now = microtime(true);
            if ($now - $t > 1) {
                $loop++;
                $total += $c;
                list($t, $c) = array(microtime(true), 0);
            }
            if ($now - $start > 2) {
                return round($total / $loop);
            }
        }
    }
    private function fibo_recur10()
    {
        $this->fibonacci_recursive(10);
    }
    private function fibo_recur20()
    {
        $this->fibonacci_recursive(20);
    }
    private function fibo10()
    {
        $this->fibonacci(10);
    }
    private function fibo20()
    {
        $this->fibonacci(20);
    }
    private function print_benchmark_int() :int
    {
        $this->test_fibonacci_recursive();
        $this->test_fibonacci();
        $this->test_fibonacci_big();

        $benchmark_10a = $this->benchmark("fibo_recur10");
        echo "Benchmark 10 run: ".number_format($benchmark_10a)."/sec or ".(1000/$benchmark_10a)."ms/op\n";

        $benchmark_20a = $this->benchmark("fibo_recur20");
        echo "Benchmark 20 run: ".number_format($benchmark_20a)."/sec or ".(1000/$benchmark_20a)."ms/op\n";

        $benchmark_10b = $this->benchmark("fibo10");
        echo "Benchmark 10 tuned run: ".number_format($benchmark_10b)."/sec or ".(1000/$benchmark_10b)."ms/op\n";

        $benchmark_20b = $this->benchmark("fibo20");
        echo "Benchmark 20 tuned run: ".number_format($benchmark_20b)."/sec or ".(1000/$benchmark_20b)."ms/op\n";

        $globalresult = (($benchmark_10a*20)+($benchmark_20a*2000)+($benchmark_10b)+($benchmark_20b))/4/2;
        $globalresult = floor($globalresult/1000);
        echo "Total Benchmark: ".number_format($globalresult)."k/sec\n";
        return($globalresult);
    }
    /**
     * Directly print benchmark results with JIT
     * @return ops/msec
     */
    static public function print_benchmark(): int
    {
        $fiboobj = new pw_benchmark_fibonacci_phpef();
        return($fiboobj->print_benchmark_int());
    }
}