<?php
namespace SabaiApps\Framework\Application;

/**
 * AbstractHttpApplication is an abstract class that provides a base implementation for HTTP applications.
 * It extends the AbstractApplication class and provides methods for handling script URLs, SSL status,
 * URL generation, and mod_rewrite formats.
 */
abstract class AbstractHttpApplication extends AbstractApplication {

	/**
	 * @var array An associative array of script URLs, where the key is the script name and the value is the URL.
	 */
	protected $_scriptUrls = array();
	/**
	 * @var string The name of the current script, defaulting to 'main'.
	 */
	protected $_currentScriptName = 'main';
	/**
	 * @var array An associative array of mod_rewrite formats for scripts, where the key is the script name and the value is the format string.
	 */
	protected $_modRewriteFormat = array();
	/**
	 * @var bool Indicates whether the application is running over SSL.
	 */
	protected $_isSsl = false;
	/**
	 * @var bool Indicates whether to add a trailing slash to URLs.
	 */
	protected $_addUrlTrailingSlash = false;

	/**
	 * Runs the application.
	 *
	 * @param IController $controller The controller to dispatch the request to.
	 * @param Context     $context The context of the request.
	 * @param string|null $route The route to dispatch the request to.
	 * @return AbstractHttpResponse
	 */
	public function run( IController $controller, Context $context, $route = null ): AbstractHttpResponse {
		return parent::run( $controller, $context, $route );
	}

	/**
	 * Sets the current script name.
	 *
	 * @param string $name The name of the script.
	 * @return AbstractHttpApplication
	 */
	public function setCurrentScriptName( $name ): AbstractHttpApplication {
		$this->_currentScriptName = $name;

		return $this;
	}

	/**
	 * Gets the current script name.
	 *
	 * @return string The name of the current script.
	 */
	public function getCurrentScriptName(): string {
		return $this->_currentScriptName;
	}

	/**
	 * Gets a URL for the given route and parameters.
	 *
	 * @param string $route The route to generate the URL for.
	 * @param array  $params The parameters to include in the URL.
	 * @param string $fragment The fragment to append to the URL.
	 * @param string $separator The separator to use between parameters.
	 * @return Url The generated URL.
	 */
	public function getUrl( $route = '', array $params = array(), $fragment = '', $separator = '&amp;' ): Url {
		return $this->createUrl(
			array(
				'route'     => $route,
				'params'    => $params,
				'fragment'  => $fragment,
				'separator' => $separator,
			)
		);
	}

	/**
	 * Gets the URL for the current script.
	 *
	 * @param string $name The name of the script URL to retrieve.
	 * @return string The URL for the script.
	 */
	public function getScriptUrl( $name = 'main' ): string {
		return $this->_scriptUrls[ $name ];
	}

	/**
	 * Sets the URL for the current script.
	 *
	 * @param string $url The URL to set.
	 * @param string $name The name of the script URL to set.
	 * @return AbstractHttpApplication
	 */
	public function setScriptUrl( $url, $name = 'main' ): AbstractHttpApplication {
		$this->_scriptUrls[ $name ] = $url;

		return $this;
	}

	/**
	 * Sets the mod_rewrite format for a specific script.
	 *
	 * @param string $modRewriteFormat The mod_rewrite format string.
	 * @param string $script The name of the script for which to set the format.
	 * @return AbstractHttpApplication
	 */
	public function setModRewriteFormat( $modRewriteFormat, $script ): AbstractHttpApplication {
		$this->_modRewriteFormat[ $script ] = $modRewriteFormat;

		return $this;
	}

	/**
	 * Sets whether the application is running over SSL.
	 *
	 * @param bool|null $flag If set to true, the application is considered to be running over SSL.
	 * If set to false, it is not. If null, the current state is returned.
	 * @return bool|AbstractHttpApplication Returns true if the application is running over SSL, false otherwise.
	 * If a flag is provided, returns the application instance for method chaining.
	 */
	public function isSsl( $flag = null ) {
		if ( isset( $flag ) ) {
			$this->_isSsl = $flag;
			return $this;
		}
		return $this->_isSsl;
	}

	/**
	 * Sets whether to add a trailing slash to URLs.
	 *
	 * @param bool|null $flag If set to true, a trailing slash will be added to URLs.
	 * If set to false, no trailing slash will be added. If null, the current state is returned.
	 * @return bool|AbstractHttpApplication Returns true if trailing slashes are added, false otherwise.
	 * If a flag is provided, returns the application instance for method chaining.
	 */
	public function addUrlTrailingSlash( $flag = null ) {
		if ( isset( $flag ) ) {
			$this->_addUrlTrailingSlash = $flag;
			return $this;
		}
		return $this->_addUrlTrailingSlash;
	}

	/**
	 * Creates a URL based on the provided options.
	 *
	 * @param array $options An associative array of options for creating the URL.
	 * - 'script_url': The base URL for the script.
	 * - 'route': The route to append to the script URL.
	 * - 'params': An associative array of parameters to include in the URL.
	 * - 'fragment': A fragment identifier to append to the URL.
	 * - 'script': The name of the script to use for the URL.
	 * - 'separator': The separator to use between parameters in the URL.
	 * - 'mod_rewrite': Whether to use mod_rewrite format for the URL.
	 * @return Url The generated URL object.
	 */
	public function createUrl( array $options = array() ): Url {
		$options += array(
			'script_url'  => null,
			'route'       => '',
			'params'      => array(),
			'fragment'    => '',
			'script'      => null,
			'separator'   => '&amp;',
			'mod_rewrite' => true,
		);
		if ( ! isset( $options['script_url'] ) ) {
			$route = rtrim( $options['route'], '/' );
			if ( $this->_addUrlTrailingSlash
				&& false === strpos( $route, '.' )
			) {
				$route .= '/';
			}
			$script_name = isset( $options['script'] ) && isset( $this->_scriptUrls[ $options['script'] ] ) ? $options['script'] : $this->_currentScriptName;
			if ( ! isset( $this->_modRewriteFormat[ $script_name ] ) || ! $options['mod_rewrite'] ) {
				$options['script_url'] = $this->getScriptUrl( $script_name );
				// Append route data to request parameters if not the root route
				if ( $route ) {
					$options['params'][ $this->getRouteParam() ] = $route;
				}
			} else {
				$options['script_url'] = sprintf( $this->_modRewriteFormat[ $script_name ], $route );
			}
			if ( $this->_isSsl && strpos( $options['script_url'], 'http:' ) === 0 ) {
				$options['script_url'] = 'https:' . substr( $options['script_url'], 5 );
			}
		}

		return new Url( $options['script_url'], $options['params'], $options['fragment'], $options['separator'], $options['route'] );
	}
}
