#!/usr/bin/env python3

import os
import signal
from tempfile import mkstemp
import time
import threading
from subprocess import Popen, PIPE
import select

import gi

gi.require_version('Gtk', '3.0')
gi.require_version('AppIndicator3', '0.1')
from gi.repository import GLib, Gtk
from gi.repository import AppIndicator3 as appindicator


GREEN_PIPE = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 14 16"
shape-rendering="crispEdges"><path stroke="rgba(0,0,0,0.5019607843137255)" d="M4 0h6M2
1h2M10 1h2M0 2h2M12 2h2M0 3h1M13 3h1M0 4h1M13 4h1M0 5h1M13 5h1M0 6h1M13 6h1M1 7h1M12
7h1M1 8h1M12 8h1M1 9h1M12 9h1M1 10h1M12 10h1M1 11h1M12 11h1M1 12h1M12 12h1M1 13h2M11
13h2M2 14h2M10 14h2M3 15h8" /> <path stroke="#88f888" d="M4 1h6M2 2h2M10 2h2M2 3h1M11
3h1M2 4h2M10 4h1M2 5h8M2 6h2M9 6h1M3 7h1M9 7h1M3 9h2M3 10h2M8 10h1M3 11h2M8 11h1M3
12h2M8 12h1M3 13h2M8 13h1M4 14h1M8 14h1" /> <path stroke="#000000" d="M4 2h6M3 3h8M4
4h6M2 7h1M11 7h1M3 8h2M9 8h2M5 9h4" /> <path stroke="#58d058" d="M1 3h1M1 4h1M1 5h1M1
6h1M4 6h4M4 7h4M2 8h1M5 8h3M2 9h1M2 10h1M5 10h2M2 11h1M5 11h2M2 12h1M5 12h2M5 13h2M5
14h2" /> <path stroke="#28a028" d="M12 3h1M11 4h2M10 5h3M8 6h1M10 6h3M8 7h1M10 7h1M8
8h1M11 8h1M9 9h3M7 10h1M9 10h3M7 11h1M9 11h3M7 12h1M9 12h3M7 13h1M9 13h2M7 14h1M9 14h1"
/> </svg>
"""

YELLOW_PIPE = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 14 16"
shape-rendering="crispEdges"><path stroke="rgba(0,0,0,0.5019607843137255)" d="M4 0h6M2
1h2M10 1h2M0 2h2M12 2h2M0 3h1M13 3h1M0 4h1M13 4h1M0 5h1M13 5h1M0 6h1M13 6h1M1 7h1M12
7h1M1 8h1M12 8h1M1 9h1M12 9h1M1 10h1M12 10h1M1 11h1M12 11h1M1 12h1M12 12h1M1 13h2M11
13h2M2 14h2M10 14h2M3 15h8" /> <path stroke="#f8e2bf" d="M4 1h6M2 2h2M10 2h2M2 3h1M11
3h1M2 4h2M10 4h1M2 5h8M2 6h2M9 6h1M3 7h1M9 7h1M3 9h2M3 10h2M8 10h1M3 11h2M8 11h1M3
12h2M8 12h1M3 13h2M8 13h1M4 14h1M8 14h1" /> <path stroke="#000000" d="M4 2h6M3 3h8M4
4h6M2 7h1M11 7h1M3 8h2M9 8h2M5 9h4" /> <path stroke="#f2ca89" d="M1 3h1M1 4h1M1 5h1M1
6h1M4 6h4M4 7h4M2 8h1M5 8h3M2 9h1M2 10h1M5 10h2M2 11h1M5 11h2M2 12h1M5 12h2M5 13h2M5
14h2" /> <path stroke="#ebab44" d="M12 3h1M11 4h2M10 5h3M8 6h1M10 6h3M8 7h1M10 7h1M8
8h1M11 8h1M9 9h3M7 10h1M9 10h3M7 11h1M9 11h3M7 12h1M9 12h3M7 13h1M9 13h2M7 14h1M9 14h1"
/> </svg>"""

