#!/usr/bin/python2.7
import os
import traceback
import time
import sys
import pytz
import json
import threading
import shutil
import requests
from datetime import datetime, timedelta
from calendar import timegm
from select import select
from evdev import ecodes, InputDevice, list_devices
from evdev.events import KeyEvent
from hosted import CONFIG, NODE

SERIAL = os.environ['SERIAL']

devices = {}

def update_devices():
    new = set(list_devices("/dev/input/"))
    old = set(devices.keys())

    for device_name in new - old:
        devices[device_name] = InputDevice(device_name)

    for device_name in old - new:
        del devices[device_name]

def current_time():
    now = datetime.utcnow()
    timestamp = timegm(now.timetuple()) + now.microsecond / 1000000.
    return now, timestamp

def send_clock(now, ts):
    tz = pytz.timezone(CONFIG['timezone'])

    now = now.replace(tzinfo=pytz.utc)
    now = now.astimezone(tz)
    now = now.replace(tzinfo=None)

    since_midnight = (
        now -
        now.replace(hour=0, minute=0, second=0, microsecond=0)
    )
    since_midnight = since_midnight.seconds + since_midnight.microseconds / 1000000.

    monday = (now - timedelta(days = now.weekday())).replace(hour=0, minute=0, second=0, microsecond=0)
    since_monday = now - monday
    since_monday = since_monday.days * 86400 + since_monday.seconds + since_monday.microseconds / 1000000.

    NODE.send('/clock/since_midnight:%f' % since_midnight)
    NODE.send('/clock/since_monday:%f' % since_monday)
    NODE.send('/clock/human:%s' % now.strftime(CONFIG['time_fmt']))

def device_event(event):
    if event.type != ecodes.EV_KEY:
        return

    NODE.send('/input/event:%s' % json.dumps(dict(
        key = ecodes.KEY[event.code].replace("KEY_", "").lower(),
        action = {
            KeyEvent.key_up: "up",
            KeyEvent.key_down: "down",
            KeyEvent.key_hold: "hold",
        }[event.value],
    )))

def monitor_input():
    update_devices()
    print devices
    r, w, e = select(devices.values(), [], [], 1)
    for device in r:
        try:
            for ev in device.read():
                device_event(ev)
        except IOError:
            # device disconnected
            pass

def send_time():
    now, ts = current_time()
    if now.year < 2000:
        print >>sys.stderr, "too soon"
        time.sleep(1)
        return

    send_clock(now, ts)
    time.sleep(0.5)

def sync_remote():
    states = {}
    http = requests.Session()
    http.headers['User-Agent'] = "Poller device %s" % SERIAL

    old_pages = {}

    while 1:
        base_url = CONFIG['poll_url'].strip()
        if not base_url:
            time.sleep(1)
            return

        # In case of a configuration change, remove all locally
        # cached files and force a fresh download.
        new_pages = CONFIG.raw['pages']
        if new_pages != old_pages:
            print >>sys.stderr, "purging old remote files"
            for filename in os.listdir("."):
                if not filename.startswith("remote-"):
                    continue
                try:
                    os.unlink(filename)
                except:
                    pass
            old_pages = new_pages
            states = {}

        files = []
        for page in CONFIG['pages']:
            files.append((page['media']['filename'], page['media']['asset_name']))

        print >>sys.stderr, "probing new content from %s" % (base_url,)
        for filename, asset_name in files:
            state = states.setdefault(filename, {})

            headers = {}
            if state.get('etag'):
                headers['If-None-Match'] = state['etag']
            if state.get('last_mod'):
                headers['If-Modified-Since'] = state['last_mod']

            url = base_url + '/' + filename
            try:
                r = http.get(
                    url = url,
                    headers = headers,
                    stream = True,
                    timeout = 5,
                )
                r.raise_for_status()
                if r.status_code == 304:
                    print >>sys.stderr, "%s unchanged" % (url,)
                    continue
                state['etag'] = r.headers.get('etag')
                state['last_mod'] = r.headers.get('last-modified')
                print >>sys.stderr, "got new content for %s: %r" % (url, state)
                with file("download.temp", "wb") as out:
                    shutil.copyfileobj(r.raw, out)
                os.rename("download.temp", "remote-%s" % asset_name)
            except Exception, err:
                print >>sys.stderr, "cannot fetch %s: %s" % (url, err)
        time.sleep(5)

def run_in_thread(fn):
    def wrap():
        try:
            while 1:
                fn()
        except Exception:
            traceback.print_exc()
            os._exit(1)
    thread = threading.Thread(target=wrap)
    thread.daemon = True
    thread.start()

if __name__ == "__main__":
    run_in_thread(send_time)
    run_in_thread(monitor_input)
    run_in_thread(sync_remote)

    while 1: time.sleep(1000)
