<?php

/**
 * Class Rendertime
 *
 * This helper class is for uncomplicated profiling of executed code.
 * Just place anywhere in your code:
 * ```php
 * Rendertime::start('key');
 * // your code
 * Rendertime::end('key');
 * ```
 *
 * You can start multiple keys at the same time. The time is recorded for each key separately.
 *
 *  To receive the rendertime statistics, just call: `Rendertime::getRendertime()`.
 */
class Rendertime
{
    private $lastCall;
    private $stack = [];
    private $renderTimes = [];

    // singleton instance
    /** @var Rendertime */
    private static $instance = null;

    private function __construct(float $start = 0)
    {
        $this->lastCall = ($start > 0) ? $start : microtime(true);
        $this->stack[] = ['other', 0];
    }

    private function __clone()
    {
    }

    /** @return Rendertime */
    public static function getInstance($fStartTime = 0)
    {
        if (null === self::$instance) {
            self::$instance = new self($fStartTime);
        }
        return self::$instance;
    }

    public static function __callStatic($name, $arguments)
    {
        return call_user_func_array([self::getInstance(), $name], $arguments);
    }

    private function start(string $key)
    {
        $now = microtime(true);
        $past = $now - $this->lastCall;
        $currentItem = &$this->stack[count($this->stack) - 1];
        $currentItem[1] += $past;

        $this->stack[] = [$key, 0];
        $this->lastCall = microtime(true);
        return true;
    }

    private function end(string $key)
    {
        $now = microtime(true);
        if (!in_array($key, array_column(array_reverse($this->stack), 0))) {
            return false;
        }

        $past = $now - $this->lastCall;
        do {
            $this->recordTime($past);
            [$currentKey] = array_pop($this->stack);
            $past = 0;
        } while ($key != $currentKey);

        $this->lastCall = microtime(true);
        return true;
    }

    private function recordTime(float $past)
    {
        [$currentKey, $value] = $this->stack[count($this->stack) - 1];
        if (!isset($this->renderTimes[$currentKey])) {
            $this->renderTimes[$currentKey] = [];
        }
        $this->renderTimes[$currentKey][] = $value + $past;
    }

    private function getRendertime(string $key = null): array
    {
        $this->recordTime(microtime(true) - $this->lastCall);
        $this->lastCall = microtime(true);

        if (is_null($key)) {
            $renderTimes = array_map([$this, 'buildStatistics'], $this->renderTimes);
            $renderTimes['overall'] = $this->buildStatistics(array_merge(...array_values($this->renderTimes)));
            return $renderTimes;
        } else {
            return $this->buildStatistics($this->renderTimes[$key] ?? []);
        }
    }

    private function buildStatistics(array $times): array
    {
        if (empty($times)) {
            return ['count' => 0, 'min' => 0, 'max' => 0, 'avg' => 0, 'sum' => 0];
        }

        $count = count($times);
        $min = min($times);
        $max = max($times);
        $sum = array_sum($times);
        $avg = $sum / $count;
        return compact('count', 'min', 'max', 'avg', 'sum');
    }
}