<?php

class Step00349 extends Migration
{
    private $registered_modules = [
        'overview'            => ['id' => 20, 'const' => '', 'sem' => true, 'inst' => false],
        'admin'               => ['id' => 17, 'const' => '', 'sem' => true, 'inst' => false],
        'forum'               => ['id' => 0, 'const' => '', 'sem' => true, 'inst' => true],
        'documents'           => ['id' => 1, 'const' => '', 'sem' => true, 'inst' => true],
        'schedule'            => ['id' => 2, 'const' => '', 'sem' => true, 'inst' => false],
        'participants'        => ['id' => 3, 'const' => '', 'sem' => true, 'inst' => false],
        'personal'            => ['id' => 4, 'const' => '', 'sem' => false, 'inst' => true],
        'wiki'                => ['id' => 8, 'const' => 'WIKI_ENABLE', 'sem' => true, 'inst' => true],
        'scm'                 => ['id' => 12, 'const' => 'SCM_ENABLE', 'sem' => true, 'inst' => true],
        'elearning_interface' => ['id' => 13, 'const' => 'ELEARNING_INTERFACE_ENABLE', 'sem' => true, 'inst' => true],
        'calendar'            => ['id' => 16, 'const' => 'COURSE_CALENDAR_ENABLE', 'sem' => true, 'inst' => true],
    ];
    private $notification_modules = [
        'basicdata'           => 27,
        'votes'               => 26,
        'news'                => 25,
        'forum'               => 0,
        'documents'           => 1,
        'schedule'            => 2,
        'participants'        => 3,
        'wiki'                => 8,
        'scm'                 => 12,
        'elearning_interface' => 13
    ];

    public function description()
    {
        return 'add table `tools_activated`; migrate data from plugins_activated and seminare.modules; add CorePlugins';
    }

