<?php
defined('BASEPATH') or exit('No direct script access allowed');

class Module
{
	const DISCOVERY_CACHE_KEY = 'module_discovery_manifest_v1';
	const AVAILABLE_CACHE_KEY = 'module_available_manifest_v1';
	const CACHE_TTL = 300;

	private static $supports = [];

	private $ci;

	private $db_modules = [];

	private $modules = [];

	private $active_modules = [];

	private $version = [];
	private $new_version_data = [];

	public $css = [];
	public $js = [];

	public function __construct()
	{
		$this->ci = &get_instance();
		if (!isset($this->ci->cache)) {
			$this->ci->load->driver('cache', ['adapter' => 'file']);
		}
		$this->ci->load->helper('hooks');

		// $this->ci->db->query("CREATE TABLE IF NOT EXISTS `tbl_modules` (
		//     `module_id` int NOT NULL AUTO_INCREMENT,
		//     `module_name` varchar(55) NOT NULL,
		//     `installed_version` varchar(11) NOT NULL,
		//     `active` tinyint(1) NOT NULL,
		//     PRIMARY KEY (`module_id`)
		//   ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;");

		$this->ci->load->model('admin_model');
		$this->ci->load->helper('directory');
		if ($this->ci->admin_model->get_current_db_version() < 400) {
			return;
		}
		$this->initialize();
	}

	public function activate($name)
	{
		$module = $this->get($name);

		if (!$module) {
			return false;
		}
		if (!$this->module_exists_in_database($name)) {
			$this->ci->db->where('module_name', $name);
			$this->ci->db->insert('tbl_modules', ['module_name' => $name, 'installed_version' => $module['headers']['version']]);
		}
		include_once($module['init_file']);

		$instalation = $module['path'] . 'install.php';
		if (file_exists($instalation)) {
			include_once($instalation);
		}

		/**
		 * Before module is activated action
		 */
		do_action('before_module_activation', $module);

		do_action("activate_{$name}_module");



		$this->ci->db->where('module_name', $name);
		$this->ci->db->update('tbl_modules', ['active' => 1]);


		/**
		 * After module is activated action
		 */
		do_action('after_module_activation', $module);
		$this->clear_cache();

		return true;
	}

	/**
	 * Deactivate Module
	 * @param string $name Module Name [system_name]
	 * @return boolean
	 */

	public function deactivate($name)
	{
		$module = $this->get($name);
		if (!$module) {
			return false;
		}

		/**
		 * Before module is deactivated action
		 */
		do_action('before_module_deactivation', $module);
		do_action("deactivate_{$name}_module");

		$uninstallPath = $module['path'] . 'deactive.php';
		if (file_exists($uninstallPath)) {
			include_once($uninstallPath);
		}

		$this->ci->db->where('module_name', $name);
		$this->ci->db->update('tbl_modules', ['active' => 0]);

		/**
		 * After module is deactivated action
		 */
		do_action('after_module_deactivation', $module);
		$this->clear_cache();

		return true;
	}

	public function uninstall($name)
	{
		$module = $this->get($name);
		if (!$module) {
			return false;
		}
		if ($module['activated'] == 1) {
			return false;
		}
		// before uninstall action
		do_action('before_module_uninstall', $module);
		do_action("uninstall_{$name}_module");

		$this->ci->db->where('module_name', $name);
		$this->ci->db->delete('tbl_modules');

		$uninstallPath = $module['path'] . 'uninstall.php';
		if (file_exists($uninstallPath)) {
			include_once($uninstallPath);
		}

		if (is_dir($module['path'])) {
			delete_files($module['path'], true);
			rmdir($module['path']);
		}
		// after uninstall action
		do_action('after_module_uninstall', $module);
		$this->clear_cache();
		return true;
	}

	public function get_active_module()
	{
		return $this->active_modules;
	}

	public function is_active($name)
	{
		return array_key_exists($name, $this->get_active_module());
	}

	public function is_inactive($name)
	{
		return !$this->is_active($name);
	}

	public function is_installed($name)
	{
		if (!isset($this->modules[$name])) {
			return false;
		}
		return $this->modules[$name]['installed_version'] !== false;
	}

	public function is_minimum_version_requirement_met($name)
	{
		$module = $this->get($name);

		if (!isset($module['headers']['requires_at_least'])) {
			return true;
		}
		$appVersion = wordwrap(config_item('migration_version'), 1, '.', true);
		$moduleRequiresAppVersion = $module['headers']['requires_at_least'];
		if (version_compare($appVersion, $moduleRequiresAppVersion, '>=')) {
			return true;
		}

		return false;
	}

	public function upgrade_database($name)
	{
		$migration = new Module_Migration($name);
		if ($migration->to_latest() === false) {
			return $migration->error_string();
		}
		return true;
	}


