
#ifdef HAVE_CONFIG_H
#include "../../ext_config.h"
#endif

#include <php.h>
#include "../../php_ext.h"
#include "../../ext.h"

#include <Zend/zend_operators.h>
#include <Zend/zend_exceptions.h>
#include <Zend/zend_interfaces.h>

#include "kernel/main.h"
#include "kernel/object.h"
#include "kernel/fcall.h"
#include "kernel/memory.h"
#include "kernel/operators.h"
#include "kernel/exception.h"


/**
 * This file is part of the Phalcon Framework.
 *
 * (c) Phalcon Team <team@phalcon.io>
 *
 * For the full copyright and license information, please view the LICENSE.txt
 * file that was distributed with this source code.
 */
/**
 * Dispatching is the process of taking the request object, extracting the
 * module name, controller name, action name, and optional parameters contained
 * in it, and then instantiating a controller and calling an action of that
 * controller.
 *
 *```php
 * $di = new \Phalcon\Di\Di();
 *
 * $dispatcher = new \Phalcon\Mvc\Dispatcher();
 *
 * $dispatcher->setDI($di);
 *
 * $dispatcher->setControllerName("posts");
 * $dispatcher->setActionName("index");
 * $dispatcher->setParams([]);
 *
 * $controller = $dispatcher->dispatch();
 *```
 *
 * @phpstan-import-type dispatcher_forward from DispatcherTypes
 */
ZEPHIR_INIT_CLASS(Phalcon_Mvc_Dispatcher)
{
	ZEPHIR_REGISTER_CLASS_EX(Phalcon\\Mvc, Dispatcher, phalcon, mvc_dispatcher, phalcon_dispatcher_abstractdispatcher_ce, phalcon_mvc_dispatcher_method_entry, 0);

	{
		zval _zc0;
		ZVAL_STRINGL(&_zc0, "index", sizeof("index") - 1);
		zephir_declare_typed_property(phalcon_mvc_dispatcher_ce, SL("defaultAction"), &_zc0, ZEND_ACC_PROTECTED, MAY_BE_STRING, NULL, 0);
	}

	{
		zval _zc0;
		ZVAL_STRINGL(&_zc0, "index", sizeof("index") - 1);
		zephir_declare_typed_property(phalcon_mvc_dispatcher_ce, SL("defaultHandler"), &_zc0, ZEND_ACC_PROTECTED, MAY_BE_STRING, NULL, 0);
	}

	{
		zval _zc0;
		ZVAL_STRINGL(&_zc0, "Controller", sizeof("Controller") - 1);
		zephir_declare_typed_property(phalcon_mvc_dispatcher_ce, SL("handlerSuffix"), &_zc0, ZEND_ACC_PROTECTED, MAY_BE_STRING, NULL, 0);
	}

	zend_class_implements(phalcon_mvc_dispatcher_ce, 1, phalcon_mvc_dispatcherinterface_ce);
	return SUCCESS;
}