    public function up()
    {
        $db = DBManager::get();
        $db->execute("CREATE TABLE IF NOT EXISTS `tools_activated` (
  `range_id` char(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
  `range_type` enum('course','institute') CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
  `plugin_id` int(10) UNSIGNED NOT NULL,
  `position` tinyint(3) UNSIGNED NOT NULL,
  `metadata` json DEFAULT NULL,
  `mkdate` int(10) UNSIGNED NOT NULL,
  `chdate` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`range_id`,`plugin_id`),
  KEY (`plugin_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        $db->execute("CREATE TABLE IF NOT EXISTS `seminar_user_notifications` (
  `user_id` char(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
  `seminar_id` char(32) CHARACTER SET latin1 COLLATE latin1_bin NOT NULL,
  `notification_data` json DEFAULT NULL,
  `chdate` int(10) UNSIGNED NOT NULL,
  `mkdate` int(10) UNSIGNED NOT NULL,
  PRIMARY KEY (`user_id`,`seminar_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci");

        $core_plugins = [
            'IliasInterfaceModule',
            'LtiToolModule',
            'GradebookModule',
            'FeedbackModule',
            'ConsultationModule',
            'CoreForum',
            'Blubber',
            'CoursewareModule'
        ];
        $studip_modules = [
            'CoreOverview',
            'CoreAdmin',
            'CoreStudygroupAdmin',
        //???    'CoreStudygroupOverview',
            'CoreDocuments',
            'CoreParticipants',
            'CoreStudygroupParticipants',
            'CoreSchedule',
            'CoreScm',
            'CoreWiki',
            'CoreCalendar',
            'CoreElearningInterface',
            'CorePersonal'
        ];
        $ouv_mapping = [
            'sem' => 0,
            'inst'=> 0,
            'basicdata' => 0,
            'vote' => -1,
            'eval' => -2,
            'news' => 'CoreOverview',
            'documents' => 'CoreDocuments',
            'schedule' => 'CoreSchedule',
            'scm' =>  'CoreScm',
            'wiki' => 'CoreWiki',
            'elearning_interface' => 'CoreElearningInterface',
            'ilias_interface' => 'IliasInterfaceModule',
            'participants' => 'CoreParticipants',
            'courseware' => 'CoursewareModule'
        ];
        PluginManager::getInstance()->getPlugin('CoreForum');
        PluginManager::getInstance()->getPlugin('Blubber');

        foreach ($core_plugins as $plugin) {
            try {
                $info = new ReflectionClass($plugin);
            } catch (ReflectionException $e) {
                continue;
            }
            $ifaces = array_merge(['CorePlugin'], $info->getInterfaceNames());
            $db->execute("UPDATE plugins SET plugintype=? WHERE pluginclassname=?", [join(',', $ifaces), $plugin]);
        }
        foreach ($studip_modules as $module) {
            $info = new ReflectionClass($module);
            $ifaces = array_merge(['CorePlugin'], $info->getInterfaceNames());
            $db->execute("INSERT INTO plugins (pluginclassname, pluginname, plugintype, enabled, navigationpos)
                VALUES (?, ?, ?, 'yes', 1)", [$module, $module, join(',', $ifaces)]);
            $db->execute("INSERT INTO roles_plugins (roleid, pluginid) SELECT roleid, ? FROM roles WHERE `system` = 'y'", [$db->lastInsertId()]);
        }

        $all_plugins = $db->fetchPairs("SELECT pluginclassname, pluginid FROM plugins");

        foreach ($db->query("SELECT seminar_id, status, modules FROM seminare") as $row) {
            $activated_plugins = $db->fetchPairs("SELECT plugins_activated.pluginid, state FROM `plugins_activated` INNER JOIN `plugins` USING(pluginid) WHERE range_id=? AND range_type='sem' ORDER BY navigationpos", [$row['seminar_id']]);
            $modules = $this->getLocalModules('sem', $row['modules'], $row['status']);
            $pos = 0;
            foreach ($modules as $pos => $module) {
                if (isset($all_plugins[$module]) && !(isset($activated_plugins[$all_plugins[$module]]) && $activated_plugins[$all_plugins[$module]] === '0')) {
                    $db->execute("INSERT IGNORE INTO `tools_activated` (`range_id`, `range_type`, `plugin_id`, `position`, `metadata`, `mkdate`, `chdate`) VALUES (?, 'course', ?, ?, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",
                        [
                            $row['seminar_id'],
                            $all_plugins[$module],
                            $pos
                        ]);
                }
            }
            foreach ($activated_plugins as $plugin_id => $state) {
                if (!$state) {
                    continue;
                }
                $db->execute("INSERT IGNORE INTO `tools_activated` (`range_id`, `range_type`, `plugin_id`, `position`, `metadata`, `mkdate`, `chdate`) VALUES (?, 'course', ?, ?, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",
                    [
                        $row['seminar_id'],
                        $plugin_id,
                        ++$pos
                    ]);
            }
        }

        foreach ($db->query("SELECT institut_id, type, modules FROM Institute") as $row) {
            $activated_plugins = $db->fetchPairs("SELECT plugins_activated.pluginid, state FROM `plugins_activated` INNER JOIN `plugins` USING(pluginid) WHERE range_id=? AND range_type='inst' ORDER BY navigationpos", [$row['institut_id']]);
            $modules = $this->getLocalModules('inst', $row['modules'], $row['type']);
            $pos = 0;
            foreach ($modules as $pos => $module) {
                if (isset($all_plugins[$module]) && !(isset($activated_plugins[$all_plugins[$module]]) && $activated_plugins[$all_plugins[$module]] === '0')) {
                    $db->execute("INSERT IGNORE INTO `tools_activated` (`range_id`, `range_type`, `plugin_id`, `position`, `metadata`, `mkdate`, `chdate`) VALUES (?, 'institute', ?, ?, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",
                        [
                            $row['institut_id'],
                            $all_plugins[$module],
                            $pos
                        ]);
                }
            }
            foreach ($activated_plugins as $plugin_id => $state) {
                if (!$state) {
                    continue;
                }
                $db->execute("INSERT IGNORE INTO `tools_activated` (`range_id`, `range_type`, `plugin_id`, `position`, `metadata`, `mkdate`, `chdate`) VALUES (?, 'institute', ?, ?, NULL, UNIX_TIMESTAMP(), UNIX_TIMESTAMP())",
                    [
                        $row['institut_id'],
                        $plugin_id,
                        ++$pos
                    ]);
            }
        }

        foreach ($db->query("SELECT seminar_id, user_id, notification FROM seminar_user WHERE notification > 0") as $row) {
            $notifications = [];
            foreach ($this->notification_modules as $module => $id) {
                if ($row['notification'] & pow(2, $id)) {
                    $plugin_id = $ouv_mapping[$module];
                    if (is_string($plugin_id)) {
                        $plugin_id = $all_plugins[$plugin_id];
                    }
                    if ($plugin_id !== null) {
                        $notifications[] = $plugin_id;
                    }
                }
            }
            if (count($notifications)) {
                $db->execute("INSERT INTO seminar_user_notifications (user_id, seminar_id, notification_data, chdate, mkdate) VALUES (?,?,?,UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", [$row['user_id'], $row['seminar_id'], json_encode($notifications)]);
            }
        }

        foreach ($db->query("SELECT range_id, user_id, notification FROM deputies WHERE notification > 0") as $row) {
            $notifications = [];
            foreach ($this->notification_modules as $module => $id) {
                if ($row['notification'] & pow(2, $id)) {
                    $plugin_id = $ouv_mapping[$module];
                    if (!is_int($plugin_id)) {
                        $plugin_id = $all_plugins[$module];
                    }
                    if ($plugin_id !== null) {
                        $notifications[] = $plugin_id;
                    }
                }
            }
            if (count($notifications)) {
                $db->execute("INSERT IGNORE INTO seminar_user_notifications (user_id, seminar_id, notification_data, chdate, mkdate) VALUES (?,?,?,UNIX_TIMESTAMP(), UNIX_TIMESTAMP())", [$row['user_id'], $row['range_id'], json_encode($notifications)]);
            }
        }

        $db->exec("DELETE FROM `object_user_visits` WHERE `type` IN ('literature', 'forum', '')");
        $db->exec("ALTER TABLE `object_user_visits` ADD `plugin_id` INT NOT NULL AFTER `type`");
        foreach ($ouv_mapping as $type => $plugin_id) {
            if (is_string($plugin_id)) {
                $plugin_id = $all_plugins[$plugin_id];
            }
            $db->execute("UPDATE `object_user_visits` SET `plugin_id` = ? WHERE `type` = ?", [$plugin_id, $type]);
        }

        $db->exec("ALTER TABLE `object_user_visits` DROP PRIMARY KEY, ADD PRIMARY KEY (`object_id`, `user_id`, `plugin_id`)");
        $db->exec("ALTER TABLE `object_user_visits` DROP `type`");
        $db->exec("ALTER TABLE `seminar_user` DROP `notification`");
        $db->exec("ALTER TABLE `seminare` DROP `modules`");
        $db->exec("ALTER TABLE `Institute` DROP `modules`");
        $db->exec("ALTER TABLE `sem_classes`
                      DROP `overview`,
                      DROP `forum`,
                      DROP `admin`,
                      DROP `documents`,
                      DROP `schedule`,
                      DROP `participants`,
                      DROP `literature`,
                      DROP `scm`,
                      DROP `wiki`,
                      DROP `resources`,
                      DROP `calendar`,
                      DROP `elearning_interface`");

    }

    private function getLocalModules($range_type, $modules, $type)
    {
        $old_sem_class = OldSemClass::getClasses();
        if ($range_type === 'sem') {
            $class = DBManager::get()->fetchColumn("SELECT class FROM sem_types WHERE id=?", [$type]);
            $sem_class = $old_sem_class[$class] ?: OldSemClass::getDefaultSemClass();
        } else {
            $sem_class = OldSemClass::getDefaultInstituteClass($type);
        }
        if (!$modules) {
            $modules = 0;
            foreach ($this->registered_modules as $slot => $val) {
                if ($val[$range_type === 'sem' ? 'sem' : 'inst']) {
                    $const = $val['const'];
                    if ($sem_class->isModuleActivated($sem_class->getSlotModule($slot)) && (!$const || Config::get()->$const)) {
                        $modules += pow(2, $val['id']);
                    }
                }
            }
        }
        $modules_list = [];
        $pos = 0;
        foreach ($this->registered_modules as $key => $val) {
            $module = $sem_class->getSlotModule($key);
            if ($sem_class->isModuleAllowed($module)) {
                if (($modules & pow(2, $val['id'])) || $sem_class->isSlotMandatory($key)) {
                    $modules_list[$pos] = $module;
                    $pos++;
                }
            }
        }
        return $modules_list;
    }

    public function down()
    {

    }
}


class OldSemClass implements ArrayAccess
{
    protected $data = [];
    protected static $slots = [
        "overview",
        "forum",
        "admin",
        "documents",
        "schedule",
        "participants",
        "scm",
        "wiki",
        "calendar",
        "elearning_interface"
    ];
    protected static $core_modules = [
        "CoreOverview",
        "CoreAdmin",
        "CoreStudygroupAdmin",
        "CoreStudygroupOverview",
        "CoreDocuments",
        "CoreParticipants",
        "CoreStudygroupParticipants",
        "CoreSchedule",
        "CoreScm",
        "CoreWiki",
        "CoreCalendar",
        "CoreElearningInterface"
    ];
    protected static $sem_classes = null;

    public static function getDefaultSemClass()
    {
        $data = [
            'name'                => "Fehlerhafte Seminarklasse!",
            'overview'            => "CoreOverview",
            'forum'               => "Blubber",
            'admin'               => "CoreAdmin",
            'documents'           => "CoreDocuments",
            'schedule'            => "CoreSchedule",
            'participants'        => "CoreParticipants",
            'scm'                 => "CoreScm",
            'wiki'                => "CoreWiki",
            'calendar'            => "CoreCalendar",
            'elearning_interface' => "CoreElearningInterface",
            'modules'             => '{"CoreOverview":{"activated":1,"sticky":1},"CoreAdmin":{"activated":1,"sticky":1}, "CoreResources":{"activated":1,"sticky":0}}',
            'visible'             => 1,
            'is_group'            => false
        ];
        return new self($data);
    }

    /**
     * Generates a dummy SemClass for institutes of this type (as defined in config.inc.php).
     * @param integer $type institute type
     * @return SemClass
     */
    public static function getDefaultInstituteClass($type)
    {
        global $INST_MODULES;

        // fall back to 'default' if modules are not defined
        $type = isset($INST_MODULES[$type]) ? $type : 'default';

        $data = [
            'name'     => 'Generierte Standardinstitutsklasse',
            'visible'  => 1,
            'overview' => 'CoreOverview', // always available
            'admin'    => 'CoreAdmin'     // always available
        ];
        $slots = [
            'forum'               => 'Blubber',
            'documents'           => 'CoreDocuments',
            'scm'                 => 'CoreScm',
            'wiki'                => 'CoreWiki',
            'calendar'            => 'CoreCalendar',
            'elearning_interface' => 'CoreElearningInterface',
            'personal'            => 'CorePersonal'
        ];
        $modules = [
            'CoreOverview' => ['activated' => 1, 'sticky' => 1],
            'CoreAdmin'    => ['activated' => 1, 'sticky' => 1]
        ];

        foreach ($slots as $slot => $module) {
            $data[$slot] = $module;
            $modules[$module] = ['activated' => (int)$INST_MODULES[$type][$slot], 'sticky' => 0];
        }
        $data['modules'] = json_encode($modules);

        return new self($data);
    }

    /**
     * Constructor can be set with integer of sem_class_id or an array of
     * the old $SEM_CLASS style.
     * @param integer | array $data
     */
    public function __construct($data)
    {
        $db = DBManager::get();
        if (is_int($data)) {
            $statement = $db->prepare("SELECT * FROM sem_classes WHERE id = :id ");
            $statement->execute(['id' => $data]);
            $this->data = $statement->fetch(PDO::FETCH_ASSOC);
        } else {
            $this->data = $data;
        }
        if ($this->data['modules']) {
            $this->data['modules'] = self::object2array(json_decode($this->data['modules']));
        } else {
            $this->data['modules'] = [];
        }
    }

    /**
     * Returns the name of the module of the slot or the module itself, if it
     * is a plugin.
     * @param string $slot
     * @return string
     */
    public function getSlotModule($slot)
    {
        if (in_array($slot, self::$slots)) {
            return $this->data[$slot];
        } else {
            return $slot;
        }
    }


    /**
     * Returns all metadata of the modules at once.
     * @return array: array($module_name => array('sticky' => (bool), 'activated' => (bool)), ...)
     */
    public function getModules()
    {
        return $this->data['modules'];
    }

    /**
     * Returns true if a module is activated on default for this sem_class.
     * @param string $modulename
     * @return boolean
     */
    public function isModuleActivated($modulename)
    {
        return !$this->data['modules'][$modulename]
            || $this->data['modules'][$modulename]['activated'];
    }

    /**
     * Returns if a module is allowed to be displayed for this sem_class.
     * @param string $modulename
     * @return boolean
     */
    public function isModuleAllowed($modulename)
    {
        return !$this->data['modules'][$modulename]
            || !$this->data['modules'][$modulename]['sticky']
            || $this->data['modules'][$modulename]['activated'];
    }

    /**
     * Returns if a module is mandatory for this sem_class.
     * @param string $module
     * @return boolean
     */
    public function isModuleMandatory($module)
    {
        return $this->data['modules'][$module]['sticky']
            && $this->data['modules'][$module]['activated'];
    }

    /**
     * Returns if the slot is mandatory, which it is if the module in this
     * slot is mandatory.
     * @param string $slot
     * @return boolean
     */
    public function isSlotMandatory($slot)
    {
        $module = $this->getSlotModule($slot);
        return $module && $this->isModuleMandatory($module);
    }

    /**
     * Returns if a module is a slot module. Good for plugins that should be
     * displayed on a specific place only if they are no slot modules.
     * @param string $module
     * @return boolean
     */
    public function isSlotModule($module)
    {
        foreach (self::$slots as $slot) {
            if ($module === $this->getSlotModule($slot)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Returns the slot name of a module.
     * @param string $module
     * @return string|null
     */
    public function getModuleSlot($module)
    {
        foreach (self::$slots as $slot) {
            if ($module === $this->getSlotModule($slot)) {
                return $slot;
            }
        }
        return null;
    }

    /**
     * returns an instance of the module of a given slotname or pluginclassname
     * @param string $slot_or_plugin
     * @return StudipModule | null
     */
    public function getModule($slot_or_plugin)
    {
        $module = $this->getSlotModule($slot_or_plugin);
        if ($module && $this->isModuleAllowed($module)) {
            if (in_array($module, self::$core_modules)) {
                return new $module();
            }
            if ($module) {
                return PluginEngine::getPlugin($module);
            }
        }
    }


    /**
     * Sets an attribute of sem_class->data
     * @param string $offset
     * @param mixed $value
     */
    public function set($offset, $value)
    {
        $this->data[$offset] = $value;
    }

    /***************************************************************************
     *                          ArrayAccess methods                            *
     ***************************************************************************/

    /**
     * deprecated, does nothing, should not be used
     * @param string $offset
     * @param mixed $value
     */
    public function offsetSet($offset, $value)
    {
    }

    /**
     * Compatibility function with old $SEM_CLASS variable for plugins. Maps the
     * new array-structure to the old boolean values.
     * @param integer $offset : name of attribute
     * @return boolean|(localized)string
     */
    public function offsetGet($offset)
    {
        switch ($offset) {
            case "name":
                return gettext($this->data['name']);
            case "only_inst_user":
                return (bool)$this->data['only_inst_user'];
            case "bereiche":
                return (bool)$this->data['bereiche'];
            case "show_browse":
                return (bool)$this->data['show_browse'];
            case "write_access_nobody":
                return (bool)$this->data['write_access_nobody'];
            case "topic_create_autor":
                return (bool)$this->data['topic_create_autor'];
            case "visible":
                return (bool)$this->data['visible'];
            case "forum":
                return $this->data['forum'] !== null;
            case "documents":
                return $this->data['documents'] !== null;
            case "schedule":
                return $this->data['schedule'] !== null;
            case "participants":
                return $this->data['participants'] !== null;
            case "scm":
                return $this->data['scm'] !== null;
            case "studygroup_mode":
                return (bool)$this->data['studygroup_mode'];
            case "admission_prelim_default":
                return (int)$this->data['admission_prelim_default'];
            case "admission_type_default":
                return (int)$this->data['admission_type_default'];
            case "is_group":
                return (bool)$this->data['is_group'];
        }
        //ansonsten
        return $this->data[$offset];
    }

    /**
     * ArrayAccess method to check if an attribute exists.
     * @param type $offset
     * @return type
     */
    public function offsetExists($offset)
    {
        return isset($this->data[$offset]);
    }

    /**
     * deprecated, does nothing, should not be used
     * @param string $offset
     */
    public function offsetUnset($offset)
    {
    }

    /***************************************************************************
     *                            static methods                               *
     ***************************************************************************/

    /**
     * Returns an array of all SemClasses in Stud.IP. Equivalent to global
     * $SEM_CLASS variable. This variable is statically stored in this class.
     * @return array of SemClass
     */
    public static function getClasses()
    {
        if (!is_array(self::$sem_classes)) {
            $db = DBManager::get();
            self::$sem_classes = [];

            $statement = $db->prepare(
                "SELECT * FROM sem_classes ORDER BY id ASC "
            );
            $statement->execute();
            $class_array = $statement->fetchAll(PDO::FETCH_ASSOC);
            foreach ($class_array as $sem_class) {
                self::$sem_classes[$sem_class['id']] = new self($sem_class);
            }
        }
        return self::$sem_classes;
    }


    /**
     * Static method to recursively transform an object into an associative array.
     * @param mixed $obj : should be of class StdClass
     * @return array
     */
    public static function object2array($obj)
    {
        $arr_raw = is_object($obj) ? get_object_vars($obj) : $obj;
        foreach ($arr_raw as $key => $val) {
            $val = (is_array($val) || is_object($val)) ? self::object2array($val) : $val;
            $arr[$key] = $val;
        }
        return $arr;
    }
}