RED_PIPE = """<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -0.5 14 16"
shape-rendering="crispEdges"><path stroke="rgba(0,0,0,0.5019607843137255)" d="M4 0h6M2
1h2M10 1h2M0 2h2M12 2h2M0 3h1M13 3h1M0 4h1M13 4h1M0 5h1M13 5h1M0 6h1M13 6h1M1 7h1M12
7h1M1 8h1M12 8h1M1 9h1M12 9h1M1 10h1M12 10h1M1 11h1M12 11h1M1 12h1M12 12h1M1 13h2M11
13h2M2 14h2M10 14h2M3 15h8" /> <path stroke="#eac1c1" d="M4 1h6M2 2h2M10 2h2M2 3h1M11
3h1M2 4h2M10 4h1M2 5h8M2 6h2M9 6h1M3 7h1M9 7h1M3 9h2M3 10h2M8 10h1M3 11h2M8 11h1M3
12h2M8 12h1M3 13h2M8 13h1M4 14h1M8 14h1" /> <path stroke="#000000" d="M4 2h6M3 3h8M4
4h6M2 7h1M11 7h1M3 8h2M9 8h2M5 9h4" /> <path stroke="#d58282" d="M1 3h1M1 4h1M1 5h1M1
6h1M4 6h4M4 7h4M2 8h1M5 8h3M2 9h1M2 10h1M5 10h2M2 11h1M5 11h2M2 12h1M5 12h2M5 13h2M5
14h2" /> <path stroke="#ba3e3e" d="M12 3h1M11 4h2M10 5h3M8 6h1M10 6h3M8 7h1M10 7h1M8
8h1M11 8h1M9 9h3M7 10h1M9 10h3M7 11h1M9 11h3M7 12h1M9 12h3M7 13h1M9 13h2M7 14h1M9 14h1"
/> </svg>"""