	public function is_database_upgrade_required($name)
	{
		$module = $this->get($name);
		$moduleInstalledVersion = $module['installed_version'];
		if ($moduleInstalledVersion == false) {
			return false;
		}
		$moduleFilesVersion = $module['headers']['version'];
		if (version_compare($moduleInstalledVersion, $moduleFilesVersion) === 1) {
			return true;
		}

		if (version_compare($moduleFilesVersion, $moduleInstalledVersion) === 1) {
			return true;
		}
		return false;
	}

	public function latest_version($name)
	{
		$module = $this->get($name);
		if (!empty($module['headers']['item_id'])) {
			$module['item_id'] = $module['headers']['item_id'];
			$result = $this->ci->admin_model->get_update_info($module);
			$result = json_decode($result);
			return $result->latest_version;
		}
		return 0;
	}

	public function new_version_available($name)
	{
		$module = $this->get($name);
		$rdata = array();
		if (!empty($module['headers']['item_id'])) {
			$module['item_id'] = $module['headers']['item_id'];
			if (!empty($module['installed_version'])) {
				$version = str_replace(".", "", $module['installed_version']);
				$result = $this->ci->admin_model->get_update_info($module);
				$result = json_decode($result);
				if (!empty($result)) {
					$latest_version = $result->latest_version;
					if ($latest_version > $version) {
						$rdata['new_version'] = $result->latest_version;
					}
				}
				if (!empty($result->changelog)) {
					$rdata['changelog'] = $result->changelog;
					$rdata['changelog_url'] = $result->changelog_url;
				}
			}
		}
		return $rdata;
	}


	public function update_to_new_version($name)
	{
		$module = $this->get($name);
		$version_available = $this->auto_update->index($name);		
		if (!empty($version_available['new_version'])) {
		}


		// return $this->get_new_version_data($name);
	}




	/**
	 * Return the number of modules that requires database upgrade
	 * @return integer
	 */
	public function number_of_modules_that_require_database_upgrade()
	{
		$CI = &get_instance();
		$cacheKey = 'no-of-modules-require-database-upgrade';
		$total = $CI->app_object_cache->get($cacheKey);
		if ($total === false) {
			$total = 0;
			foreach ($this->modules as $module) {
				if ($this->is_database_upgrade_required($module['system_name'])) {
					$total++;
				}
			}
			$CI->app_object_cache->add($cacheKey, $total);
		}

		return $total;
	}

	/**
	 * Get all modules or specific module if module system name is passed
	 * This method returns all modules including active and inactive
	 * @param mixed $module
	 * @return mixed
	 */
	public function get($module = null)
	{
		if (!$module) {
			$modules = $this->modules;
			/* Sort modules by name */

			usort($modules, function ($a, $b) {
				return strcmp(strtolower($a['headers']['module_name']), strtolower($b['headers']['module_name']));
			});

			return $modules;
		}

		if (isset($this->modules[$module])) {
			return $this->modules[$module];
		}

		return null;
	}

	public function get_module_from_database($name)
	{
		if (isset($this->db_modules[$name])) {
			return $this->db_modules[$name];
		}
		return $this->get_module_info($name);
	}

	public function initialize()
	{
		// For caching
		$this->query_db_modules();
		foreach ($this->get_discovered_modules() as $module) {
			$name = $module['name'];
			if (!isset($this->modules[$name])) {
				$this->modules[$name]['system_name'] = $name;
				$this->modules[$name]['headers'] = $module['headers'];
				$this->modules[$name]['init_file'] = $module['init_file'];
				$this->modules[$name]['path'] = $module['path'];

				$get_module = $this->get_module_from_database($name);
				$this->modules[$name]['activated'] = 0;
				if (!empty($get_module)) {
					if ($get_module->active == 1) {
						$this->modules[$name]['activated'] = 1;
						$this->active_modules[$name] = $this->modules[$name];
					}
				}
				$this->modules[$name]['installed_version'] = $get_module ? $get_module->installed_version : false;
			}
		}
	}

	public function add_supports_feature($module_name, $feature)
	{
		if (!isset(self::$supports[$module_name])) {
			self::$supports[$module_name] = [];
		}

		if (in_array($feature, self::$supports[$module_name])) {
			return;
		}

		self::$supports[$module_name][] = $feature;
	}

	public function supports_feature($module_name, $feature)
	{
		return isset(self::$supports[$module_name]) && in_array($feature, self::$supports[$module_name]);
	}