/**
 * Forwards the execution flow to another controller/action.
 *
 * ```php
 * use Phalcon\Events\Event;
 * use Phalcon\Mvc\Dispatcher;
 * use App\Backend\Bootstrap as Backend;
 * use App\Frontend\Bootstrap as Frontend;
 *
 * // Registering modules
 * $modules = [
 *     "frontend" => [
 *         "className" => Frontend::class,
 *         "path"      => __DIR__ . "/app/Modules/Frontend/Bootstrap.php",
 *         "metadata"  => [
 *             "controllersNamespace" => "App\Frontend\Controllers",
 *         ],
 *     ],
 *     "backend" => [
 *         "className" => Backend::class,
 *         "path"      => __DIR__ . "/app/Modules/Backend/Bootstrap.php",
 *         "metadata"  => [
 *             "controllersNamespace" => "App\Backend\Controllers",
 *         ],
 *     ],
 * ];
 *
 * $application->registerModules($modules);
 *
 * // Setting beforeForward listener
 * $eventsManager  = $di->getShared("eventsManager");
 *
 * $eventsManager->attach(
 *     "dispatch:beforeForward",
 *     function(Event $event, Dispatcher $dispatcher, array $forward) use ($modules) {
 *         $metadata = $modules[$forward["module"]]["metadata"];
 *
 *         $dispatcher->setModuleName(
 *             $forward["module"]
 *         );
 *
 *         $dispatcher->setNamespaceName(
 *             $metadata["controllersNamespace"]
 *         );
 *     }
 * );
 *
 * // Forward
 * $this->dispatcher->forward(
 *     [
 *         "module"     => "backend",
 *         "controller" => "posts",
 *         "action"     => "index",
 *     ]
 * );
 * ```
 *
 * @phpstan-param dispatcher_forward $forward
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, forward)
{
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval *forward_param = NULL, eventsManager, _0, _1$$3;
	zval forward;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&forward);
	ZVAL_UNDEF(&eventsManager);
	ZVAL_UNDEF(&_0);
	ZVAL_UNDEF(&_1$$3);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("eventsManager", 13, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		ZEPHIR_Z_PARAM_ARRAY(forward, forward_param)
	ZEND_PARSE_PARAMETERS_END();
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);
	zephir_fetch_params(1, 1, 0, &forward_param);
	zephir_get_arrval(&forward, forward_param);
	zephir_read_property_cached(&_0, this_ptr, _zephir_prop_0, 972, PH_NOISY_CC | PH_READONLY);
	ZEPHIR_CPY_WRT(&eventsManager, &_0);
	if (Z_TYPE_P(&eventsManager) == IS_OBJECT) {
		ZEPHIR_INIT_VAR(&_1$$3);
		ZVAL_STRING(&_1$$3, "dispatch:beforeForward");
		ZEPHIR_CALL_METHOD(NULL, &eventsManager, "fire", NULL, 0, &_1$$3, this_ptr, &forward);
		zephir_check_call_status();
	}
	ZEPHIR_CALL_PARENT(NULL, phalcon_mvc_dispatcher_ce, getThis(), "forward", NULL, 0, &forward);
	zephir_check_call_status();
	ZEPHIR_MM_RESTORE();
}

/**
 * Returns the active controller in the dispatcher
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, getActiveController)
{

	RETURN_MEMBER(getThis(), "activeHandler");
}

/**
 * Possible controller class name that will be located to dispatch the
 * request
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, getControllerClass)
{
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval *this_ptr = getThis();
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);

	ZEPHIR_RETURN_CALL_METHOD(this_ptr, "gethandlerclass", NULL, 0);
	zephir_check_call_status();
	RETURN_MM();
}

/**
 * Gets last dispatched controller name
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, getControllerName)
{

	RETURN_MEMBER_TYPED(getThis(), "handlerName", IS_STRING);
}

/**
 * Returns the latest dispatched controller
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, getLastController)
{

	RETURN_MEMBER(getThis(), "lastHandler");
}

/**
 * Gets previous dispatched controller name
 *
 * Note: This is an Mvc-specific alias for the base
 * getPreviousHandlerName().
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, getPreviousControllerName)
{

	RETURN_MEMBER_TYPED(getThis(), "previousHandlerName", IS_STRING);
}

/**
 * Sets the controller name to be dispatched
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, setControllerName)
{
	zval controllerName_zv;
	zend_string *controllerName = NULL;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&controllerName_zv);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("handlerName", 11, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STR(controllerName)
	ZEND_PARSE_PARAMETERS_END();
	ZVAL_STR(&controllerName_zv, controllerName);
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_0, 973, &controllerName_zv);
	RETURN_THISW();
}

/**
 * Sets the default controller suffix
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, setControllerSuffix)
{
	zval controllerSuffix_zv;
	zend_string *controllerSuffix = NULL;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&controllerSuffix_zv);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("handlerSuffix", 13, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STR(controllerSuffix)
	ZEND_PARSE_PARAMETERS_END();
	ZVAL_STR(&controllerSuffix_zv, controllerSuffix);
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_0, 974, &controllerSuffix_zv);
	RETURN_THISW();
}

/**
 * Sets the default controller name
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, setDefaultController)
{
	zval controllerName_zv;
	zend_string *controllerName = NULL;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&controllerName_zv);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("defaultHandler", 14, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_STR(controllerName)
	ZEND_PARSE_PARAMETERS_END();
	ZVAL_STR(&controllerName_zv, controllerName);
	zephir_update_property_zval_cached(this_ptr, _zephir_prop_0, 975, &controllerName_zv);
	RETURN_THISW();
}

/**
 * Handles a user exception
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, handleException)
{
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long ZEPHIR_LAST_CALL_STATUS;
	zval *exception, exception_sub, eventsManager, _0, _1$$3, _2$$3;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&exception_sub);
	ZVAL_UNDEF(&eventsManager);
	ZVAL_UNDEF(&_0);
	ZVAL_UNDEF(&_1$$3);
	ZVAL_UNDEF(&_2$$3);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("eventsManager", 13, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 1)
		Z_PARAM_OBJECT_OF_CLASS(exception, zend_ce_exception)
	ZEND_PARSE_PARAMETERS_END();
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);
	zephir_fetch_params(1, 1, 0, &exception);
	zephir_read_property_cached(&_0, this_ptr, _zephir_prop_0, 972, PH_NOISY_CC | PH_READONLY);
	ZEPHIR_CPY_WRT(&eventsManager, &_0);
	if (Z_TYPE_P(&eventsManager) == IS_OBJECT) {
		ZEPHIR_INIT_VAR(&_2$$3);
		ZVAL_STRING(&_2$$3, "dispatch:beforeException");
		ZEPHIR_CALL_METHOD(&_1$$3, &eventsManager, "fire", NULL, 0, &_2$$3, this_ptr, exception);
		zephir_check_call_status();
		if (ZEPHIR_IS_FALSE_IDENTICAL(&_1$$3)) {
			RETURN_MM_BOOL(0);
		}
	}
	ZEPHIR_MM_RESTORE();
}

/**
 * Throws an internal exception
 */
