# This file is part of Tryton.  The COPYRIGHT file at the top level of
# this repository contains the full copyright notices and license terms.
from collections import OrderedDict, defaultdict
from itertools import groupby

from trytond.model import (
    DeactivableMixin, ModelSQL, ModelView, fields, sequence_ordered, tree)
from trytond.pool import Pool
from trytond.rpc import RPC
from trytond.transaction import Transaction, inactive_records


def one_in(i, j):
    """Check the presence of an element of setA in setB
    """
    for k in i:
        if k in j:
            return True
    return False


CLIENT_ICONS = [(x, x) for x in [
        'tryton-add',
        'tryton-archive',
        'tryton-attach',
        'tryton-back',
        'tryton-barcode-scanner',
        'tryton-bookmark-border',
        'tryton-bookmark',
        'tryton-bookmarks',
        'tryton-cancel',
        'tryton-clear',
        'tryton-close',
        'tryton-copy',
        'tryton-create',
        'tryton-chat',
        'tryton-date',
        'tryton-delete',
        'tryton-download',
        'tryton-email',
        'tryton-error',
        'tryton-exit',
        'tryton-export',
        'tryton-filter',
        'tryton-format-align-center',
        'tryton-format-align-justify',
        'tryton-format-align-left',
        'tryton-format-align-right',
        'tryton-format-bold',
        'tryton-format-color-text',
        'tryton-format-italic',
        'tryton-format-underline',
        'tryton-forward',
        'tryton-history',
        'tryton-import',
        'tryton-info',
        'tryton-launch',
        'tryton-link',
        'tryton-log',
        'tryton-menu',
        'tryton-note',
        'tryton-notification',
        'tryton-notification-off',
        'tryton-notification-on',
        'tryton-ok',
        'tryton-open',
        'tryton-print',
        'tryton-public',
        'tryton-refresh',
        'tryton-remove',
        'tryton-save',
        'tryton-search',
        'tryton-sound-off',
        'tryton-sound-on',
        'tryton-star-border',
        'tryton-star',
        'tryton-switch',
        'tryton-translate',
        'tryton-unarchive',
        'tryton-undo',
        'tryton-warning',
        ]]