class Tubes(object):
    def __init__(self):
        self.quitting = threading.Event()
        self.event = threading.Event()

        self.indicator = appindicator.Indicator.new(
            id='are-the-tubes-clogged',
            icon_name=red_icon_tempfile,
            category=appindicator.IndicatorCategory.SYSTEM_SERVICES,
        )
        self.indicator.set_status(appindicator.IndicatorStatus.ACTIVE)
        self.menu = Gtk.Menu()
        self.ping_menu_item = Gtk.MenuItem(label='')
        self.availability_menu_item_1m = Gtk.MenuItem(label='')
        self.availability_menu_item_5m = Gtk.MenuItem(label='')
        self.availability_menu_item_15m = Gtk.MenuItem(label='')
        self.quit_menu_item = Gtk.MenuItem(label='Quit')
        self.menu.append(self.ping_menu_item)
        self.menu.append(self.availability_menu_item_1m)
        self.menu.append(self.availability_menu_item_5m)
        self.menu.append(self.availability_menu_item_15m)
        self.menu.append(self.quit_menu_item)
        self.quit_menu_item.connect('activate', self.quit)
        self.menu.show_all()
        self.indicator.set_menu(self.menu)

        self.internet_thread = threading.Thread(target=self.check_internet)
        self.internet_thread.daemon = True
        self.internet_thread.start()

        self.internet_subproc = None
        self.mainloop_thread = threading.Thread(target=self.mainloop)
        self.mainloop_thread.start()

    def check_internet(self):
        while not self.quitting.is_set():
            self.internet_subproc = Popen(['ping', '8.8.8.8'], stdout=PIPE, stderr=PIPE)
            stdout_fd = self.internet_subproc.stdout.fileno()
            stderr_fd = self.internet_subproc.stderr.fileno()
            while not self.quitting.is_set() and self.internet_subproc.poll() is None:
                read_ready, _, _ = select.select([stdout_fd, stderr_fd], [], [])
                for fd in read_ready:
                    if fd == stdout_fd:
                        line = self.internet_subproc.stdout.readline().decode('utf8')
                        if 'bytes from' in line:
                            self.event.set()
                    elif fd == stderr_fd:
                        line = self.internet_subproc.stderr.readline().decode('utf8')
                    GLib.idle_add(self.update_ping_menu_item, line.strip())
            if not self.quitting.is_set():
                time.sleep(1)

    def set_icon(self, icon):
        self.indicator.set_icon_full(icon, icon_descriptions[icon])

    def update_ping_menu_item(self, text):
        self.ping_menu_item.set_label(text)

    def update_availabity_menu_items(self, avail_1m, avail_5m, avail_15m):
        self.availability_menu_item_1m.set_label(
            f"Last 1 min availability: {avail_1m:.00f}%"
        )
        self.availability_menu_item_5m.set_label(
            f"Last 5 min availability: {avail_5m:.00f}%"
        )
        self.availability_menu_item_15m.set_label(
            f"Last 15 min availability: {avail_15m:.00f}%"
        )

    def mainloop(self):

        one_min_availability = 100
        five_min_availability = 100
        fifteen_min_availability = 100

        timeouts = 1
        while not self.quitting.is_set():
            start_time = time.monotonic()
            success = self.event.wait(1.5)
            interval = time.monotonic() - start_time
            if interval > 2:
                # Computer was sleeping or something, don't let this affect
                # the availability stats:
                interval = 1.5
            # Exponential averaging at three timescales:
            k_1 = interval / 60
            k_5 = interval / (5 * 60)
            k_15 = interval / (15 * 60)
            one_min_availability = (
                k_1 * 100 * success + (1 - k_1) * one_min_availability
            )
            five_min_availability = (
                k_5 * 100 * success + (1 - k_5) * five_min_availability
            )
            fifteen_min_availability = (
                k_15 * 100 * success + (1 - k_15) * fifteen_min_availability
            )

            GLib.idle_add(
                self.update_availabity_menu_items,
                one_min_availability,
                five_min_availability,
                fifteen_min_availability,
            )
            if success:
                self.event.clear()
                if timeouts:
                    GLib.idle_add(self.set_icon, green_icon_tempfile)
                timeouts = 0
            else:
                timeouts += 1
                if timeouts == 1:
                    GLib.idle_add(self.set_icon, yellow_icon_tempfile)
                elif timeouts == 2:
                    GLib.idle_add(self.set_icon, red_icon_tempfile)

    def quit(self, *args):
        self.quitting.set()
        self.event.set()
        self.mainloop_thread.join()
        try:
            self.internet_subproc.terminate()
        except OSError:
            pass
        self.internet_thread.join()
        Gtk.main_quit()


if __name__ == '__main__':
    # Make files in /tmp/ for the icons:
    green_icon_fd, green_icon_tempfile = mkstemp(suffix='.svg')
    yellow_icon_fd, yellow_icon_tempfile = mkstemp(suffix='.svg')
    red_icon_fd, red_icon_tempfile = mkstemp(suffix='.svg')

    icon_data = {
        green_icon_fd: GREEN_PIPE,
        yellow_icon_fd: YELLOW_PIPE,
        red_icon_fd: RED_PIPE,
    }

    icon_descriptions = {
        green_icon_tempfile: "Green pipe",
        yellow_icon_tempfile: "Yellow pipe",
        red_icon_tempfile: "Red pipe",
    }

    for fd, svg_data in icon_data.items():
        with os.fdopen(fd, 'w') as f:
            f.write(svg_data)

    tubes = Tubes()

    def signal_handler(*_):
        tubes.quitting.set()
        GLib.idle_add(tubes.quit)

    for sig in (signal.SIGINT, signal.SIGTERM, signal.SIGHUP):
        signal.signal(sig, signal_handler)

    Gtk.main()

    # Cleanup temp files:
    for filename in [green_icon_tempfile, yellow_icon_tempfile, red_icon_tempfile]:
        os.unlink(filename)
