<?php

/**
 * FanPress CM 5.x
 * @license http://www.gnu.org/licenses/gpl.txt GPLv3
 */

namespace fpcm\module;

/**
 * Modules list
 *
 * @author Stefan Seehafer <sea75300@yahoo.de>
 * @copyright (c) 2011-2022, Stefan Seehafer
 * @license http://www.gnu.org/licenses/gpl.txt GPLv3
 * @package fpcm\module
 */
class modules extends \fpcm\model\abstracts\tablelist {

    const MODULES_ALL = 'all';

    /**
     * Enabled modules cache
     * @var array
     */
    private $enabledCache;

    /**
     * Module key cache
     * @var array
     */
    private $keyCache;

    /**
     * Suppress deprecation note in module manager
     * @var bool
     * @since 5.3.0-dev
     * @ignore
     * @internal
     */
    public bool $suppress_deprecation_note = false;

    /**
     * Konstruktor
     * @return bool
     */
    public function __construct()
    {
        $this->table = \fpcm\classes\database::tableModules;
        $this->dbcon = \fpcm\classes\loader::getObject('\fpcm\classes\database');
        $this->cache = \fpcm\classes\loader::getObject('\fpcm\classes\cache');

        if (\fpcm\classes\baseconfig::installerEnabled()) {
            return false;
        }

        $this->events = \fpcm\classes\loader::getObject('\fpcm\events\events');
        $this->config = \fpcm\classes\loader::getObject('\fpcm\model\system\config');
    }

    /**
     * Module keys from database
     * @return array
     */
    public function getKeysFromDatabase() : array
    {
        if (\fpcm\classes\baseconfig::installerEnabled()) {
            return [];
        }

        if (is_array($this->keyCache)) {
            return $this->keyCache;
        }

        $this->keyCache = [];

        $result = $this->dbcon->selectFetch( (new \fpcm\model\dbal\selectParams($this->table))->setItem('mkey')->setFetchAll(true) );
        if (!$result) {
            return $this->keyCache;
        }

        foreach ($result as $dataset) {
            $this->keyCache[] = $dataset->mkey;
        }

        return $this->keyCache;
    }

    /**
     * Fetch local module database
     * @param bool $sort
     * @param \fpcm\model\modules\search|null $search
     * @return array
     */
    public function getFromDatabase(bool $sort = false, ?\fpcm\module\search $search = null) : array
    {
        if (\fpcm\classes\baseconfig::installerEnabled()) {
            return [];
        }

        $whereQ = ['1=1'];
        $params = [];

        if ($search instanceof \fpcm\module\search) {

            if ($search->text) {
                $whereQ[] = '(mkey LIKE ? OR data LIKE ?)';
                $params[] = "%{$search->text}%";
                $params[] = "%{$search->text}%";
            }

            if ($search->status > -1) {
                $whereQ[] = '(installed = 1 AND active = ?)';
                $params[] = $search->status;
            }

        }

        $where = implode(' AND ', $whereQ);

        if ($sort) {
            $where .= ' ' . $this->dbcon->orderBy(['installed DESC, active DESC, mkey ASC']);
        }

        $obj = (new \fpcm\model\dbal\selectParams($this->table))->setFetchAll(true)->setWhere($where);

        if (count($params)) {
            $obj->setParams($params);
        }

        $result = $this->dbcon->selectFetch($obj);
        if (!$result) {
            return [];
        }

        $modules = [];
        foreach ($result as $dataset) {
            $this->createResult($dataset, $modules);
        }

        return $modules;
    }

    /**
     * Fetch installed modules from database
     * @return array
     */
    public function getInstalledDatabase() : array
    {
        if (\fpcm\classes\baseconfig::installerEnabled()) {
            return [];
        }

        $result = $this->dbcon->selectFetch( (new \fpcm\model\dbal\selectParams($this->table))->setWhere('installed = 1')->setFetchAll(true) );
        if (!$result) {
            return [];
        }

        $modules = [];
        foreach ($result as $dataset) {
            $this->createResult($dataset, $modules);
        }

        return $modules;
    }

