<?php

namespace Velis;

/**
 * Base exception class
 * @author Olek Procki <olo@velis.pl>
 */
class Exception extends \Exception
{
    /**
     * Additional debug info
     * @var mixed
     */
    private $debugInfo;

    /**
     * Exception constructor
     *
     * @param string|null $message
     * @param int $code
     * @param \Exception $previous
     * @param mixed $debugInfo
     */
    public function __construct($message = '', $code = 0, $previous = null, $debugInfo = null)
    {
        parent::__construct($message, $code, $previous);
        $this->debugInfo = $debugInfo;
    }

    /**
     * Returns additional debug info
     * @return mixed
     */
    public function getDebugInfo()
    {
        return $this->debugInfo;
    }

    /**
     * Raises application error (Exception in devel mode, warning on production)
     *
     * @param string $message
     * @param int $code
     * @param \Exception $prevException
     * @param mixed $debugInfo
     * @throws Exception
     */
    public static function raise($message, $code = 0, $prevException = null, $debugInfo = null)
    {
        try {
            throw new static(
                $message,
                $code,
                $prevException,
                $debugInfo
            );
        } catch (Exception $e) {
            if ($_SERVER['ON_DEV']) {
                throw $e;
            } else {
                trigger_error($e, E_USER_DEPRECATED);
            }
        }
    }


    /**
     * Outputs exception info
     *
     * @return string
     */
    public function output(): string
    {
        $eDetails = static::class . ': ' . $this->getMessage() . "\n";

        if ($this->getCode()) {
            $eDetails .= 'Code: ' . $this->getCode() . "\n\n";
        }
        if ($this->getDebugInfo()) {
            $eDetails .= "Debug info:\n" . print_r($this->getDebugInfo(), true) . "\n\n";
        }

        $eDetails .= "Stack trace:\n";
        $frameCounter = 0;

        foreach ($this->getTrace() as $trace) {
            $eDetails .= '#' . $frameCounter++ . ' ';

            if ($trace['file']) {
                $eDetails .= $trace['file'] . '(' . $trace['line'] . ')';
            } else {
                $eDetails .= '[internal function]';
            }

            $eDetails .= ': ';

            if (isset($trace['class'])) {
                $eDetails .= $trace['class'] . $trace['type'];
            }

            $eDetails .= $trace['function'] . "()\n";
        }

        $previous = $this;
        $previousCounter = 0;

        while ($previous = $previous->getPrevious()) {
            if ($previousCounter >= 5) {
                break;
            }

            $eDetails .= "\nCaused by:\n" . $previous;
            $previousCounter++;
        }

        return $eDetails;
    }


    /**
     * Returns string exception representation
     * @return string
     */
    public function __toString()
    {
        return $this->output();
    }
}
