import inspect
import logging
import os
import re
import time

import gi
gi.require_version('Wnck', '3.0')
gi.require_version('Gdk', '3.0')
gi.require_version('Gtk', '3.0')
gi.require_version('Keybinder', '3.0')

from gi.repository import Wnck, Gtk, Keybinder, Gdk
from .autogenerated import IfWindow, WnckWindowActions, GdkWindowActions
try:
    from .xtest import press, click
except ModuleNotFoundError:
    press, click = None, None

scr = Wnck.Screen.get_default()
gdk_screen = Gdk.Screen.get_default()
kb = Keybinder
bindings = []
bound = []
logger = logging.getLogger()


class Match:
    def __init__(self, cfg):
        if not cfg:
            cfg = {'is_true': cfg}
        elif not isinstance(cfg, dict):
            cfg = {'eq': cfg}
        # for k, v in list(cfg.items()):
        #     if k == 'not':
        #         for k2, v2 in v.items():
        #             cfg[f'{k2}_not'] = v2
        #         del cfg['not']

        for k in cfg:
            if not hasattr(self, k):
                vals = ', '.join(f for f in self.__class__.__dict__ if not f.startswith('_'))
                logger.error(f'Wrong condition {k}. Possible values: {vals}')
        self.cfg = cfg
    def __call__(self, val):
        for k, v in self.cfg.items():
            if not getattr(self, k)(v, val):
                return False
        return True
    def is_true(self, val, s):
        ''' is is_true'''
        return bool(s)
    def is_false(self, val, s):
        ''' false '''
        return not bool(s)
    # def is_true_not(self, val, s):
    #     ''' false '''
    #     return not bool(s)
    def contains(self, val, s):
        ''' contains a substring '''
        s = str(s)
        return val in s
    def contains_not(self, val, s):
        ''' dons't contain a substring '''
        s = str(s)
        return val not in s
    def eq(self, val, k):
        ''' equals  '''
        val = type(k)(val)
        return k == val
    def ne(self, val, k):
        ''' not equal  '''
        val = type(k)(val)
        return k != val
    # def eq_not(self, val, k):
    #     ''' not equal  '''
    #     val = type(k)(val)
    #     return k != val
    def re(self, val, k):
        ''' regexp '''
        return bool(re.search(val, str(k)))
    # def re_not(self, val, k):
    #     ''' doesn't match regexp '''
    #     return not self.re(val, k)
    def icontains(self, val, s):
        ''' contains a substring, ignore case '''
        return val.lower() in str(s).lower()
    def icontains_not(self, val, s):
        ''' doesn't contain a substring, ignore case '''
        return val.lower() in str(s).lower()
    def ieq(self, val, k):
        ''' equals, ignore case '''
        return str(k).lower() == val.lower()
    def ine(self, val, k):
        ''' not equal, ignore case '''
        return str(k).lower() != val.lower()
    def ieq_not(self, val, k):
        ''' not equal, ignore case '''
        return str(k).lower() != val.lower()
    def ire(self, val, k):
        ''' regexp, ignore case '''
        return bool(re.search(val, str(k), re.IGNORECASE))
    def ire_not(self, val, k):
        ''' doesn't match regexp, ignore case '''
        return not self.ire(val, k)


def apply(func, arg):
    try:
        return func(arg)
    except Exception as e:
        logger.exception(e)




