<?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_EventHandler_Rest extends Core_Application_Plugin_EventHandler_Abstract
{
    /**
     * @var array
     */
    protected $_subscriptions = array(
        array(
            'event'    => Core_Dispatcher::EVENT_PREDISPATCH_EARLY,
            'method'   => 'contextHandler',
            'priority' => -10000,
        ),
        array(
            'event'    => Core_Dispatcher::EVENT_PREDISPATCH,
            'method'   => 'oauthTokenHandler',
            'priority' => -500,
        ),
        array(
            'event'    => Core_Dispatcher::EVENT_POSTDISPATCH,
            'method'   => 'postDispatchHandler',
            'priority' => 999,
            'filter'   => 'params.application.context == "rest"'
        ),
        array(
            'event'    => Core_Application::EVENT_EXCEPTION,
            'method'   => 'exceptionHandler',
            'priority' => -100,
            'filter'   => 'params.application.context == "rest"'
        ),
    );

    /**
     * @var string
     */
    protected $_filter = 'params.location.module == "api" && params.location.controller != "oauth"';

    /**
     * @var bool
     */
    protected $_injectIdentity = true;

    /**
     * @var Core_Response
     */
    protected $_response;

    /**
     * @var OAuth2_Server
     */
    protected $_oauth2server;

    /**
     * @var Core_User
     */
    protected $_user;

    /**
     * @return void
     * @throws RuntimeException
     */
    public function init()
    {
        for ($x = 0; $x < count($this->_subscriptions); $x++) {
            if (!isset($this->_subscriptions[$x]['filter'])) {
                $this->_subscriptions[$x]['filter'] = $this->getFilter();
            }
        }
        parent::init();
    }

    /**
     * @param string $filter
     *
     * @return \Core_Application_Plugin_EventHandler_Rest
     */
    public function setFilter($filter)
    {
        $this->_filter = $filter;
        return $this;
    }

    /**
     * @return string
     */
    public function getFilter()
    {
        return $this->_filter;
    }

    /**
     * @param bool $injectIdentity
     *
     * @return \Core_Application_Plugin_EventHandler_Rest
     */
    public function setInjectIdentity($injectIdentity)
    {
        $this->_injectIdentity = (bool) $injectIdentity;
        return $this;
    }

    /**
     * @return bool
     */
    public function isInjectIdentity()
    {
        return $this->_injectIdentity;
    }

    /**
     * @param Core_Response $response
     *
     * @return \Core_Application_Plugin_EventHandler_RequestValidator
     */
    public function setResponse(Core_Response $response)
    {
        $this->_response = $response;
        return $this;
    }

    /**
     * @return Core_Response
     */
    public function getResponse()
    {
        if (null === $this->_response) {
            $this->setResponse(Core_Application::get('Response'));
        }
        return $this->_response;
    }

    /**
     * @param OAuth2_Server $oauth2server
     *
     * @return \Core_Application_Plugin_EventHandler_Rest
     */
    public function setOauth2server(OAuth2_Server $oauth2server)
    {
        $this->_oauth2server = $oauth2server;
        return $this;
    }

    /**
     * @return OAuth2_Server
     */
    public function getOauth2server()
    {
        if (null === $this->_oauth2server) {
            $this->setOauth2server(Core_Application::get('Oauth2Server'));
        }
        return $this->_oauth2server;
    }

    /**
     * @param Core_User $user
     *
     * @return \Core_Application_Plugin_EventHandler_Rest
     */
    public function setUser(Core_User $user)
    {
        $this->_user = $user;
        return $this;
    }

    /**
     * @return Core_User
     */
    public function getUser()
    {
        if (null === $this->_user) {
            $this->setUser(Core_Application::get('User'));
        }
        return $this->_user;
    }

    /**
     * @param Core_Event_Interface $event Event to handle
     *
     * @return void
     */
    public function oauthTokenHandler(Core_Event_Interface $event)
    {
        // must have a valid access token
        $oauth2server   = $this->getOauth2server();
        $oauth2request  = OAuth2_Request::createFromGlobals();
        $oauth2response = new OAuth2_Response;

        if (false === $oauth2server->verifyResourceRequest($oauth2request, $oauth2response)) {
            // access token is not valid
            $event->setIsProcessed(true);
            $response = $this->getResponse();
            $response
                ->setType('application/json')
                ->setCode($oauth2response->getStatusCode())
                ->setHeaders($oauth2response->getHttpHeaders())
                ->setBody($oauth2response->getResponseBody());
            return $response;
        } elseif ($this->isInjectIdentity()) {
            $token = $oauth2server->getAccessTokenData($oauth2request, $oauth2response);
            if (isset($token['user_id'])) {
                // user ID attached to access token
                $id = $token['user_id'];
            } else {
                // user ID not attached to access token
                // explicitly here so not to start a session later on
                $id = false;
            }
            $this->getUser()
                ->setPersist(false)
                ->setIdentity($id);
        }
    }

    /**
     * @param Core_Event_Interface $event Ignored
     *
     * @return void
     */
    public function contextHandler(Core_Event_Interface $event)
    {
        Core_Application::getInstance()->setContext('rest');

        // CORS for our REST backend
        $response = $this->getResponse();
        $response->setHeader('Access-Control-Allow-Origin',  '*');
    }

    /**
     * @param Core_Event_Interface $event Ignored
     * @param mixed                $data  If is array, will auto-serialize response
     *
     * @return type
     */
    public function postDispatchHandler(Core_Event_Interface $event, $data = null)
    {
        if (is_array($data)) {
            $data = $this->_processOutputArray($data);
            $response = $this->getResponse();
            $response
                ->setHeader('Vary', 'Accept-Encoding,Accept-Language')
                ->setType('application/json')   // TODO: temporary
                ->setFormat('json')             // TODO: temporary
                ->setBody($data);
            return $response;
        }
        return $data;
    }

    /**
     * Change case of array keys to lower, transforming CamelCase to snake_case
     *
     * @param array $array
     *
     * @return array
     */
    protected function _processOutputArray($array)
    {
        $output = array();
        foreach ($array as $key => $value) {
            $key = strtolower(preg_replace('/([A-Z]+)/', '_\\1', lcfirst($key)));
            if (is_array($value)) {
                $output[$key] = $this->_processOutputArray($value);
            } else {
                $output[$key] = $value;
            }
        }

        return $output;
    }

    /**
     * @param Core_Event_Interface $event
     */
    public function exceptionHandler(Core_Event_Interface $event)
    {
        $event->setIsProcessed(true);
        $exception = $event->getParam('exception');
        $response  = $this->getResponse();
        $response->setFormat('json');
        switch (true) {
            case $exception instanceof Core_Validate_Exception:
                $code = Core_Response::STATUS_UNPROCESSABLE_ENTITY;
                $body = $exception->getErrors();
                $type = 'application/vnd.error.tree';
                break;

            case $exception instanceof LogicException:
                $code = Core_Response::STATUS_UNPROCESSABLE_ENTITY;
                $body = array(array('message' => $exception->getMessage()));
                $type = 'application/vnd.error';
                break;

            default:
                $code = $this->_getConstant($exception, 'STATUS_CODE', Core_Response::STATUS_INTERNAL_SERVER_ERROR);
                $body = array(array('message' => $exception->getMessage()));
                $type = 'application/vnd.error';
        }

        $response
            ->setCode($code)
            ->setType($type)
            ->setBody($body);
        echo $response;
        exit;
    }

    /**
     * @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;
    }
}
