#!/usr/bin/python
# Get rid of import * for Tkinter
# Click on the window to get some more information
#
# When I generate an event and Tk window doens't have focus, hangs
# until Tk gets focus and there's some sort of event (like mouse
# movement)
#
# When I generate an event and Tk window _has_ focus, hangs until some
# sort of window event (like mouse move)

with_tk = True
try: from Tkinter import *
except ImportError: with_tk = False
    
import signal
import sys 
import os
import popen2
import threading
import time
import datetime

class hook:
    def __init__(self):
        self._hooks = []
    def add(self, f):
        self._hooks.append(f)
    def remove(self, f):
        self._hooks.remove(f)
    def run(self,*a, **kw):
        return [f(*a, **kw) for f in self._hooks]

##############################
# Graphical class
if with_tk:
    class PingApp(Frame):

        def createWidgets(self):
            self.canvas = Canvas(self)
            self.canvas['width'] = 30
            self.canvas['height'] = 30
            self.canvas['background'] = 'white'
            self.light = self.canvas.create_oval(5,5,30,30)
            self.canvas.itemconfigure(self.light, {'fill': 'green'})
            self.canvas.pack()

            self.oval_color = 'green'

            # Events
            self.bind('<<PingGood>>', self.UpdateGood)
            self.bind('<<PingBad>>', self.UpdateBad)
            self.bind('<<PingWarning>>', self.UpdateWarning)

        def UpdateGood(self, event):
            self.canvas.itemconfigure(self.light, {'fill': 'green'})
        def UpdateBad(self, event):
            self.canvas.itemconfigure(self.light, {'fill': 'red'})
        def UpdateWarning(self, event):
            self.canvas.itemconfigure(self.light, {'fill': 'yellow'})

        def StatusGood(self):
            self.event_generate('<<PingGood>>', when='tail')
        def StatusBad(self):
            self.event_generate('<<PingBad>>', when='tail')
        def StatusWarning(self):
            self.event_generate('<<PingWarning>>', when='tail')

        def __init__(self, master=None):
            Frame.__init__(self, master)
            self.pack()
            self.createWidgets()

def test():
    r = ResultCollector()
    r.add_success()
    r.add_failure()
    r.last(2)
    r.last_sum(2)
    r.last_ave(2)

    p = Ping('google.com')
    p.run()
    
    p = Ping('192.168.0.200')
    p.run()

    ps = PingStream()    
    ps.state = ps.good
    ps.results.results = [0 for i in range(200)]
    ps.update_stats()

    ps.state = ps.warning
    ps.results.results = [1 for i in range(200)]
    ps.update_stats()

    ps.state = ps.warning
    ps.results.results = [0 for i in range(200)]
    ps.update_stats()
    
    ps.state = ps.bad
    ps.results.results = [1 for i in range(200)]
    ps.update_stats()

    ps.state = ps.hope
    ps.results.results = [1 for i in range(200)]
    ps.update_stats()

    ps.state = ps.hope
    ps.results.results = [0 for i in range(200)]
    ps.update_stats()

class ResultCollector:
    def __init__(self):
        self.max_results = 150

        self.results = [1 for i in range(self.max_results)] 
        self.lock = threading.Lock()

    def add_success(self):
        self.lock.acquire()
        # trim results to required length
        self.results = self.results[1:] + [1]
        self.lock.release()
        
    def add_failure(self):
        self.lock.acquire()
        # trim results to required length
        self.results = self.results[1:] + [0]
        self.lock.release()

    def last(self, n):
        return self.results[-n:]

    def last_sum(self, n):
        return sum(self.results[-n:])

    def last_ave(self, n):
        return (1.0*sum(self.results[-n:])) / n
        
class Ping(threading.Thread):
    def __init__(self,host, successf=None, failf=None):
        threading.Thread.__init__(self)

        self.host = host
        self.interval = 1
        self.timeout = 10

        if successf is None: self.successf = self.print_success
        else: self.successf = successf

        if failf is None: self.failf = self.print_failure
        else: self.failf = failf
        
        self.commandline = 'ping -c 1 %s' % host
        self.setDaemon(True)
        
    def print_success(self): print 'Pinging ' + self.host + ' succeeded!'
    def print_failure(self): print 'Pinging ' + self.host + ' failed!'

    def examine_output(self, f):
        found_result = False
        for line in iter(f):
            if line.find("0 packets received") != -1:
                self.failf()
                found_result = True
            elif line.find("1 packets received") != -1:
                self.successf()
                found_result = True
        if not found_result:
            self.failf()                    
        
    def run(self):
        process = popen2.Popen3(self.commandline, capturestderr=True)
        
        iterations = 0
        while process.poll() == -1:
            if iterations >= self.timeout:
                os.kill(process.pid, signal.SIGKILL)
            iterations += 1
            time.sleep(self.interval)

        self.examine_output(process.fromchild)

        
