<?php

namespace EesyPHP;

class App {

  /**
   * Options
   * @var array
   */
  protected static $options = array(
    'init_time' => null,
  );

  /**
   * Root directory path
   * @var string|null
   */
  protected static $root_directory_path = null;

  /**
   * Initialization
   * @param string|null $config_file Application configuration file path
   * @param array|null $options Application options (default: null)
   * @param string|null $root_directory_path Application root directory path (default: null)
   * @return void
   */
  public static function init($config_file, $options=null, $root_directory_path=null) {
    Config::register_extra_variable('init_time', hrtime(true));
    if (is_array($options)) self :: $options = $options;

    if (is_null($root_directory_path)) {
      $traces = debug_backtrace();
      $root_directory_path = realpath(dirname($traces[0]['file']).'/../');
    }
    self :: $root_directory_path = $root_directory_path;
    Config::register_extra_variable('root_directory_path', $root_directory_path);

    $config_file = Config::replace_variables($config_file);
    if ($config_file && !Config::load($config_file))
      Log::fatal('Fail to load configuration file (%s)', $config_file);

    // Set config default values
    App :: set_defaults(
      array(
        'overwrite_config_files' => array(),
        'upload_tmp_directory' => null,
        'upload_max_filesize' => null,
        'sentry.enabled' => true,
        'log.enabled' => true,
        'session.enabled' => true,
        'templates.enabled' => true,
        'url.enabled' => true,
        'mail.enabled' => true,
        'i18n.enabled' => true,
        'cli.enabled' => true,
      )
    );

    // Load overwrite configuration file
    foreach (self :: get('overwrite_config_files', null, 'array') as $file) {
      $file = Config::replace_variables($file);
      if (is_file($file)) Config::load($file, true);
    }

    if (self :: get('sentry.enabled', null, 'bool'))
      SentryIntegration :: init();
    $sentry_transaction = new SentryTransaction();
    $sentry_span = new SentrySpan('app.init', 'Application initialization');

    if (self :: get('log.enabled', null, 'bool'))
      Log::init();
    if (self :: get('session.enabled', null, 'bool'))
      Session::init();
    if (self :: get('templates.enabled', null, 'bool'))
      Tpl :: init();
    if (self :: get('url.enabled', null, 'bool')) {
      Url::init();
      Url :: add_url_handler(null, array('EesyPHP\\App', 'handle_homepage'));
    }
    if (Auth :: enabled()) {
      Auth :: init();
      Url :: add_url_handler('#^logout$#', array('EesyPHP\\App', 'handle_logout'), null, false);
    }
    if (self :: get('mail.enabled', null, 'bool'))
      Email :: init();
    if (self :: get('i18n.enabled', null, 'bool'))
      I18n::init();
    if (self :: get('cli.enabled', null, 'bool'))
      Cli::init();

    // Define common upload_tmp_dir & upload_max_filesize PHP ini
    if (self::isset('upload_tmp_directory'))
      Config :: ini_set('upload_tmp_dir', self::get('upload_tmp_directory', null, 'string'));
    if (self::isset('upload_max_filesize')) {
      Config :: ini_set('upload_max_filesize', self::get('upload_max_filesize', null, 'string'));
      // post_max_size must be larger than upload_max_filesize
      // See: https://www.php.net/manual/en/ini.core.php#ini.post-max-size
      Config :: ini_set('post_max_size', strval(self::get('upload_max_filesize', null, 'int') * 1.1));
    }

    $sentry_span->finish();
  }

  /**
   * Check if the application is initialized
   * @return bool
   */
  public static function initialized() {
    return !is_null(self :: $root_directory_path);
  }

  /**
   * Check if a specific configuration variable is set
   *
   * @param string $key The configuration variable key
   * @return bool
   **/
  public static function isset($key) {
    return Config::isset($key, self :: $options) || (Config::loaded() && Config::isset($key));
  }

  /**
   * Get a specific option value
   *
   * @param string $key The configuration variable key
   * @param mixed $default The default value to return if configuration variable
   *                       is not set (Default : null)
   * @param string $cast   The type of expected value. The configuration variable
   *                       value will be cast as this type. Could be : bool, int,
   *                       float or string. (Optional, default : raw value)
   * @param bool $split If true, $cast is 'array' and value retreived from configuration
   *                    is a string, split the value by comma (optional, default: true)
   * @return mixed The configuration variable value
   **/
  public static function get($key, $default=null, $cast=null, $split=true) {
    $default = self :: get_default($key, $default, $cast, $split);
    return Config::get(
      $key,
      Config::loaded()?Config::get($key, $default, $cast, $split):$default,
      $cast,
      $split,
      self :: $options,
      array(self :: class, 'get')
    );
  }

  /**
   * Get a specific option default value
   *
   * @param string $key The configuration variable key
   * @param mixed $default Default value provided by context
   *              (optional, priority if not nul, default : null)
   * @param string $cast   The type of expected value. The configuration variable
   *                       default value will be cast as this type. Could be : bool, int,
   *                       float or string. (Optional, default : raw value)
   * @param bool $split If true, $cast is 'array' and value retreived from configuration
   *                    is a string, split the value by comma (optional, default: true)
   * @return mixed The configuration variable default value
   **/
  public static function get_default($key, $default=null, $cast=null, $split=true) {
    if (!is_null($default))
      return $default;
    return Config::get(
      "default.$key",
      $default,
      $cast,
      $split,
      self :: $options
    );
  }

  /**
   * Get list of keys of a specific option
   *
   * @param string $key The configuration variable key
   *
   * @return array An array of the keys of a specific option
   **/
  public static function keys($key) {
    $value = self :: get($key);
    return (is_array($value)?array_keys($value):array());
  }

  /**
   * Set a specific option value
   *
   * @param string $key The configuration variable key
   * @param mixed $value The configuration variable value
   * @return boolean
   **/
  public static function set($key, $value) {
    if (Config::isset($key, self :: $options))
      return Config::set($key, $value, self :: $options);
    return Config::set($key, $value);
  }

  /**
   * Set a specific option default value
   *
   * @param string $key The configuration variable key
   * @param mixed $value The configuration variable default value
   * @return boolean
   **/
  public static function set_default($key, $value) {
    return Config::set("default.$key", $value, self :: $options);
  }

  /**
   * Set a specific options default value
   *
   * @param array<string,mixed> $values Associative array of configuration variables name and
   *                                    default values
   * @return boolean
   **/
  public static function set_defaults($values) {
    $error = false;
    foreach($values as $key => $value)
      if (!self :: set_default($key, $value))
        $error = true;
    return !$error;
  }

  /**
   * Retreive application root directory path
   * @return string|null
   */
  public static function root_directory_path() {
    return self :: $root_directory_path?self :: $root_directory_path:'.';
  }

  /**
   * Default homepage handler
   * @param UrlRequest $request
   * @return void
   */
  public static function handle_homepage($request) {
    if (Tpl::initialized())
      Tpl :: display("homepage.tpl", I18n::_("Hello world !"));
    else
      echo "<h1>".I18n::_("Hello world!")."</h1>";
  }

  /**
   * Default logout handler
   * @param UrlRequest $request
   * @return void
   */
  public static function handle_logout($request) {
    Auth::logout();
    if (Tpl::initialized())
      Tpl :: display("logout.tpl", I18n::_("Disconnected"));
    else
      echo "<h1>".I18n::_("You are now disconnected.")."</h1>";
  }

}
