<?php

namespace Breyta\Test;

use PHPUnit\Framework\AssertionFailedError;
use PHPUnit\Framework\Test;
use PHPUnit\Framework\Warning;
use PHPUnit\TextUI\ResultPrinter;

class Printer extends ResultPrinter
{

    /**
     * Replacement symbols for test statuses.
     *
     * @var array
     */
    protected static $symbols = [
        'E' => "\e[31m!\e[0m", // red !
        'F' => "\e[31m\xe2\x9c\x96\e[0m", // red X
        'W' => "\e[33mW\e[0m", // yellow W
        'I' => "\e[33mI\e[0m", // yellow I
        'R' => "\e[33mR\e[0m", // yellow R
        'S' => "\e[36mS\e[0m", // cyan S
        '.' => "\e[32m\xe2\x9c\x94\e[0m", // green checkmark
    ];
    /**
     * Structure of the outputted test row.
     *
     * @var string
     */
    protected $testRow = '';

    /** @var string */
    protected $previousClassName = '';

    /**
     * {@inheritdoc}
     */
    protected function writeProgress(string $progress): void
    {
        if ($this->hasReplacementSymbol($progress)) {
            $progress = static::$symbols[$progress];
        }
        $this->write("  {$progress} {$this->testRow}" . PHP_EOL);
        $this->column++;
        $this->numTestsRun++;
    }
    /**
     * {@inheritdoc}
     */
    public function addError(Test $test, \Throwable $e, float $time): void
    {
        $this->buildTestRow(get_class($test), $test->getName(), $time, 'fg-red');
        parent::addError($test, $e, $time);
    }
    /**
     * {@inheritdoc}
     */
    public function addFailure(Test $test, AssertionFailedError $e, float $time): void
    {
        $this->buildTestRow(get_class($test), $test->getName(), $time, 'fg-red');
        parent::addFailure($test, $e, $time);
    }
    /**
     * {@inheritdoc}
     */
    public function addWarning(Test $test, Warning $e, float $time): void
    {
        $this->buildTestRow(get_class($test), $test->getName(), $time, 'fg-yellow');
        parent::addWarning($test, $e, $time);
    }
    /**
     * {@inheritdoc}
     */
    public function addIncompleteTest(Test $test, \Throwable $e, float $time): void
    {
        $this->buildTestRow(get_class($test), $test->getName(), $time, 'fg-yellow');
        parent::addIncompleteTest($test, $e, $time);
    }
    /**
     * {@inheritdoc}
     */
    public function addRiskyTest(Test $test, \Throwable $e, float $time): void
    {
        $this->buildTestRow(get_class($test), $test->getName(), $time, 'fg-yellow');
        parent::addRiskyTest($test, $e, $time);
    }
    /**
     * {@inheritdoc}
     */
    public function addSkippedTest(Test $test, \Throwable $e, float $time): void
    {
        $this->buildTestRow(get_class($test), $test->getName(), $time, 'fg-cyan');
        parent::addSkippedTest($test, $e, $time);
    }
    /**
     * {@inheritdoc}
     */
    public function endTest(Test $test, float $time): void
    {
        list($className, $methodName) = \PHPUnit\Util\Test::describe($test);
        $this->buildTestRow($className, $methodName, $time);
        parent::endTest($test, $time);
    }
    /**
     * {@inheritdoc}
     *
     * We'll handle the coloring ourselves.
     */
    protected function writeProgressWithColor(string $color, string $buffer): void
    {
        $this->writeProgress($buffer);
    }
    /**
     * Formats the results for a single test.
     *
     * @param $className
     * @param $methodName
     * @param $time
     * @param $color
     */
    protected function buildTestRow($className, $methodName, $time, $color = 'fg-white')
    {
        if ($className != $this->previousClassName) {
            $this->write(PHP_EOL . $this->formatWithColor('fg-magenta', $className) . PHP_EOL);
            $this->previousClassName = $className;
        }

        $this->testRow = sprintf(
            "(%s) %s",
            $this->formatTestDuration($time),
            $this->formatWithColor($color, "{$this->formatMethodName($methodName)}")
        );
    }
    /**
     * Makes the method name more readable.
     *
     * @param $method
     * @return mixed
     */
    protected function formatMethodName($method)
    {
        return ucfirst(
            $this->splitCamels(
                $this->splitSnakes($method)
            )
        );
    }
    /**
     * Replaces underscores in snake case with spaces.
     *
     * @param $name
     * @return string
     */
    protected function splitSnakes($name)
    {
        return str_replace('_', ' ', $name);
    }
    /**
     * Splits camel-cased names while handling caps sections properly.
     *
     * @param $name
     * @return string
     */
    protected function splitCamels($name)
    {
        return preg_replace('/(?<=[a-z])(?=[A-Z])|(?<=[A-Z])(?=[A-Z][a-z])/', ' $1', $name);
    }
    /**
     * Colours the duration if the test took longer than 500ms.
     *
     * @param $time
     * @return string
     */
    protected function formatTestDuration($time)
    {
        $testDurationInMs = round($time * 1000);
        $duration = $testDurationInMs > 500
            ? $this->formatWithColor('fg-yellow', $testDurationInMs)
            : $testDurationInMs;
        return sprintf('%s ms', $duration);
    }
    /**
     * Verifies if we have a replacement symbol available.
     *
     * @param $progress
     * @return bool
     */
    protected function hasReplacementSymbol($progress)
    {
        return in_array($progress, array_keys(static::$symbols));
    }
    /**
     * Checks if the class name is in format Class::method.
     *
     * @param $testName
     * @return bool
     */
    protected function hasCompoundClassName($testName)
    {
        return !empty($testName) && strpos($testName, '::') > -1;
    }
}