#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import sys
import traceback
from xdg import BaseDirectory
from configparser import ConfigParser
from datetime import datetime

import requests
import json

VERSION = '0.3.2'
APP_NAME = 'Nightscout Menubar'
PROJECT_HOMEPAGE = 'https://github.com/mddub/nightscout-osx-menubar'

SGVS_PATH = '/api/v1/entries/sgv.json?count={count}'
UPDATE_FREQUENCY_SECONDS = 20
MAX_SECONDS_TO_SHOW_DELTA = 600
HISTORY_LENGTH = 2
MAX_BAD_REQUEST_ATTEMPTS = 3
REQUEST_TIMEOUT_SECONDS = 2
LONG_OUTPUT=0

################################################################################
# Display options

MENUBAR_TEXT = u"{sgv} {direction} {delta} [{time_ago}]"
MENU_ITEM_TEXT = u"{sgv} {direction} {delta} [{time_ago}]"


# def time_ago(seconds):
#     if seconds >= 3600:
#         return "%sh" % (seconds / 3600)
#     elif seconds >= 60:
#         return "%sm" % (seconds / 60)
#     else:
#         return "%ss" % seconds

def time_ago(seconds):
    if seconds >= 3600:
        return "{:0.2f}h".format(seconds / 3600)
    elif seconds >= 60:
        return "{:0.1f}m".format(seconds / 60)
    else:
        return "{:0.0f}s".format(seconds / 1.0)

################################################################################


class NightscoutException(Exception):
    pass


class NightscoutConfig(object):
    FILENAME = 'config'
    SECTION = 'NightscoutMenubar'
    HOST = 'nightscout_host'
    USE_MMOL = 'use_mmol'

    def __init__(self, app_name):
        self.config_path = os.path.join(BaseDirectory.save_config_path(app_name), self.FILENAME)
        self.config = ConfigParser()
        self.config.read([self.config_path])
        if not self.config.has_section(self.SECTION):
            self.config.add_section(self.SECTION)
        if not self.config.has_option(self.SECTION, self.HOST):
            self.set_host('')
        if not self.config.has_option(self.SECTION, self.USE_MMOL):
            self.set_use_mmol(False)

    def get_host(self):
        return self.config.get(self.SECTION, self.HOST)

    def set_host(self, host):
        self.config.set(self.SECTION, self.HOST, host)
        with open(self.config_path, 'w') as f:
            self.config.write(f)

    def get_use_mmol(self):
        return bool(self.config.get(self.SECTION, self.USE_MMOL))

    def set_use_mmol(self, mmol):
        self.config.set(self.SECTION, self.USE_MMOL, 'true' if mmol else 'false')
        with open(self.config_path, 'w') as f:
            self.config.write(f)

config = NightscoutConfig(APP_NAME)


def maybe_convert_units(mgdl):
    return round(mgdl / 18.0, 1) if config.get_use_mmol() else mgdl


def update_menu(title, items):
    if LONG_OUTPUT == 1:
        print(', '.join([title] + items))
    print(title)


def get_entries(retries=0, last_exception=None):
    if retries >= MAX_BAD_REQUEST_ATTEMPTS:
        print("Retried too many times: %s" % last_exception)
        raise NightscoutException(last_exception)

    try:
        resp = requests.get(
            config.get_host() + SGVS_PATH.format(count=(HISTORY_LENGTH + 2)),
            # Don't let bad connectivity cause the app to freeze
            timeout=REQUEST_TIMEOUT_SECONDS,
        )
    except requests.exceptions.Timeout as e:
        # Don't retry timeouts, since the app is unresponsive while a request is in progress,
        # and a new request will be made in UPDATE_FREQUENCY_SECONDS seconds anyway.
        print("Timed out: %s" % repr(e))
        raise NightscoutException(repr(e))
    except requests.exceptions.RequestException as e:
        return get_entries(retries + 1, repr(e))

    if resp.status_code != 200:
        return get_entries(retries + 1, "Nightscout returned status %s" % resp.status_code)

    try:
        arr = resp.json()
        if type(arr) == list and (len(arr) == 0 or type(arr[0]) == dict):
            return arr
        else:
            return get_entries(retries + 1, "Nightscout returned bad data")
    except json.scanner.JSONDecodeError:
        return get_entries(retries + 1, "Nightscout returned bad JSON")


def filter_bgs(entries):
    bgs = [e.copy() for e in entries if 'sgv' in e]
    for bg in bgs:
        bg['sgv'] = int(bg['sgv'])
    return bgs


def seconds_ago(timestamp):
    return int(datetime.now().strftime('%s')) - timestamp / 1000


def get_direction(entry):
    return {
        'DoubleUp': u'⇈',
        'SingleUp': u'↑',
        'FortyFiveUp': u'↗',
        'Flat': u'→',
        'FortyFiveDown': u'↘',
        'SingleDown': u'↓',
        'DoubleDown': u'⇊',
    }.get(entry.get('direction'), '-')


def get_delta(last, second_to_last):
    posneg = '+' if last['sgv'] >= second_to_last['sgv'] else u'−'
    return posneg + str(abs(maybe_convert_units(last['sgv'] - second_to_last['sgv'])))


def get_menubar_text(entries):
    bgs = filter_bgs(entries)
    last, second_to_last = bgs[0:2]
    if (last['date'] - second_to_last['date']) / 1000 <= MAX_SECONDS_TO_SHOW_DELTA:
        delta = get_delta(last, second_to_last)
    else:
        delta = '?'
    return MENUBAR_TEXT.format(
        sgv=maybe_convert_units(last['sgv']),
        delta=delta,
        direction=get_direction(last),
        time_ago=time_ago(seconds_ago(last['date'])),
    )


def get_history_menu_items(entries):
    bgs = filter_bgs(entries)
    return [
        MENU_ITEM_TEXT.format(
            sgv=maybe_convert_units(e['sgv']),
            delta=get_delta(e, bgs[i + 1]) if i + 1 < len(bgs) else '?',
            direction=get_direction(e),
            time_ago=time_ago(seconds_ago(e['date'])),
        )
        for i, e in enumerate(bgs)
    ][1:HISTORY_LENGTH + 1]


def update_data(sender):
    entries = None
    try:
        try:
            entries = get_entries()
        except NightscoutException as e:
            if config.get_host():
                update_menu("<?>", [])
            else:
                update_menu("<Need settings>", [])
        else:
            update_menu(get_menubar_text(entries), get_history_menu_items(entries))
    except Exception as e:
        print("Nightscout data: " + json.dumps(entries))
        print(repr(e))
        _, _, tb = sys.exc_info()
        traceback.print_tb(tb)
        update_menu("<!>", [repr(e)[:100]])

if __name__ == "__main__":
    update_data(None)
