<?php
/**
 * @package         Conditions
 * @version         23.1.9739
 * 
 * @author          Peter van Westen <info@regularlabs.com>
 * @link            http://regularlabs.com
 * @copyright       Copyright © 2023 Regular Labs All Rights Reserved
 * @license         http://www.gnu.org/licenses/gpl-2.0.html GNU/GPL
 */

namespace RegularLabs\Component\Conditions\Administrator\Helper;

use Joomla\CMS\Factory as JFactory;
use RegularLabs\Component\Conditions\Administrator\Model\ItemModel;
use RegularLabs\Library\DB as RL_DB;
use RegularLabs\Library\Language as RL_Language;

class ConvertAssignments
{
    static $article_group_id   = 0;
    static $conditions_by_hash = [];
    static $conditions_by_item = [];
    static $field_ids          = [];
    static $mirrors            = [];

    public static function convert($extension, $params_table, $name_table = '', $name_column = 'name', $id_column = 'id')
    {
        $name_table = $name_table ?: $params_table;

        $db    = JFactory::getDbo();
        $query = $db->getQuery(true)
            // Select the required fields from the table.
            ->select('a.*')
            ->from($db->quoteName('#__' . $params_table, 'a'))
            ->where(RL_DB::like('a.params', '*"assignto_*'));
        $db->setQuery($query);

        $items = $db->loadObjectList();

        if (empty($items))
        {
            return true;
        }

        foreach ($items as $item)
        {
            if ( ! self::handleItem($item, $extension, $params_table, $name_table, $name_column, $id_column))
            {
                return false;
            }
        }

        foreach (self::$mirrors as $source_id => $mirrored_ids)
        {
            if ($source_id < 0)
            {
                self::createAndMapReverseCondition($source_id * -1, $mirrored_ids, $extension, $params_table, $name_table, $name_column, $id_column);
                continue;
            }

            self::mapFromSourceCondition($source_id, $mirrored_ids, $extension, $params_table, $name_table, $name_column, $id_column);
        }

        return true;
    }

    public static function getCondition($item, $extension, $table, $name_column = 'name')
    {
        RL_Language::load('com_conditions');

        $name = Helper::getForItemText($extension, $item->id, $table, $name_column);

        $condition = (object) [
            'name'      => $name,
            'match_all' => ($item->params->match_method ?? 'and') === 'and' ? 1 : 0,
            'published' => 1,
            'groups'    => [
                (object) [
                    'match_all' => ($item->params->match_method ?? 'and') === 'and' ? 1 : 0,
                    'rules'     => [],
                ],
                (object) [
                    'match_all' => 1,
                    'rules'     => [],
                ],
            ],
        ];

        self::$article_group_id = $condition->match_all ? 0 : 1;

        self::addRules($item->params, $condition->groups);

        if (empty($condition->groups[0]->rules))
        {
            unset($condition->groups[0]);
        }

        if (empty($condition->groups[1]->rules))
        {
            unset($condition->groups[1]);
        }

        $condition->hash = md5(json_encode([$condition->match_all, $condition->groups]));

        return $condition;
    }

    public static function saveConditionByItem($item, $extension, $name_table, $name_column = 'name')
    {
        $condition = self::getCondition($item, $extension, $name_table, $name_column);

        if (empty($condition->groups))
        {
            return true;
        }

        $model = new ItemModel;

        if (isset(self::$conditions_by_hash[$condition->hash]))
        {
            $condition_id = self::$conditions_by_hash[$condition->hash];

            $model->map(
                $condition_id,
                $extension,
                $item->id,
                $name_table,
                $name_column
            );

            self::$conditions_by_item[$item->id] = $condition_id;

            return true;
        }

        $condition_saved = $model->saveByObject(
            $condition,
            $extension,
            $item->id,
            $name_table,
            $name_column
        );

        if ( ! $condition_saved)
        {
            return false;
        }

        self::$conditions_by_item[$item->id]        = $condition->id;
        self::$conditions_by_hash[$condition->hash] = $condition->id;

        return true;
    }