PHP_METHOD(Phalcon_Mvc_Dispatcher, throwDispatchException)
{
	zephir_method_globals *ZEPHIR_METHOD_GLOBALS_PTR = NULL;
	zend_long exceptionCode, ZEPHIR_LAST_CALL_STATUS;
	zval message_zv, *exceptionCode_param = NULL, container, response, exception, _0, _2, _3, _1$$3;
	zend_string *message = NULL;
	zval *this_ptr = getThis();

	ZVAL_UNDEF(&message_zv);
	ZVAL_UNDEF(&container);
	ZVAL_UNDEF(&response);
	ZVAL_UNDEF(&exception);
	ZVAL_UNDEF(&_0);
	ZVAL_UNDEF(&_2);
	ZVAL_UNDEF(&_3);
	ZVAL_UNDEF(&_1$$3);
	static zend_string *_zephir_prop_0 = NULL;
	if (UNEXPECTED(!_zephir_prop_0)) {
		_zephir_prop_0 = zend_string_init("container", 9, 1);
	}

	ZEND_PARSE_PARAMETERS_START(1, 2)
		Z_PARAM_STR(message)
		Z_PARAM_OPTIONAL
		Z_PARAM_LONG(exceptionCode)
	ZEND_PARSE_PARAMETERS_END();
	ZEPHIR_METHOD_GLOBALS_PTR = pecalloc(1, sizeof(zephir_method_globals), 0);
	zephir_memory_grow_stack(ZEPHIR_METHOD_GLOBALS_PTR, __func__);
	if (ZEND_NUM_ARGS() > 1) {
		exceptionCode_param = ZEND_CALL_ARG(execute_data, 2);
	}
	zephir_memory_observe(&message_zv);
	ZVAL_STR_COPY(&message_zv, message);
	if (!exceptionCode_param) {
		exceptionCode = 0;
	} else {
		}
	zephir_read_property_cached(&_0, this_ptr, _zephir_prop_0, 976, PH_NOISY_CC | PH_READONLY);
	ZEPHIR_CPY_WRT(&container, &_0);
	if (Z_TYPE_P(&container) == IS_NULL) {
		ZEPHIR_INIT_VAR(&_1$$3);
		object_init_ex(&_1$$3, phalcon_mvc_dispatcher_exceptions_responseserviceunavailable_ce);
		ZEPHIR_CALL_METHOD(NULL, &_1$$3, "__construct", NULL, 0);
		zephir_check_call_status();
		zephir_throw_exception_debug(&_1$$3, "phalcon/Mvc/Dispatcher.zep", 222);
		ZEPHIR_MM_RESTORE();
		return;
	}
	ZEPHIR_INIT_VAR(&_3);
	ZVAL_STRING(&_3, "response");
	ZEPHIR_CALL_METHOD(&_2, &container, "getshared", NULL, 0, &_3);
	zephir_check_call_status();
	ZEPHIR_CPY_WRT(&response, &_2);
	ZVAL_LONG(&_0, 404);
	ZEPHIR_INIT_NVAR(&_3);
	ZVAL_STRING(&_3, "Not Found");
	ZEPHIR_CALL_METHOD(NULL, &response, "setstatuscode", NULL, 0, &_0, &_3);
	zephir_check_call_status();
	ZEPHIR_INIT_VAR(&exception);
	object_init_ex(&exception, phalcon_mvc_dispatcher_exception_ce);
	ZVAL_LONG(&_0, exceptionCode);
	ZEPHIR_CALL_METHOD(NULL, &exception, "__construct", NULL, 9, &message_zv, &_0);
	zephir_check_call_status();
	ZEPHIR_CALL_METHOD(&_2, this_ptr, "handleexception", NULL, 0, &exception);
	zephir_check_call_status();
	if (ZEPHIR_IS_FALSE_IDENTICAL(&_2)) {
		RETURN_MM_BOOL(0);
	}
	zephir_throw_exception_debug(&exception, "phalcon/Mvc/Dispatcher.zep", 244);
	ZEPHIR_MM_RESTORE();
	return;
}