	/**
	 * Get module headers info
	 * @param string $module_source the module init file location
	 * @return array
	 */
	public function get_headers($module_source)
	{
		$module_data = read_file($module_source); // Read the module init file.

		preg_match('|Module Name:(.*)$|mi', $module_data, $name);
		preg_match('|Module ID:(.*)$|mi', $module_data, $item_id);
		// preg_match('|Item ID:(.*)$|mi', $module_data, $items_id);
		preg_match('|Module URI:(.*)$|mi', $module_data, $uri);
		preg_match('|Version:(.*)|i', $module_data, $version);
		preg_match('|Description:(.*)$|mi', $module_data, $description);
		preg_match('|Author:(.*)$|mi', $module_data, $author_name);
		preg_match('|Author URI:(.*)$|mi', $module_data, $author_uri);
		preg_match('|Requires at least:(.*)$|mi', $module_data, $requires_at_least);

		$arr = [];

		if (isset($name[1])) {
			$arr['module_name'] = trim($name[1]);
		}
		if (isset($item_id[1])) {
			$arr['item_id'] = trim($item_id[1]);
		}
		// if (isset($items_id[1])) {
		//     $arr['item_id'] = trim($items_id[1]);
		// }
		if (isset($uri[1])) {
			$arr['uri'] = trim($uri[1]);
		}

		if (isset($version[1])) {
			$arr['version'] = trim($version[1]);
		} else {
			$arr['version'] = 0;
		}

		if (isset($description[1])) {
			$arr['description'] = trim($description[1]);
		}

		if (isset($author_name[1])) {
			$arr['author'] = trim($author_name[1]);
		}

		if (isset($author_uri[1])) {
			$arr['author_uri'] = trim($author_uri[1]);
		}

		if (isset($requires_at_least[1])) {
			$arr['requires_at_least'] = trim($requires_at_least[1]);
		}

		return $arr;
	}

	/**
	 * Check whether module is inserted into database table
	 * @param string $name module system name
	 * @return boolean
	 */
	private function module_exists_in_database($name)
	{
		return (bool)$this->get_module_from_database($name);
	}

	/**
	 * Get valid modules
	 * @return array
	 */
	public static function get_valid_modules()
	{
		$modules = directory_map(MODULES_PATH, 1);

		$valid_modules = [];

		if ($modules) {
			foreach ($modules as $name) {
				$name = strtolower(trim($name));

				/**
				 * Filename may be returned like chat/ or chat\ from the directory_map function
				 */
				foreach (['\\', '/'] as $trim) {
					$name = rtrim($name, $trim);
				}

				// If the module hasn't already been added and isn't a file
				if (!stripos($name, '.')) {
					$module_path = MODULES_PATH . $name . '/';
					$init_file = $module_path . $name . '.php';
					// Make sure a valid module file by the same name as the folder exists
					if (file_exists($init_file)) {
						$valid_modules[] = [
							'init_file' => $init_file,
							'name' => $name,
							'path' => $module_path,
						];
					}
				}
			}
		}

		return $valid_modules;
	}

	private function query_db_modules()
	{
		$db_modules = get_result('tbl_modules');
		foreach ($db_modules as $db_module) {
			$this->db_modules[$db_module->module_name] = $db_module;
		}
	}
	private function get_module_info($name)
	{
		return get_row('tbl_modules', array('module_name' => $name));
	}

	private
	function clean_tmp_files()
	{
		if (is_dir($this->tmp_update_dir)) {
			@delete_files($this->tmp_update_dir);
			if (@!delete_dir($this->tmp_update_dir)) {
				@rename($this->tmp_update_dir, $this->tmp_dir . 'delete_this_' . uniqid());
			}
		}
	}
	
	public function load_css()
	{

		$all_css = $this->css;
		$css = '';
		if (!empty($all_css)) {
			foreach ($all_css as $vcss) {
				$css .= '<link href="' . base_url($vcss) . '"  rel="stylesheet" type="text/css" >';
			}
		}
		return $css;
	}

	public function load_js()
	{
		$all_js = $this->js;
		$js = '';
		if (!empty($all_js)) {
			foreach ($all_js as $vjs) {
				$js .= '<script src="' . base_url($vjs) . '"></script>';
			}
		}
		return $js;
	}

	public function clear_cache()
	{
		if (isset($this->ci->cache)) {
			$this->ci->cache->delete(self::DISCOVERY_CACHE_KEY);
			$this->ci->cache->delete(self::AVAILABLE_CACHE_KEY);
		}
		$this->modules = [];
		$this->active_modules = [];
	}

	private function get_discovered_modules()
	{
		$cached = isset($this->ci->cache) ? $this->ci->cache->get(self::DISCOVERY_CACHE_KEY) : null;
		if (is_array($cached) && !empty($cached)) {
			return $cached;
		}

		$discovered = [];
		foreach (self::get_valid_modules() as $module) {
			$module['headers'] = $this->get_headers($module['init_file']);
			$discovered[] = $module;
		}

		if (isset($this->ci->cache)) {
			$this->ci->cache->save(self::DISCOVERY_CACHE_KEY, $discovered, self::CACHE_TTL);
		}

		return $discovered;
	}
}