    private static function addRule(&$groups, $type, $exclude, $params = [], $id = 0)
    {
        $groups[$id]->rules[] = (object) [
            'type'    => $type,
            'exclude' => $exclude ? 1 : 0,
            'params'  => (object) $params,
        ];
    }

    private static function addRuleAgentBrowser($params, &$groups)
    {
        if (empty($params->assignto_browsers)
            || empty($params->assignto_browsers_selection))
        {
            return;
        }

        self::addRule($groups,
            'agent__browser',
            $params->assignto_browsers == 2,
            [
                'selection' => $params->assignto_browsers_selection ?? [],
            ]
        );
    }

    private static function addRuleAgentBrowserMobile($params, &$groups)
    {
        if (empty($params->assignto_browsers)
            || empty($params->assignto_mobile_selection))
        {
            return;
        }

        self::addRule($groups,
            'agent__browser_mobile',
            $params->assignto_browsers == 2,
            [
                'selection' => $params->assignto_mobile_selection ?? [],
            ]
        );
    }

    private static function addRuleBasic($new_name, $old_name, $params, &$groups, $id = 0)
    {
        if (empty($params->{$old_name}))
        {
            return;
        }

        self::addRule($groups,
            $new_name,
            $params->{$old_name} == 2,
            [
                'selection' => $params->{$old_name . '_selection'} ?? [],
            ],
            $id
        );
    }

    private static function addRuleCategory($new_name, $old_name, $params, &$groups)
    {
        if (empty($params->{$old_name}))
        {
            return;
        }

        self::addRule($groups,
            $new_name,
            $params->{$old_name} == 2,
            [
                'selection'        => $params->{$old_name . '_selection'} ?? [],
                'include_children' => $params->{$old_name . 'inc_children'} ?? 0,
                'page_types'       => self::correctPageTypeValues($params->{$old_name . '_inc'} ?? []),
            ]
        );
    }

    private static function addRuleContentArticleAuthor($params, &$groups)
    {
        if (empty($params->assignto_articles)
            || empty($params->assignto_articles_authors)
        )
        {
            return;
        }

        self::addRule($groups,
            'content__article__author',
            $params->assignto_articles == 2,
            [
                'selection' => $params->assignto_articles_authors ?? [],
            ],
            self::$article_group_id
        );
    }

    private static function addRuleContentArticleContentKeyword($params, &$groups)
    {
        if (empty($params->assignto_articles)
            || empty($params->assignto_articles_authors)
        )
        {
            return;
        }

        self::addRule($groups,
            'content__article__content_keyword',
            $params->assignto_articles == 2,
            [
                'selection' => $params->assignto_articles_content_keywords ?? '',
            ],
            self::$article_group_id
        );
    }

    private static function addRuleContentArticleDate($params, &$groups)
    {
        if (empty($params->assignto_articles)
            || ! isset($params->assignto_articles_date)
            || $params->assignto_articles_date == ''
        )
        {
            return;
        }

        $comparison = $params->assignto_articles_date_comparison ?? 'between';
        if ($comparison == 'fromto')
        {
            $comparison = 'between';
        }

        self::addRule($groups,
            'content__article__date',
            $params->assignto_articles == 2,
            [
                'selection'  => $params->assignto_articles_date ?? '',
                'comparison' => $comparison,
                'date'       => $params->assignto_articles_date_date ?? '',
                'from'       => $params->assignto_articles_date_from ?? '',
                'to'         => $params->assignto_articles_date_to ?? '',
                'recurring'  => 0,
            ],
            self::$article_group_id
        );
    }

    private static function addRuleContentArticleFeatured($params, &$groups)
    {
        if (empty($params->assignto_articles)
            || ! isset($params->assignto_articles_featured)
            || $params->assignto_articles_featured == ''
        )
        {
            return;
        }

        $exclude = $params->assignto_articles == 2
            ? $params->assignto_articles_featured
            : ! $params->assignto_articles_featured;

        self::addRule($groups,
            'content__article__featured',
            $exclude,
            [],
            self::$article_group_id
        );
    }