class UIMenu(
        DeactivableMixin,
        sequence_ordered(order='ASC NULLS LAST'),
        tree(separator=' / '),
        ModelSQL, ModelView):
    __name__ = 'ir.ui.menu'

    name = fields.Char('Menu', required=True, translate=True)
    childs = fields.One2Many('ir.ui.menu', 'parent', 'Children')
    parent = fields.Many2One('ir.ui.menu', 'Parent Menu', ondelete='CASCADE')
    complete_name = fields.Function(fields.Char('Complete Name'),
        'get_rec_name', searcher='search_rec_name')
    icon = fields.Selection('list_icons', 'Icon', translate=False)
    action = fields.Function(fields.Reference('Action',
            selection=[
                ('', ''),
                ('ir.action.report', 'ir.action.report'),
                ('ir.action.act_window', 'ir.action.act_window'),
                ('ir.action.wizard', 'ir.action.wizard'),
                ('ir.action.url', 'ir.action.url'),
                ], translate=False), 'get_action', setter='set_action')
    action_keywords = fields.One2Many(
        'ir.action.keyword', 'model', "Action Keywords",
        filter=[
            ('keyword', '=', 'tree_open'),
            ])
    groups = fields.Many2Many(
        'ir.ui.menu-res.group', 'menu', 'group', "Groups",
        filter=[
            ('active', '=', True),
            ])
    favorite = fields.Function(fields.Boolean('Favorite'), 'get_favorite')
    favorites = fields.Many2Many(
        'ir.ui.menu.favorite', 'menu', 'user', "Favorites")

    @classmethod
    def order_complete_name(cls, tables):
        return cls.name.convert_order('name', tables, cls)

    @staticmethod
    def default_icon():
        return 'tryton-folder'

    @classmethod
    def default_sequence(cls):
        return 50

    @staticmethod
    def list_icons():
        pool = Pool()
        Icon = pool.get('ir.ui.icon')
        return sorted(CLIENT_ICONS
            + [(name, name) for _, name in Icon.list_icons()])

    @classmethod
    def search_global(cls, text):
        # TODO improve search clause
        for record in cls.search([
                    ('rec_name', 'ilike', '%%%s%%' % text),
                    ]):
            if record.action_keywords:
                yield record, record.rec_name, record.icon

    @classmethod
    def search(cls, domain, offset=0, limit=None, order=None, count=False,
            query=False):
        pool = Pool()
        ModelAccess = pool.get('ir.model.access')
        transaction = Transaction()

        def has_action_access(menu):
            if menu.action_keywords and all(
                    k.action.type == 'ir.action.act_window'
                    for k in menu.action_keywords):
                for keyword in menu.action_keywords:
                    res_model = keyword.action.action.res_model
                    if not res_model:
                        break
                    if ModelAccess.check(res_model, raise_exception=False):
                        break
                else:
                    return False
            return True

        menus = super().search(domain, offset=offset, limit=limit,
                order=order, count=False, query=query)
        if query:
            return menus

        if transaction.check_access and menus:

            menus = list(filter(has_action_access, menus))

            parent_ids = list({x.parent.id for x in menus if x.parent})
            parents = cls.search([
                    ('id', 'in', parent_ids),
                    ])
            # Re-browse to avoid side-cache access
            menus = cls.browse([x.id for x in menus
                    if (x.parent and x.parent in parents) or not x.parent])

        if count:
            return len(menus)
        return menus

    @classmethod
    @inactive_records
    def get_action(cls, menus, name):
        pool = Pool()
        actions = dict((m.id, None) for m in menus)
        menus = cls.browse(menus)
        action_keywords = sum((list(m.action_keywords) for m in menus), [])

        def action_type(keyword):
            return keyword.action.type
        action_keywords.sort(key=action_type)
        for type, action_keywords in groupby(action_keywords, key=action_type):
            action_keywords = list(action_keywords)
            action2keywords = defaultdict(list)
            for action_keyword in action_keywords:
                model = action_keyword.model
                actions[model.id] = '%s,-1' % type
                action2keywords[action_keyword.action.id].append(
                    action_keyword)

            Action = pool.get(type)
            factions = Action.search([
                    ('action', 'in', list(action2keywords.keys())),
                    ])
            for action in factions:
                for action_keyword in action2keywords[action.id]:
                    actions[action_keyword.model.id] = str(action)
        return actions

    @classmethod
    def set_action(cls, menus, name, value):
        pool = Pool()
        ActionKeyword = pool.get('ir.action.keyword')
        action_keywords = ActionKeyword.search([
            ('keyword', '=', 'tree_open'),
            ('model', 'in', [str(menu) for menu in menus]),
            ])
        if action_keywords:
            with Transaction().set_context(_timestamp=False):
                ActionKeyword.delete(action_keywords)
        if not value:
            return
        if isinstance(value, str):
            action_type, action_id = value.split(',')
        else:
            action_type, action_id = value
        if int(action_id) <= 0:
            return
        Action = pool.get(action_type)
        action = Action(int(action_id))
        to_create = []
        for menu in menus:
            with Transaction().set_context(_timestamp=False):
                to_create.append({
                        'keyword': 'tree_open',
                        'model': str(menu),
                        'action': action.action.id,
                        })
        if to_create:
            ActionKeyword.create(to_create)

    @classmethod
    def get_favorite(cls, menus, name):
        pool = Pool()
        Favorite = pool.get('ir.ui.menu.favorite')
        user = Transaction().user
        favorites = Favorite.search([
                ('menu', 'in', [m.id for m in menus]),
                ('user', '=', user),
                ])
        menu2favorite = {
            m.id: False if m.action_keywords else None for m in menus}
        menu2favorite.update(dict((f.menu.id, True) for f in favorites))
        return menu2favorite


class UIMenuGroup(ModelSQL):
    __name__ = 'ir.ui.menu-res.group'
    menu = fields.Many2One(
        'ir.ui.menu', "Menu", ondelete='CASCADE', required=True)
    group = fields.Many2One(
        'res.group', "Group", ondelete='CASCADE', required=True)


class UIMenuFavorite(ModelSQL):
    __name__ = 'ir.ui.menu.favorite'

    menu = fields.Many2One('ir.ui.menu', 'Menu', required=True,
        ondelete='CASCADE')
    user = fields.Many2One('res.user', 'User', required=True,
        ondelete='CASCADE')

    @classmethod
    def __setup__(cls):
        super().__setup__()
        cls.__rpc__.update({
                'get': RPC(check_access=False),
                'set': RPC(check_access=False, readonly=False),
                'unset': RPC(check_access=False, readonly=False),
                })

    @staticmethod
    def default_user():
        return Transaction().user

    @classmethod
    def get(cls):
        user = Transaction().user
        favorites = cls.search([
                ('user', '=', user),
                ],
            order=[('id', 'DESC')])
        return list(OrderedDict.fromkeys(
                (f.menu.id, f.menu.rec_name, f.menu.icon) for f in favorites))

    @classmethod
    def set(cls, menu_id):
        user = Transaction().user
        cls.create([{
                    'menu': menu_id,
                    'user': user,
                    }])

    @classmethod
    def unset(cls, menu_id):
        user = Transaction().user
        favorites = cls.search([
                ('menu', '=', menu_id),
                ('user', '=', user),
                ])
        cls.delete(favorites)
