<?php
/**
 * core.framework
 *
 * @category  Core
 * @package   Core_Application
 * @copyright Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license   proprietary
 */

/**
 * @category  Core
 * @package   Core_Application
 * @copyright Copyright (c) 2011. Burza d.o.o. (http://web.burza.hr/en/)
 * @license   proprietary
 */
class Core_Application_Plugin_ExceptionEvent extends Core_Application_Plugin_Abstract
{
    /**
     * @var boolean
     */
    protected $_debug = false;
    
    /**
     * @var boolean
     */
    protected $_throwErrorExceptions = false;
    
    /**
     * @var Core_Event_Dispatcher_Interface
     */
    protected $_eventDispatcher;

    /**
     * @return void
     */
    public function init()
    {
        // exception handler
        set_exception_handler(array($this, 'exceptionHandler'));

        // error handler
        set_error_handler(array($this, 'errorHandler'));

        // exit handler (to handle the "Fatal error" situation)
        register_shutdown_function(array($this, 'exitHandler'));
    }
    
    /**
     * @param boolean $debug
     * 
     * @return \Core_Application_Plugin_ExceptionEvent
     */
    public function setDebug($debug)
    {
        $this->_debug = (bool) $debug;
        return $this;
    }
    
    /**
     * @return boolean
     */
    public function isDebug()
    {
        return $this->_debug;
    }
    
    /**
     * @param boolean $throwErrorExceptions
     * 
     * @return \Core_Application_Plugin_ExceptionEvent
     */
    public function setThrowErrorExceptions($throwErrorExceptions)
    {
        $this->_throwErrorExceptions = (bool) $throwErrorExceptions;
        return $this;
    }
    
    /**
     * @return boolean
     */
    public function isThrowErrorExceptions()
    {
        return $this->_throwErrorExceptions;
    }

    /**
     * @param Core_Event_Dispatcher_Interface $eventDispatcher
     *
     * @return \Core_Application_Plugin_ExceptionEvent
     */
    public function setEventDispatcher(Core_Event_Dispatcher_Interface $eventDispatcher)
    {
        $this->_eventDispatcher = $eventDispatcher;
        return $this;
    }

    /**
     * @return Core_Event_Dispatcher_Interface
     */
    public function getEventDispatcher()
    {
        if (null === $this->_eventDispatcher) {
            $this->setEventDispatcher(Core_Application::get('EventDispatcher'));
        }
        return $this->_eventDispatcher;
    }

    /**
     * @param Exception $exception
     *
     * @return boolean
     */
    public function exceptionHandler(Exception $exception)
    {
        try {
            $application     = Core_Application::getInstance();
            $eventType       = $this->_getConstant($exception, 'EVENT_EXCEPTION', Core_Application::EVENT_EXCEPTION);
            $exceptionEvent  = new Core_Event($application, $eventType, compact('exception', 'application'));

            // dispatch this exception until catched
            $this->getEventDispatcher()->dispatchUntil($exceptionEvent);

            return $exceptionEvent->isProcessed();
        } catch (Exception $inxception) {
            // INXCEPTION: an exception was raised while handling an exception being raised
            $message = $inxception->getMessage();
            if ($this->isDebug()) {
                echo 'Exception "'. get_class($inxception)
                    . '" raised within exception handler handling "'
                    . get_class($exception) .'": '
                    . $message;
            }
            error_log($message);
            return true;
        }
    }

    /**
     * @param int     $errorType            Error type
     * @param string  $errorMessage         Error details
     * @param string  $errorFile            Error file
     * @param int     $errorLine            Error line
     * @param int     $errorContext         Error context
     * @param boolean $throwErrorExceptions Force throwing of ErrorException
     *
     * @return boolean
     * @throws ErrorException
     */
    public function errorHandler($errorType, $errorMessage, $errorFile, $errorLine, $errorContext = array(), $throwErrorExceptions = null)
    {
        $reportError = (bool) (error_reporting() & $errorType);
        if ($reportError) {
            $exception = new ErrorException($errorMessage, 0, $errorType, $errorFile, $errorLine);
            if (null === $throwErrorExceptions) {
                $throwErrorExceptions = $this->isThrowErrorExceptions();
            }

            if (true === $throwErrorExceptions) {
                throw $exception;
            } else {
                $this->exceptionHandler($exception);
            }
        }
        return $reportError;
    }

    /**
     * @return boolean
     */
    public function exitHandler()
    {
        $error = error_get_last();
        if (null !== $error) {
            // force $throwErrorExceptions=false as we cannot catch the exception after a fatal error
            return $this->errorHandler($error['type'], $error['message'], $error['file'], $error['line'], array(), false);
        }
        return false;
    }

    /**
     * @param object $object Object instance to pull constant from
     * @param string $name   Constant name
     * @param mixed  $value  Default
     *
     * @return mixed
     */
    protected function _getConstant($object, $name, $value)
    {
        $constant = get_class($object) .'::'. $name;
        if (defined($constant)) {
            $value = constant($constant);
        }
        return $value;
    }
}