    private static function addRuleContentArticleField($params, &$groups)
    {
        if (empty($params->assignto_articles)
            || empty($params->assignto_articles_fields)
        )
        {
            return;
        }

        foreach ($params->assignto_articles_fields as $field)
        {
            $field_id = self::getFieldIdByName($field->field);

            if ( ! $field_id)
            {
                continue;
            }
            self::addRule($groups,
                'content__article__field',
                $params->assignto_articles == 2,
                [
                    'field'      => $field_id,
                    'comparison' => $field->field_comparison ?? '',
                    'value'      => $field->field_value ?? '',
                ],
                self::$article_group_id
            );
        }
    }

    private static function addRuleContentArticleMetaKeyword($params, &$groups)
    {
        if (empty($params->assignto_articles)
            || empty($params->assignto_articles_keywords)
        )
        {
            return;
        }

        self::addRule($groups,
            'content__article__meta_keyword',
            $params->assignto_articles == 2,
            [
                'selection' => $params->assignto_articles_keywords ?? '',
            ],
            self::$article_group_id
        );
    }

    private static function addRuleDateDate($params, &$groups)
    {
        if (empty($params->assignto_date))
        {
            return;
        }

        self::addRule($groups,
            'date__date',
            $params->assignto_date == 2,
            [
                'comparison' => 'between',
                'from'       => $params->assignto_date_publish_up ?? '',
                'to'         => $params->assignto_date_publish_down ?? '',
                'recurring'  => $params->assignto_date_recurring ?? 0,
            ]
        );
    }

    private static function addRuleDateSeason($params, &$groups)
    {
        if (empty($params->assignto_seasons))
        {
            return;
        }

        self::addRule($groups,
            'date__season',
            $params->assignto_seasons == 2,
            [
                'selection'  => $params->assignto_seasons_selection ?? [],
                'hemisphere' => $params->assignto_seasons_hemisphere ?? 'northern',
            ]
        );
    }

    private static function addRuleDateTime($params, &$groups)
    {
        if (empty($params->assignto_time))
        {
            return;
        }

        self::addRule($groups,
            'date__time',
            $params->assignto_time == 2,
            [
                'comparison' => 'between',
                'from'       => $params->assignto_time_publish_up ?? '',
                'to'         => $params->assignto_time_publish_down ?? '',
            ]
        );
    }

    private static function addRuleMenuHomePage($params, &$groups)
    {
        if (empty($params->assignto_homepage))
        {
            return;
        }

        self::addRule($groups,
            'menu__home_page',
            $params->assignto_homepage == 2
        );
    }

    private static function addRuleMenuMenuItem($params, &$groups)
    {
        if (empty($params->assignto_menuitems))
        {
            return;
        }

        self::addRule($groups,
            'menu__menu_item',
            $params->assignto_menuitems == 2,
            [
                'selection'        => $params->assignto_menuitems_selection ?? [],
                'include_children' => $params->assignto_menuitems_inc_children ?? 0,
            ]
        );
    }

    private static function addRuleOtherTag($params, &$groups)
    {
        if (empty($params->assignto_tags))
        {
            return;
        }

        self::addRule($groups,
            'other__tag',
            $params->assignto_tags == 2,
            [
                'selection'        => $params->assignto_tags_selection ?? [],
                'match_all'        => $params->assignto_tags_match_all ?? 0,
                'include_children' => $params->assignto_tags_inc_children ?? 0,
            ]
        );
    }

    private static function addRuleOtherUrl($params, &$groups)
    {
        if (empty($params->assignto_urls))
        {
            return;
        }

        self::addRule($groups,
            'other__url',
            $params->assignto_urls == 2,
            [
                'selection'      => $params->assignto_urls_selection ?? [],
                'case_sensitive' => $params->assignto_urls_casesensitive ?? 0,
                'regex'          => $params->assignto_urls_regex ?? 0,
            ]
        );
    }