class PingStream(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        
        # States
        self.good = 3
        self.warning = 2
        self.hope = 1
        self.bad = 0

        # Constants
        self.host = 'google.com'        
        self.interval = 1
        
        # Actual initialization
        self.state = self.good
        self.results = ResultCollector()
	if with_tk:
	        self.setDaemon(True)

	#nodes
        self.to_good_hook = hook()
        self.to_warning_hook = hook()
        self.to_hope_hook = hook()
        self.to_bad_hook = hook()

        self.from_good_hook = hook()
        self.from_warning_hook = hook()
        self.from_hope_hook = hook()
        self.from_bad_hook = hook()

	#edges 
	self.good_warning_hook = hook()
	self.warning_good_hook = hook()
	self.warning_bad_hook = hook()
	self.hope_good_hook = hook()
	self.hope_bad_hook = hook()
	self.bad_hope_hook = hook()

    def run(self):
    	iterations = 0        
        while True:
	    if iterations % 3600 == 0:
	         print "Still pinging at", datetime.datetime.today().ctime()
		 sys.stdout.flush()
            iterations += 1
            self.update_stats()
            time.sleep(self.interval)

    def update_stats(self):
        # Do one ping
        p = Ping(self.host, self.results.add_success, self.results.add_failure)
        p.start()
        
        # Decide on state changes
        # Don't update the state variable right away to avoid
        # triggering 'if' clauses further down...
        newstate = self.state 
        if self.state == self.good:
            if self.results.last_ave (20) < 0.9:
                newstate = self.warning
		self.from_warning_hook.run()
                self.good_warning_hook.run()
                self.to_warning_hook.run()
                
        if self.state == self.warning:
            if self.results.last_ave (20) > 0.9:
                newstate = self.good
		self.from_warning_hook.run()
                self.warning_good_hook.run()                
                self.to_good_hook.run()                
            if self.results.last_ave (60) < 0.5:
                newstate = self.bad		
		self.from_warning_hook.run()
		self.warning_bad_hook.run()
                self.to_bad_hook.run()


        if self.state == self.hope:
            if self.results.last_ave (20) < 0.5:
                newstate = self.bad
		self.from_hope_hook.run()
		self.hope_bad_hook.run()
                self.to_bad_hook.run()                
            if self.results.last_ave (60) > 0.9:
                newstate = self.good
		self.from_hope_hook.run()
		self.hope_good_hook.run()
                self.to_good_hook.run()                

        if self.state == self.bad:
            if self.results.last_ave (20) > 0.5:
                newstate = self.hope
		self.from_bad_hook.run()
		self.bad_hope_hook.run()
                self.to_hope_hook.run()                

        self.state = newstate

class PingLog:
    def __init__(self):
        self._time = time.time()
        
    def internet_down(self):
    	print "Internet is down!"
        self._time = time.time()
	sys.stdout.flush()
        
    def internet_up(self):
    	print "Internet is up!"
        now = time.time()
        secs = (now - self._time) % 60
        mins = ((now - self._time)/60) % 60
        hrs = (now - self._time)/3600         
        print datetime.datetime.today().ctime(), ":Internet was down for %d hours, %d minutes, and %d seconds" % (hrs, mins, secs)
        sys.stdout.flush()

stream = PingStream()

if with_tk:
    app = PingApp()
    stream.to_good_hook.add(app.StatusGood)
    stream.to_warning_hook.add(app.StatusWarning)
    stream.to_hope_hook.add(app.StatusWarning)
    stream.to_bad_hook.add(app.StatusBad)

log = PingLog()
stream.hope_good_hook.add(log.internet_up)
stream.warning_bad_hook.add(log.internet_down)


if with_tk:
    app.mainloop()
    stream.start()
else:
    stream.run()
