"""webui -- mostly-automatically generate a simple web interface.

"""

import jinja2
import json
import logging
import datetime
import requests
import functools
from flask import Response, request
from idiotic import event, utils
from idiotic.item import Toggle, Trigger, Number, Motor, Text
from idiotic.scene import Scene

MODULE_NAME = "webui"

LOG = logging.getLogger("module.webui")

USE_GRAPHS = False

try:
    import pygal
    USE_GRAPHS = True
except:
    LOG.warning("Could not import pygal; graphs are disabled")

SECT_INCLUDE = ("include_tags",
                "exclude_tags",
                "include_items",
                "exclude_items")

include_tags = set()
exclude_tags = set()
default_graph = False
asset_path = None

env = None

template_args = {}

def configure(config, api, assets):
    global include_tags, exclude_tags, asset_path, env, template_args
    global default_graph

    template_args["title"] = config.get("page_title", "idiotic")
    template_args["root"] = api.path

    default_graph = config.get("enable_graph", False)

    asset_path = assets
    env = jinja2.Environment(loader=jinja2.FileSystemLoader(asset_path))

    api.add_url_rule('/main.js', '_main_js', _main_js)
    api.add_url_rule('/webui_conf.json', '__webui_conf', _webui_conf(config))
    api.add_url_rule('/sparkline/<item>.svg', '_sparkline', _sparkline)
    api.add_url_rule('/graph/<item>.svg', '_graph', _graph)

    traverse(api, "/", config)

    return

def traverse(api, path, tree):
    if not tree:
        return

    sections = []
    if "sections" in tree:
        sections = tree["sections"]
    else:
        sections = [{k: tree[k] for k in tree
                     if k in SECT_INCLUDE }]

    api.add_url_rule(path, path.replace('/', '_'), functools.partial(_main_page, sections))

    for key, val in tree.get("subpages", {}):
        traverse(api, utils.join_url(path, key), val)

def scene(name, command=None, **kwargs):
    scene = getattr(scenes, name)
    if command == "enter":
        scene.enter()
    elif command == "exit":
        scene.exit()
    elif command is not None:
        raise NameError("Command '{}' does not exist on scene {}".format(
            command, scene))
    return scene.active

def _main_page(sections, *_, **__):
    args = dict(template_args)
    return Response(env.get_template('main.html').render(**args), mimetype='text/html')

def _main_js(*_, **__):
    return Response(env.get_template('main.js').render(), mimetype='text/javascript')

def _webui_conf(config):
    def __webui_conf(*_, **__):
        return Response(json.dumps(config), mimetype='application/json')
    return __webui_conf

def __empty_svg():
    return """<?xml version="1.0" encoding="UTF-8" standalone="no"?> <svg
xmlns="http://www.w3.org/2000/svg" version="1.1" viewBox="0 0 1 1"
width="1" height="1" id="svg_pixel"> </svg>"""

def _sparkline(item, *args, **kwargs):
    if USE_GRAPHS:
        history = items[item].state_history
        if not history:
            return __empty_svg()

        _, values = zip(*history.last(min(10, len(history))))

        graph = pygal.Line(interpolate='cubic', style=pygal.style.LightStyle)
        graph.add("Last 10", values)
        return Response(graph.render_sparkline(height=25).decode('UTF-8'), mimetype='image/svg+xml')
    else:
        return Response(__empty_svg(), mimetype='image/svg+xml')

def __avg_time(datetimes):
    total = sum(dt.hour * 3600 + dt.minute * 60 + dt.second for dt in datetimes)
    avg = total / len(datetimes)
    minutes, seconds = divmod(int(avg), 60)
    hours, minutes = divmod(minutes, 60)
    return datetime.datetime.combine(datetime.date(1900, 1, 1), datetime.time(hours, minutes, seconds))

def __group(times, values, count=50, group=lambda v: sum(v)/len(v)):
    if len(times) != len(values):
        raise ValueError("times and values must have same length")

    if len(times) < count or len(times) < 2:
        return times, values

    count = min(count, len(times))

    diff = (times[-1] - times[0]) / count

    divisions = [times[0] + i * diff for i in range(count)] + [times[-1]]

    temp = []

    res = []

    partitions = zip(divisions[0:], divisions[1:])

    i = 0
    for start, end in partitions:
        while i < len(times) and start <= times[i] <= end:
            temp.append((times[i], values[i]))
            i += 1
        else:
            if temp:
                ts, vs = zip(*temp)
                res.append((__avg_time(ts), group(vs)))
            temp = []

    return zip(*res)

def _graph(item, *_, **kwargs):
    args = utils.single_args(request.args)

    time = args.get('time', 86400)
    offset = args.get('offset', 0)
    count = args.get('count', None)

    if USE_GRAPHS:
        history = items[item].state_history
        if not history:
            return Response(__empty_svg(), mimetype='image/svg+xml')

        if count:
            times, values = zip(*history.last(min(10, len(history))))
        else:
            times, values = zip(*history.since(datetime.datetime.now() - datetime.timedelta(seconds=int(time))))

        times, values = __group(times, values)

        graph = pygal.Line(style=pygal.style.LightStyle, x_label_rotation=-60)
        graph.title = items[item].name
        graph.add("Value", values)
        graph.x_labels = (t.strftime("%H:%M:%S") for t in times)
        return Response(graph.render().decode('UTF-8'), mimetype='image/svg+xml')
    else:
        return Response(__empty_svg(), mimetype='image/svg+xml')

def _include_item(item, include_tags, exclude_tags, include_items, exclude_items):
    return ((not include_tags or set(item.tags) & include_tags) or
            (item.name in include_items or
             utils.mangle_name(item.name) in include_items)) and \
            (not exclude_tags or not set(item.tags) & exclude_tags) and \
            (not exclude_items or (item.name not in exclude_items and
                                   utils.mangle_name(item.name) not in exclude_items))

@@ -1149,24 +1149,76 @@
 set_path))%0A%0A
+    api.add_url_rule('/', '_main_page', _main_page)%0A
     api.add_
@@ -1479,845 +1479,31 @@
     
-traverse(api, %22/%22, config)%0A%0A    return%0A%0Adef traverse(api, path, tree):%0A    if not tree:%0A        return%0A%0A    sections = %5B%5D%0A    if %22sections%22 in tree:%0A        sections = tree%5B%22sections%22%5D%0A    else:%0A        sections = %5B%7Bk: tree%5Bk%5D for k in tree%0A                     if k in SECT_INCLUDE %7D%5D%0A%0A    api.add_url_rule(path, path.replace('/', '_'), functools.partial(_main_page, sections))%0A%0A    for key, val in tree.get(%22subpages%22, %7B%7D):%0A        traverse(api, utils.join_url(path, key), val)%0A%0Adef scene(name, command=None, **kwargs):%0A    scene = getattr(scenes, name)%0A    if command == %22enter%22:%0A        scene.enter()%0A    elif command == %22exit%22:%0A        scene.exit()%0A    elif command is not None:%0A        raise NameError(%22Command '%7B%7D' does not exist on scene %7B%7D%22.format(%0A            command, scene))%0A    return scene.active%0A%0Adef _main_page(sections, 
+return%0A%0Adef _main_page(
 *_, 

Remove unused serving logic