    private static function addRuleVisitorUserGroup($params, &$groups)
    {
        if (empty($params->assignto_usergrouplevels))
        {
            return;
        }

        self::addRule($groups,
            'visitor__user_group',
            $params->assignto_usergrouplevels == 2,
            [
                'selection'        => $params->assignto_usergrouplevels_selection ?? [],
                'match_all'        => $params->assignto_usergrouplevels_match_all ?? 0,
                'include_children' => $params->assignto_usergrouplevels_inc_children ?? 0,
            ]
        );
    }

    private static function addRules($params, &$groups)
    {
        self::addRuleMenuMenuItem($params, $groups);
        self::addRuleMenuHomepage($params, $groups);

        self::addRuleDateDate($params, $groups);
        self::addRuleDateSeason($params, $groups);
        self::addRuleBasic('date__month', 'assignto_months', $params, $groups);
        self::addRuleBasic('date__day', 'assignto_days', $params, $groups);
        self::addRuleDateTime($params, $groups);

        self::addRuleBasic('content__page_type', 'assignto_contentpagetypes', $params, $groups);
        self::addRuleCategory('content__category', 'assignto_cats', $params, $groups);

        self::addRuleBasic('content__article__id', 'assignto_articles', $params, $groups, self::$article_group_id);
        self::addRuleContentArticleFeatured($params, $groups);
        self::addRuleContentArticleDate($params, $groups);
        self::addRuleContentArticleAuthor($params, $groups);
        self::addRuleContentArticleContentKeyword($params, $groups);
        self::addRuleContentArticleMetaKeyword($params, $groups);
        self::addRuleContentArticleField($params, $groups);

        self::addRuleBasic('visitor__user', 'assignto_users', $params, $groups);
        self::addRuleVisitorUserGroup($params, $groups);
        self::addRuleBasic('visitor__language', 'assignto_languages', $params, $groups);
        self::addRuleBasic('visitor__ip', 'assignto_ips', $params, $groups);

        self::addRuleBasic('agent__device', 'assignto_devices', $params, $groups);
        self::addRuleBasic('agent__os', 'assignto_os', $params, $groups);
        self::addRuleAgentBrowser($params, $groups);
        self::addRuleAgentBrowserMobile($params, $groups);

        self::addRuleBasic('geo__continent', 'assignto_geocontinents', $params, $groups);
        self::addRuleBasic('geo__country', 'assignto_geocountries', $params, $groups);
        self::addRuleBasic('geo__region', 'assignto_georegions', $params, $groups);
        self::addRuleBasic('geo__postal_code', 'assignto_geopostalcodes', $params, $groups);

        self::addRuleOtherTag($params, $groups);
        self::addRuleBasic('other__component', 'assignto_components', $params, $groups);
        self::addRuleBasic('other__template', 'assignto_templates', $params, $groups);
        self::addRuleOtherUrl($params, $groups);
        self::addRuleBasic('other__php', 'assignto_php', $params, $groups);

        self::addRuleBasic('hikashop__page_type', 'assignto_hikashoppagetypes', $params, $groups);
        self::addRuleCategory('hikashop__category', 'assignto_hikashopcats', $params, $groups);
        self::addRuleBasic('hikashop__item', 'assignto_hikashopproducts', $params, $groups);

        self::addRuleBasic('flexicontent__page_type', 'assignto_flexicontentpagetypes', $params, $groups);
        self::addRuleCategory('flexicontent__tag', 'assignto_flexicontenttags', $params, $groups);
        self::addRuleBasic('flexicontent__type', 'assignto_flexicontenttypes', $params, $groups);
    }

    private static function correctPageTypeValues($values = [])
    {
        if (empty($values))
        {
            return [];
        }

        $aliases = [
            'inc_cats'   => 'categories',
            'inc_arts'   => 'articles',
            'inc_others' => 'others',
            'inc_tags'   => 'tags',
            'inc_items'  => 'items',
        ];

        $page_types = [];

        foreach ($values as $page_type)
        {
            if ($page_type === 'x')
            {
                continue;
            }

            $page_types[] = $aliases[$page_type] ?? $page_type;
        }

        return $page_types;
    }

