<?php

/**
 * LICENSE: This Software is the property of Lifestyle Webconsulting GmbH (Aschaffenburg, Germany)
 * and is protected by copyright law - it is NOT Freeware.
 *
 * Any unauthorized use of this software without a valid license
 * is a violation of the license agreement and will be prosecuted by
 * civil and criminal law.
 *
 * @copyright  2018 Lifestyle Webconsulting GmbH
 * @version    $Id:$
 * @link       http://www.life-style.de
 */

namespace Sso\ApiBundle\Api\Debug;

use Symfony\Component\Debug\ExceptionHandler as BaseExceptionHandler;

/**
 * Class ExceptionHandler
 * @package Sso\ApiBundle\Api\Debug
 */
class ExceptionHandler extends BaseExceptionHandler
{

    /**
     * Output format: html|xml
     *
     * @var string
     */
    protected static $outputFormat = 'html';

    /**
     * Set output format
     *
     * @param string $format html|xml
     */
    public static function setOuputFormat($format)
    {
        self::$outputFormat = strtolower($format);
    }

    /**
     * Registers the exception handler.
     *
     * @param bool $debug
     *
     * @return ExceptionHandler The registered exception handler
     */
    public static function register($debug = true)
    {
        $handler = new static($debug);

        set_exception_handler(array($handler, 'handle'));

        return $handler;
    }

    public function handle(\Exception $exception)
    {
        if ($exception instanceof OutOfMemoryException) {
            $this->sendPhpResponse($exception);

            return;
        }

        // To be as fail-safe as possible, the exception is first handled
        // by our simple exception handler, then by the user exception handler.
        // The latter takes precedence and any output from the former is cancelled,
        // if and only if nothing bad happens in this handling path.

        $caughtOutput = 0;

        $this->caughtOutput = false;
        ob_start(array($this, 'catchOutput'));
        try {
            if (class_exists('Symfony\Component\HttpFoundation\Response')) {
                $response = $this->createResponse($exception);
                $response->sendHeaders();
                $response->sendContent();
            } else {
                $this->sendPhpResponse($exception);
            }
        } catch (\Exception $e) {
            // Ignore this $e exception, we have to deal with $exception
        }
        if (false === $this->caughtOutput) {
            ob_end_clean();
        }
        if (isset($this->caughtOutput[0])) {
            ob_start(array($this, 'cleanOutput'));
            echo $this->caughtOutput;
            $caughtOutput = ob_get_length();
        }
        $this->caughtOutput = 0;

        if (!empty($this->handler)) {
            try {
                call_user_func($this->handler, $exception);

                if ($caughtOutput) {
                    $this->caughtOutput = $caughtOutput;
                }
            } catch (\Exception $e) {
                if (!$caughtOutput) {
                    // All handlers failed. Let PHP handle that now.
                    throw $exception;
                }
            }
        }
    }

    /**
     * Gets the HTML content associated with the given exception.
     *
     * @param FlattenException $exception A FlattenException instance
     *
     * @return string The content as a string
     */
    public function getContent(FlattenException $exception)
    {
        switch ($exception->getStatusCode()) {
            case 404:
                $title = 'Sorry, the page you are looking for could not be found.';
                break;
            default:
                $title = 'Whoops, looks like something went wrong.';
        }

        $content = '';
        if ($this->debug) {
            try {
                $count = count($exception->getAllPrevious());
                $total = $count + 1;
                foreach ($exception->toArray() as $position => $e) {
                    $ind = $count - $position + 1;
                    $class = $this->abbrClass($e['class']);
                    $message = nl2br($e['message']);
                    $content .= sprintf(<<<EOF
                        <div class="block_exception clear_fix">
                            <h2><span>%d/%d</span> %s: %s</h2>
                        </div>
                        <div class="block">
                            <ol class="traces list_exception">

EOF
                        , $ind, $total, $class, $message);
                    foreach ($e['trace'] as $trace) {
                        $content .= '       <li>';
                        if ($trace['function']) {
                            $content .= sprintf('at %s%s%s(%s)', $this->abbrClass($trace['class']), $trace['type'],
                                $trace['function'], $this->formatArgs($trace['args']));
                        }
                        if (isset($trace['file']) && isset($trace['line'])) {
                            if ($linkFormat = ini_get('xdebug.file_link_format')) {
                                $link = str_replace(array('%f', '%l'), array($trace['file'], $trace['line']),
                                    $linkFormat);
                                $content .= sprintf(' in <a href="%s" title="Go to source">%s line %s</a>', $link,
                                    $trace['file'], $trace['line']);
                            } else {
                                $content .= sprintf(' in %s line %s', $trace['file'], $trace['line']);
                            }
                        }
                        $content .= "</li>\n";
                    }

                    $content .= "    </ol>\n</div>\n";
                }
            } catch (\Exception $e) {
                // something nasty happened and we cannot throw an exception anymore
                if ($this->debug) {
                    $title = sprintf('Exception thrown when handling an exception (%s: %s)', get_class($exception),
                        $exception->getMessage());
                } else {
                    $title = 'Whoops, looks like something went wrong.';
                }
            }
        }

        return <<<EOF
            <div id="sf-resetcontent" class="sf-reset">
                <h1>$title</h1>
                $content
            </div>
EOF;
    }

}
