<?php 
 
/* 
 * This file is part of the Symfony package. 
 * 
 * (c) Fabien Potencier <fabien@symfony.com> 
 * 
 * For the full copyright and license information, please view the LICENSE 
 * file that was distributed with this source code. 
 */ 
 
namespace Symfony\Component\Stopwatch; 
 
/** 
 * Represents an Event managed by Stopwatch. 
 * 
 * @author Fabien Potencier <fabien@symfony.com> 
 */ 
class StopwatchEvent 
{ 
    /** 
     * @var StopwatchPeriod[] 
     */ 
    private array $periods = []; 
 
    private float $origin; 
    private string $category; 
    private bool $morePrecision; 
 
    /** 
     * @var float[] 
     */ 
    private array $started = []; 
 
    private string $name; 
 
    /** 
     * @param float       $origin        The origin time in milliseconds 
     * @param string|null $category      The event category or null to use the default 
     * @param bool        $morePrecision If true, time is stored as float to keep the original microsecond precision 
     * @param string|null $name          The event name or null to define the name as default 
     * 
     * @throws \InvalidArgumentException When the raw time is not valid 
     */ 
    public function __construct(float $origin, string $category = null, bool $morePrecision = false, string $name = null) 
    { 
        $this->origin = $this->formatTime($origin); 
        $this->category = \is_string($category) ? $category : 'default'; 
        $this->morePrecision = $morePrecision; 
        $this->name = $name ?? 'default'; 
    } 
 
    /** 
     * Gets the category. 
     */ 
    public function getCategory(): string 
    { 
        return $this->category; 
    } 
 
    /** 
     * Gets the origin in milliseconds. 
     */ 
    public function getOrigin(): float 
    { 
        return $this->origin; 
    } 
 
    /** 
     * Starts a new event period. 
     * 
     * @return $this 
     */ 
    public function start(): static 
    { 
        $this->started[] = $this->getNow(); 
 
        return $this; 
    } 
 
    /** 
     * Stops the last started event period. 
     * 
     * @return $this 
     * 
     * @throws \LogicException When stop() is called without a matching call to start() 
     */ 
    public function stop(): static 
    { 
        if (!\count($this->started)) { 
            throw new \LogicException('stop() called but start() has not been called before.'); 
        } 
 
        $this->periods[] = new StopwatchPeriod(array_pop($this->started), $this->getNow(), $this->morePrecision); 
 
        return $this; 
    } 
 
    /** 
     * Checks if the event was started. 
     */ 
    public function isStarted(): bool 
    { 
        return !empty($this->started); 
    } 
 
    /** 
     * Stops the current period and then starts a new one. 
     * 
     * @return $this 
     */ 
    public function lap(): static 
    { 
        return $this->stop()->start(); 
    } 
 
    /** 
     * Stops all non already stopped periods. 
     */ 
    public function ensureStopped() 
    { 
        while (\count($this->started)) { 
            $this->stop(); 
        } 
    } 
 
    /** 
     * Gets all event periods. 
     * 
     * @return StopwatchPeriod[] 
     */ 
    public function getPeriods(): array 
    { 
        return $this->periods; 
    } 
 
    /** 
     * Gets the relative time of the start of the first period in milliseconds. 
     */ 
    public function getStartTime(): int|float 
    { 
        if (isset($this->periods[0])) { 
            return $this->periods[0]->getStartTime(); 
        } 
 
        if ($this->started) { 
            return $this->started[0]; 
        } 
 
        return 0; 
    } 
 
    /** 
     * Gets the relative time of the end of the last period in milliseconds. 
     */ 
    public function getEndTime(): int|float 
    { 
        $count = \count($this->periods); 
 
        return $count ? $this->periods[$count - 1]->getEndTime() : 0; 
    } 
 
    /** 
     * Gets the duration of the events in milliseconds (including all periods). 
     */ 
    public function getDuration(): int|float 
    { 
        $periods = $this->periods; 
        $left = \count($this->started); 
 
        for ($i = $left - 1; $i >= 0; --$i) { 
            $periods[] = new StopwatchPeriod($this->started[$i], $this->getNow(), $this->morePrecision); 
        } 
 
        $total = 0; 
        foreach ($periods as $period) { 
            $total += $period->getDuration(); 
        } 
 
        return $total; 
    } 
 
    /** 
     * Gets the max memory usage of all periods in bytes. 
     */ 
    public function getMemory(): int 
    { 
        $memory = 0; 
        foreach ($this->periods as $period) { 
            if ($period->getMemory() > $memory) { 
                $memory = $period->getMemory(); 
            } 
        } 
 
        return $memory; 
    } 
 
    /** 
     * Return the current time relative to origin in milliseconds. 
     */ 
    protected function getNow(): float 
    { 
        return $this->formatTime(microtime(true) * 1000 - $this->origin); 
    } 
 
    /** 
     * Formats a time. 
     * 
     * @throws \InvalidArgumentException When the raw time is not valid 
     */ 
    private function formatTime(float $time): float 
    { 
        return round($time, 1); 
    } 
 
    /** 
     * Gets the event name. 
     */ 
    public function getName(): string 
    { 
        return $this->name; 
    } 
 
    public function __toString(): string 
    { 
        return sprintf('%s/%s: %.2F MiB - %d ms', $this->getCategory(), $this->getName(), $this->getMemory() / 1024 / 1024, $this->getDuration()); 
    } 
}