    private static function createAndMapReverseCondition($source_id, $items, $extension, $params_table, $name_table, $name_column = 'name', $id_column = 'id')
    {
        if ( ! isset(self::$conditions_by_item[$source_id]))
        {
            return;
        }

        $model = new ItemModel;

        $first_item = array_shift($items);

        $condition = $model->getConditionById(self::$conditions_by_item[$source_id]);

        $reverse_condition = self::getReverseCondition($condition);

        $condition_saved = $model->saveByObject(
            $reverse_condition,
            $extension,
            $first_item->id,
            $name_table,
            $name_column
        );

        if ( ! $condition_saved)
        {
            return;
        }

        self::removeAssignments($first_item, $params_table, $id_column);

        foreach ($items as $item)
        {
            $model->map(
                $reverse_condition->id,
                $extension,
                $item->id,
                $name_table,
                $name_column
            );

            self::removeAssignments($item, $params_table, $id_column);
        }
    }

    private static function getFieldIdByName($name)
    {
        if (isset(static::$field_ids[$name]))
        {
            return static::$field_ids[$name];
        }

        $db    = JFactory::getDbo();
        $query = $db->getQuery(true)
            ->select('a.id')
            ->from('#__fields AS a')
            ->where(RL_DB::is('a.name', $name));

        $db->setQuery($query, 0, 1);

        static::$field_ids[$name] = $db->loadResult();

        return static::$field_ids[$name];
    }

    private static function getReverseCondition($source_condition)
    {
        RL_Language::load('com_conditions');

        $reverse_condition = (object) [
            'name'      => 'Reverse: ' . $source_condition->name,
            'match_all' => 1,
            'published' => 1,
            'groups'    => [
                (object) [
                    'match_all' => 1,
                    'rules'     => [],
                ],
            ],
        ];

        self::addRule($reverse_condition->groups, 'other__condition', true, (object) [
            'selection' => $source_condition->id,
        ]);

        return $reverse_condition;
    }

    private static function handleItem($item, $extension, $params_table, $name_table, $name_column = 'name', $id_column = 'id')
    {
        if (empty($item->params))
        {
            return true;
        }

        $item->id     = $item->{$id_column};
        $item->params = json_decode($item->params);

        if ( ! empty($item->mirror_id) && ! empty($item->mirror_id))
        {
            if ( ! isset(self::$mirrors[$item->mirror_id]))
            {
                self::$mirrors[$item->mirror_id] = [];
            }

            self::$mirrors[$item->mirror_id][] = $item;

            return true;
        }

        if ( ! self::saveConditionByItem($item, $extension, $name_table, $name_column))
        {
            return false;
        }

        self::removeAssignments($item, $params_table, $id_column);

        return true;
    }

    private static function mapFromSourceCondition($source_id, $items, $extension, $params_table, $name_table, $name_column = 'name', $id_column = 'id')
    {
        if ( ! isset(self::$conditions_by_item[$source_id]))
        {
            return;
        }

        $condition_id = self::$conditions_by_item[$source_id];

        $model = new ItemModel;

        foreach ($items as $item)
        {
            $model->map(
                $condition_id,
                $extension,
                $item->id,
                $name_table,
                $name_column
            );

            self::removeAssignments($item, $params_table, $id_column);
        }
    }

    private static function removeAssignments($item, $table, $id_column = 'id')
    {
        foreach ($item->params as $key => $value)
        {
            if (in_array($key, [
                    'mirror_module',
                    'mirror_moduleid',
                    'match_method',
                    'show_assignments',
                    'has_geoip_library',
                ])
                || strpos($key, 'assignto_') === 0)
            {
                unset($item->params->{$key});
            }
        }

        $params = json_encode(($item->params));

        $db = JFactory::getDbo();

        $query = $db->getQuery(true)
            ->update($db->quoteName('#__' . $table))
            ->set($db->quoteName('params') . ' = ' . $db->quote($params))
            ->where($db->quoteName($id_column) . ' = ' . (int) $item->id);

        $db->setQuery($query)->execute();
    }
}
