#!/bin/python
from __future__ import (unicode_literals, division, absolute_import, print_function)

import time
import re
import subprocess

from threading import Lock, Timer, Thread

from powerline.lemonbar import LemonbarPowerline, INTERNAL_BAR_COMMAND, SEGMENT_NAME
from powerline.commands.lemonbar import get_argparser
from powerline.bindings.wm import get_connected_xrandr_outputs

bars = {}
shell = None
powerline = None
lock = Lock()
args = []

segment_info={'payloads': {}}

needs_restart = []

def execute(process, shell, callback):
    for stdout_line in iter(process.stdout.readline, b''):
        line = stdout_line[0:stdout_line.rfind(SEGMENT_NAME)]
        segment_name = stdout_line[stdout_line.rfind(SEGMENT_NAME) + len(SEGMENT_NAME):-1]
        if shell:
            shell.stdin.write(line + b'\n')
            shell.stdin.flush()
        if callback:
            callback(line.decode(), segment_name.decode())
    process.stdout.close()

def render(reschedule=False):
    t = Timer(args.interval, render, kwargs={'reschedule': True})
    stamp = time.time()
    global lock
    try:
        for output in bars:
            process, thread, width = bars[output]
            with lock:
                process.stdin.write(powerline.render(mode=modes[0], width=width,
                    matcher_info=output, segment_info=segment_info).encode('utf-8') + b'\n')
                process.stdin.flush()
                if reschedule and not t.is_alive():
                    t.start()
    except BrokenPipeError: # The lemonbar died, so should we
        pass
    global needs_restart
    n_r = needs_restart
    if len(n_r):
        with lock:
            n_r = needs_restart
            needs_restart = []
        for output in n_r:
            restart_bar(output)
        render(reschedule=True)

def handle_bar_command(output_name, cmd, segment_name):
    if cmd.find('#') > 0:
        cmd = cmd[0:cmd.find('#')]

    # bar restarts
    if cmd == 'restart':
        restart_bar(output_name)
        render(reschedule=True)
    elif cmd.startswith('restart:'):
        restart_bar(cmd.split(':')[1])
        render(reschedule=True)
    # communication with segments
    elif cmd == 'ch_clear':
        # print('cleared comm chan {0}.'.format(segment_name))
        if segment_name:
            segment_info['payloads'].update({segment_name: None})
        render(reschedule=True)
    elif cmd == 'ch_fill':
        if segment_name:
            segment_info['payloads'].update({segment_name: True})
        render(reschedule=True)
    elif cmd == 'ch_toggle':
        if segment_name:
            if segment_name in segment_info['payloads'] and segment_info['payloads'][segment_name]:
                segment_info['payloads'].update({segment_name: None})
            else:
                segment_info['payloads'].update({segment_name: True})
        render(reschedule=True)
    elif cmd.startswith('pass:'):
        # print('passed {0} to comm chan {1}.'.format(cmd.split(':')[1], segment_name))
        if segment_name:
            segment_info['payloads'].update({segment_name: cmd.split(':')[1]})
        render(reschedule=True)
    elif cmd.startswith('pass_oneshot:'):
        if segment_name:
            import time
            segment_info['payloads'].update({segment_name: (cmd.split(':')[1], time.time())})
        render(reschedule=True)

def bar_callback(output_name, line, segment_name):
    # powerline.pl.info('click on {0} (segment {2}): {1}'.format(output_name, line, segment_name))
    # print('click on {0} (segment {2}): {1}'.format(output_name, line, segment_name))
    if line.startswith(INTERNAL_BAR_COMMAND.decode()):
        for cmd in line.split(';'):
            handle_bar_command(output_name, cmd, segment_name)

def restart_bar(output_name):
    if not powerline:
        return

    if output_name in bars:
        p, t, w = bars.pop(output_name)
        p.kill()

    for screen in get_connected_xrandr_outputs(powerline.pl):
        if screen['name'] != output_name:
            continue
        if not args.alt_output:
            command = [args.bar_command, '-g', '{0}x{1}+{2}+{3}'.format(screen['width'], args.height, screen['x'], screen['y'])] + args.args[1:]
        else:
            command = [args.bar_command, '-O', screen['name'], '-g', 'x{0}'.format(args.height)] + args.args[1:]
        process = subprocess.Popen(command, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        if not args.no_clicks:
            thr = Thread(target=execute, args=(process, shell, lambda x, y: bar_callback(screen['name'], x, y)))
        else:
            thr = Thread(target=execute, args=(process, shell, None))
        if not thr.is_alive():
            thr.start()

        bars.update({output_name: (process, thr, int(int(screen['width']) / (int(args.height) * 0.555)))})
        return

def restart_callback(output):
    global needs_restart
    needs_restart.extend([output])

if __name__ == '__main__':
    parser = get_argparser()
    args = parser.parse_args()

    powerline = LemonbarPowerline()
    powerline.update_renderer()
    if not args.no_clicks:
        shell = subprocess.Popen(['/bin/sh'], stdin=subprocess.PIPE, stdout=subprocess.PIPE)

    for screen in get_connected_xrandr_outputs(powerline.pl):
        restart_bar(screen['name'])

    modes = ['default']

    segment_info['restart'] = restart_callback

    def update(evt):
        modes[0] = evt.change
        render()

    render(reschedule=True)

    if args.i3:
        import i3ipc
        conn = i3ipc.Connection()
        while True:
            conn.on('workspace::focus', lambda conn, evt: render())
            conn.on('mode', lambda conn, evt: update(evt))
            render()
            conn.main()
            time.sleep(1) # If this gets executed, i3 got restarted / crashed / whatever
            conn = i3ipc.Connection()
            powerline = LemonbarPowerline()
            powerline.update_renderer()

    while True:
        time.sleep(1e8)