    /**
     * Get installed modules with updates
     * @param bool $countOnly
     * @return int|array
     */
    public function getInstalledUpdates(bool $countOnly = false) : int|array
    {
        if (\fpcm\classes\baseconfig::installerEnabled()) {
            return $countOnly ? 0 : [];
        }

        $installed = $this->getInstalledDatabase();
        if (!count($installed)) {
            return $countOnly ? 0 : [];
        }

        $list = [];
        foreach ($installed as $key => $module) {

            if (!$module->hasUpdates()) {
                continue;
            }

            $list[] = $key;

        }

        return $countOnly ? count($list) : $list;
    }

    /**
     * Fetch module data from repository
     * @return array
     */
    public function getFromRepository() : array
    {
        $repoData = (new \fpcm\model\updater\modules())->getData();

        if (!is_array($repoData) || !count($repoData)) {
            return [];
        }

        $modules = [];
        foreach ($repoData as $key => $value) {

            $module = new repoModule($key, false);
            $module->createFromRepoArray([
                'name' => $value['name'],
                'description' => $value['description'] ?? '',
                'version' => $value['version'] ?? '',
                'author' => $value['author'] ?? '',
                'link' => $value['link'] ?? '',
                'requirements' => $value['requirements'] ?? [],
                'changelogUrl' => $value['changelog'] ?? '',
                'size' => $value['size'] ?? '',
            ]);

            $modules[$key] = $module;
        }

        return $modules;
    }

    /**
     * Fetch modul data from file system
     * @return bool
     */
    public function updateFromFilesystem() : bool
    {
        $folders = glob(\fpcm\classes\dirs::getDataDirPath(\fpcm\classes\dirs::DATA_MODULES, '*/*'), GLOB_ONLYDIR);
        if (!$folders) {
            return true;
        }

        $dbList = $this->getFromDatabase();
        foreach ($folders as $folder) {
            $key = module::getKeyFromPath($folder);
            $module = new module( $key, false );
            if (isset($dbList[$key])) {
                continue;
            }

            if (!$module->addModule()) {
                return false;
            }
        }

        return true;
    }

    /**
     * Fetch installed and enabled modules from database
     * @return array
     */
    public function getEnabledDatabase() : array
    {
        if (\fpcm\classes\baseconfig::installerEnabled()) {
            return [];
        }

        if (is_array($this->enabledCache)) {
            return $this->enabledCache;
        }

        $cacheName = 'modules/'. __FUNCTION__;

        if (!$this->cache->isExpired($cacheName)) {
            $this->enabledCache = $this->cache->read($cacheName);
            return is_array($this->enabledCache) ? $this->enabledCache : [];
        }

        $this->enabledCache = [];
        $result = $this->dbcon->selectFetch((new \fpcm\model\dbal\selectParams())->setTable($this->table)->setItem('mkey, data')->setWhere('installed = 1 AND active = 1')->setFetchAll(true));
        if (!$result) {
            $this->cache->write($cacheName, []);
            return $this->enabledCache;
        }

        foreach ($result as $dataset) {

            if ($dataset->data) {
                $cfg = new config($dataset->mkey, $dataset->data);
                if (version_compare($cfg->requirements->system, '5.3.0-dev', '<')) {

                    if (!$this->suppress_deprecation_note) {
                        trigger_error(sprintf("Module %s does not support FanPress CM 5.3.x! This module connat be installed for now.", $dataset->mkey));
                    }

                    continue;
                }
            }

            $this->enabledCache[] = $dataset->mkey;
        }

        $this->cache->write($cacheName, $this->enabledCache);
        return $this->enabledCache;
    }

    /**
     * Create module result
     * @param object $dataset
     * @param array $modules
     * @return bool
     */
    private function createResult($dataset, array &$modules) : bool
    {
        $module = new module($dataset->mkey, false);
        $module->createFromDbObject($dataset);
        $modules[$dataset->mkey] = $module;
        return true;
    }

}
