import sys
import json
import time

import core.theme
import core.event

def dump_json(obj):
    return obj.dict()

def assign(src, dst, key, src_key=None, default=None):
    if not src_key:
        src_key = key.replace('_', '-') # automagically replace - with _

    for k in src_key if isinstance(src_key, list) else [src_key]:
        if k in src:
            dst[key] = src[k]
            return
    if default is not None:
        dst[key] = default

class block(object):
    __COMMON_THEME_FIELDS = [
        'separator', 'separator-block-width', 'default-separators',
        'border-top', 'border-left', 'border-right', 'border-bottom',
        'fg', 'bg', 'padding', 'prefix', 'suffix'
    ]
    def __init__(self, theme, module, widget):
        self.__attributes = {}
        for key in self.__COMMON_THEME_FIELDS:
            tmp = theme.get(key, widget)
            if tmp is not None:
                self.__attributes[key] = tmp

        self.__attributes['name'] = module.id
        self.__attributes['instance'] = widget.id
        self.__attributes['prev-bg'] = theme.get('bg', 'previous')

    def set(self, key, value):
        self.__attributes[key] = value

    def is_pango(self, attr):
        if isinstance(attr, dict) and 'pango' in attr:
            return True
        return False

    def pangoize(self, text):
        if not self.is_pango(text):
            return text
        self.__attributes['markup'] = 'pango'
        attr = dict(text['pango'])
        text = attr.get('full_text', '')
        if 'full_text' in attr:
            del attr['full_text']
        result = '<span'
        for key, value in attr.items():
            result = '{} {}="{}"'.format(result, key, value)
        result = '{}>{}</span>'.format(result, text)
        return result

    def dict(self):
        result = {}

        assign(self.__attributes, result, 'full_text', ['full_text', 'separator'])
        assign(self.__attributes, result, 'separator', 'default-separators')

        if '_decorator' in self.__attributes:
            assign(self.__attributes, result, 'color', 'bg')
            assign(self.__attributes, result, 'background', 'prev-bg')
            result['_decorator'] = True
        else:
            assign(self.__attributes, result, 'color', 'fg')
            assign(self.__attributes, result, 'background', 'bg')

        if 'full_text' in self.__attributes:
            result['full_text'] = self.pangoize(result['full_text'])
            result['full_text'] = self.__format(self.__attributes['full_text'])

        for k in [
            'name', 'instance', 'separator_block_width', 'border', 'border_top',
            'border_bottom', 'border_left', 'border_right', 'markup'
        ]:
            assign(self.__attributes, result, k)

        return result

    def __pad(self, text):
        padding = self.__attributes.get('padding', '')
        if not text: return padding
        return '{}{}{}'.format(padding, text, padding)

    def __format(self, text):
        if text is None: return None
        prefix = self.pangoize(self.__attributes.get('prefix'))
        suffix = self.pangoize(self.__attributes.get('suffix'))
        return '{}{}{}'.format(
            self.__pad(prefix),
            text,
            self.__pad(suffix)
        )

class i3(object):
    def __init__(self, theme=core.theme.Theme(), config=core.config.Config([])):
        self.__modules = []
        self.__content = {}
        self.__theme = theme
        self.__config = config
        core.event.register('start', self.draw, 'start')
        core.event.register('update', self.draw, 'statusline')
        core.event.register('stop', self.draw, 'stop')

    def theme(self, new_theme=None):
        if new_theme:
            self.__theme = new_theme
        return self.__theme

    def modules(self, modules=None):
        if not modules:
            return self.__modules
        self.__modules = modules if isinstance(modules, list) else [ modules ]

    def draw(self, what, args=None):
        cb = getattr(self, what)
        data = cb(args) if args else cb()
        if 'blocks' in data:
            sys.stdout.write(json.dumps(data['blocks'], default=dump_json))
        if 'suffix' in data:
            sys.stdout.write(data['suffix'])
        sys.stdout.write('\n')
        sys.stdout.flush()

    def start(self):
        return {
            'blocks': { 'version': 1, 'click_events': True },
            'suffix': '\n[',
        }

    def stop(self):
        return { 'suffix': '\n]' }

    def __separator_block(self, module, widget):
        if not self.__theme.get('separator'):
            return []
        blk = block(self.__theme, module, widget)
        blk.set('_decorator', True)
        return [blk]

    def __content_block(self, module, widget):
        blk = block(self.__theme, module, widget)
        blk.set('min_width', widget.get('theme.minwidth'))
        blk.set('full_text', self.__content[widget])
        if self.__config.debug():
            blk.set('__state', ', '.join(module.state(widget)))
        return blk

    def blocks(self, module):
        blocks = []
        for widget in module.widgets():
            if widget.module() and self.__config.autohide(widget.module().name()):
                if not any(state in widget.state() for state in [ 'warning', 'critical']):
                    continue
            blocks.extend(self.__separator_block(module, widget))
            blocks.append(self.__content_block(module, widget))
            core.event.trigger('next-widget')
        return blocks

    # TODO: only updates full text, not the state!?
    def update(self, affected_modules=None):
        now = time.time()
        for module in self.__modules:
            if affected_modules and not module.id in affected_modules:
                continue
            if not affected_modules and module.next_update:
                if now < module.next_update:
                    continue
            module.update_wrapper()
            module.next_update = now + float(module.parameter('interval', self.__config.interval()))
            for widget in module.widgets():
                self.__content[widget] = widget.full_text()

    def statusline(self):
        blocks = []
        for module in self.__modules:
            blocks.extend(self.blocks(module))
        return {
            'blocks': blocks,
            'suffix': ','
        }

    def wait(self, interval):
        time.sleep(interval)

# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4