class If(IfWindow):

    def __init__(self, cfg, then, rule_name, rules):
        self.enabled = bool(cfg.pop('enabled', True))
        self.rules = rules
        self.then = then
        self.rule_name = rule_name
        self.event = cfg.pop('event', 'active_window_changed')
        if 'key' in cfg:
            key = cfg.pop('key')
            bindings.append((self, key, then))
            self.event = 'key_pressed'
        self.conditions = self._parse_config(cfg)

    def __str__(self):
        return self.rule_name

    def _parse_config(self, cfg):
        rule_list = []
        for k, v in cfg.items():
            if k in ('and', 'or'):
                rule_list.append(({'and': self._and, 'or': self._or}[k], self._parse_config(v)))
            elif k in ('sh', 'py'):
                rule_list.append((getattr(self, k), v))
            elif hasattr(self, k):
                rule_list.append((Match(v), getattr(self, k)))
            else:
                rule_list.append((Match(v), self._custom(k)))
        return rule_list


    def _cb(self, event, *args):
        if event == self.event and self():
            return self.then()

    def __call__(self):
        return self.enabled and self._and(self.conditions)

    @staticmethod
    def _and(conditions):
        return all(apply(k, v() if callable(v) else v) for k, v in conditions)

    @staticmethod
    def _or(conditions):
        return any(apply(k, v() if callable(v) else v) for k, v in conditions)

    def rule_enabled(self, rule_name):
        ''' If rule is enabled '''
        return self.rules[rule_name].enabled

    @staticmethod
    def sh(cmd):
        ''' If shell comannd has exitcode 0 '''
        return not os.system(cmd)

    @staticmethod
    def py(cmd):
        ''' Eval python code '''
        return eval(compile(cmd, 'angelspie.py', 'exec'))

    def _custom(self, key):
        ''' run chain of methods like scr.get_active_window.get_application.get_name '''
        obj_map = {'scr': lambda: scr, 'gdk_scr': Gdk.Screen.get_default, 'win': scr.get_active_window,
                   'ws': scr.get_active_workspace, 'gdk_win': lambda: Gdk.Screen.get_default().get_active_window()}
        keys = key.split('.')
        def cb():
            obj = obj_map[keys[0]]()
            for key in keys[1:]:
                obj = getattr(obj, key)()
            return str(obj)
        return cb

    def _vars(self):
        return {k: getattr(self, k)() for k, _ in inspect.getmembers(self.__class__) if not k.startswith('_') and k not in self.__class__.__dict__}


class Then(WnckWindowActions, GdkWindowActions):
    def __init__(self, cfg, rule_name, rules):
        self.rule_name = rule_name
        self.rules = rules
        cfg = [cfg] if hasattr(cfg, 'items') else list(map(dict, cfg))
        for action in cfg:
            for k in action:
                if not hasattr(self, k):
                    vals = ', '.join(self._actions())
                    logger.error(f'Wrong action {k}. Possible values: {vals}')
        self.cfg = cfg

    def _vars(self):
        return self.rules[self.rule_name]._vars()

    def _actions(self):
        return [f for f, _ in inspect.getmembers(self) if not f.startswith('_')]

    def __call__(self):
        for cfg in self.cfg:
            for k, v in cfg.items():
                getattr(self, k)(v)
        return True

    def sh(self, cmd):
        ''' Run shell command '''
        os.system(cmd)

    def echo(self, s):
        ''' Print to stdout '''
        print(s.format(**self._vars()))

    def debug(self, s):
        ''' Information about current window, etc '''
        print(repr(self._vars()))

    def py(self, s):
        ''' Run python code '''
        return eval(compile(s, 'angelspie.py', 'exec'))

    def sleep(self, s):
        ''' Sleep'''
        print(s)
        time.sleep(float(s))
        print(1)

    def press(self, key):
        ''' Emulate keypress '''
        if press is None:
            logger.error('Press function requires python-xlib library')
            return
        for key in key.split():
            press(key)

    def click(self, button):
        ''' Emulate mouse click '''
        if click is None:
            logger.error('Click function requires python-xlib library')
            return
        click(int(button))

    def trigger(self, rule_name):
        self.rules[rule_name].then()

    def enable(self, rule_name):
        self.rules[rule_name].enabled = True
        rebind()

    def disable(self, rule_name):
        self.rules[rule_name].enabled = False
        rebind()


def rebind(*args):
    while bound:
        kb.unbind(bound.pop())
    for condition, key, then in bindings:
        if condition():
            bound.append(key)
            kb.bind(key, lambda key: then())
