#! /usr/bin/env python
# Author: Natan 'whatah' Zohar
# License: GPLv2 (at the moment)

# Watch out, there is a compressed glade file at the end.

import gobject
import gtk
import gtk.glade
import os
import signal
import subprocess
import sys
import threading
import time
import zlib

gtk.gdk.threads_init()

NEW         =   "NEW"
DELETE      =   "DELETE"
JOIN        =   "JOIN"
LEAVE       =   "LEAVE"
KICK        =   "KICK"
ONLINE      =   "logged in"
OFFLINE     =   "offline"
TIMEOUT     =   5
GLADE_DATA  =   None


class Output:
    def __init__(self, outfunc):
        self.output = outfunc
        
    def write(self, text):
        self.output(text)

class PersonObj(dict):
    def __getattr__(self, attr):
        return self[attr]
    
    def __setattr__(self, attr, value):
        self[attr] = value

class Executor:
    def kill_after_timeout(self, proc, timeout):
        while timeout > 0:
            time.sleep(1)
            if not proc.returncode is None:
                return
            timeout -= 1
        #TODO: use the windows kill API if we are not on *nix or mac. 
        os.kill(proc.pid, signal.SIGKILL)
        proc.timeout = True
    
    def execute(self, cmd, timeout):
        subproc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE)
        subproc.timeout = False

        # start the kill timer.
        killthread = threading.Thread(target=self.kill_after_timeout, args=[subproc, timeout])
        killthread.start()

        if not subproc.returncode:
            subproc.wait()
        if subproc.timeout:
            raise IOError("Program '%s' timed out after %s seconds" % (' '.join(cmd), timeout))
        
        output = subproc.stdout.read()
        self.returncode = subproc.returncode
        self.output = output
        return output

class HamParser:
    def __init__(self):
        self.dict = {}
        self.rooms = {}
        self.online = {}
        self.names = {}
        self.lock = threading.Lock()
        self.executor = Executor()

    def __getattr__(self, attr):
        try:
            return self.dict[attr]
        except KeyError:
            return None
    
    def readStatus(self):
        try:
            for line in open(os.path.expanduser("~/.hamachi/state")).readlines():
                if line.strip() == "":
                    continue
                line = line.split()
                if len(line) == 2:
                    self.dict[line[0].strip().lower()] = line[1].strip()
        except IOError:
            print "Is hamachi properly configured?"

        lines = self.sendCommand()
        for line in lines:
            if line.strip() == "":
                continue
            line = line.split(':')
            if len(line) == 2:
                self.dict[line[0].strip()] = line[1].strip()

    def sendCommand(self, *args):
        cmd = ["hamachi"]
        cmd.extend(args)
        self.lock.acquire()
        try:
            self.lastOutput = self.executor.execute(cmd, TIMEOUT)
            self.lastLines = self.lastOutput.split('\n')
            self.returncode = self.executor.returncode
        except IOError, e:
            print e
            self.lastLines = []
        self.lock.release()
        return self.lastLines

    def readRooms(self):
        self.online = {}
        self.sendCommand("get-nicks")
        lines = self.sendCommand("list")
        current_room = None
        self.rooms = {}
        for line in lines:
            if line.strip() == "":
                continue
            if line.strip()[0] == '*':
                online = True
            else:
                online = False
            line = line.replace('*', ' ')
            tokens = line.strip().split()
            if line[3] != ' ':
                current_room = tokens[0]
                self.online[current_room] = online
                self.rooms[current_room] = []
            else:
                person = PersonObj()
                person.ip = tokens[0].strip()
                if len(tokens) > 1:
                    person.name = tokens[1].strip()
                else:
                    person.name = "<Unknown>"
                if len(tokens) > 2:
                    person.personal_ip = tokens[2].strip()
                else:
                    person.personal_ip = "<Unknown>"

                self.rooms[current_room].append(person)
                self.online[person.ip] = online
                self.names[person.ip] = person.name
                 
                



class HamGUI:
    def __init__(self):
        data = zlib.decompress(GLADE_DATA)
        self.xml = gtk.glade.xml_new_from_buffer(data, len(data))
        self.rooms = {}
        self.interval = None
        self.timer = 0
#        sys.stdout = Output(self.output)
        self.hamCommunicator = HamParser()
        self.initWidgets()
        self.connectHandlers()


        gobject.idle_add(self.updateGUI)

        gobject.idle_add(self.xml.get_widget("hamView").expand_all)
        self.xml.get_widget("mainWindow").show_all()
        
        # try starting hamachi
        self.hamCommunicator.sendCommand("start")

    def initWidgets(self):
        self.xml.get_widget("mainWindow").set_default_size(200, 500)
        self.hamTree = gtk.TreeStore(str, str, str, bool)
        column = gtk.TreeViewColumn()

        cellStr = gtk.CellRendererText()
        cellPixbuf = gtk.CellRendererPixbuf()
        
        column.pack_start(cellPixbuf, False)
        column.pack_start(cellStr, False)
        column.add_attribute(cellPixbuf, 'stock-id', 0)
        column.set_attributes(cellStr, text=1)

        cellStr = gtk.CellRendererText()
        column.pack_start(cellStr, False)
        column.set_attributes(cellStr, text=2)
        
        self.xml.get_widget("hamView").append_column(column)



        self.xml.get_widget("hamView").set_headers_visible(False)
        self.xml.get_widget("hamView").set_model(self.hamTree)
        self.selection = self.xml.get_widget("hamView").get_selection()
        self.selection.connect('changed', self.selection_changed)

        self.xml.get_widget("newnetwork").set_property('sensitive', True)
        self.xml.get_widget("delnetwork").set_property('sensitive', False)
        self.xml.get_widget("joinnetwork").set_property('sensitive', True)
        self.xml.get_widget("leavenetwork").set_property('sensitive', False)
        self.xml.get_widget("kicknetwork").set_property('sensitive', False)

        self.outputBuffer = self.xml.get_widget("outputView").get_buffer()
        self.connectionTip = gtk.Tooltips()

    def connectHandlers(self):
        autodict = {    "network_new_clicked" : self.new_network,
                        "network_del_clicked" : self.del_network,
                        "network_join_clicked" : self.join_network,
                        "network_leave_clicked" : self.leave_network,
                        "network_kick_clicked"  : self.kick_network,
                        "cancel_clicked"        : self.hide_dialog,
                        "new_entry_changed"     : self.new_entry_changed, 
                        "dialogok_clicked"      : self.action_requested,
                        "connect_toggled"       : self.connect_toggled,
                        "refresh_clicked"       : self.refresh_clicked, 
                    "refresh_spinner_changed"   : self.refresh_changed,
                        "quit_event"            : gtk.main_quit, 
                        }

        self.xml.signal_autoconnect(autodict)
        self.interval = int(self.xml.get_widget("refresh_spinner").get_value())
        gobject.timeout_add(1000, self.GUIUpdater)

    def updateHamView(self, clear=False):
        added = {}
        if clear:
            self.hamTree.clear()
            self.rooms = {}
        for room in self.hamCommunicator.rooms.keys():
            if room in self.rooms:
                parent = self.rooms[room]
            else:
                parent = self.hamTree.append(None, [None,  room, None, self.hamCommunicator.online[room]])
                self.rooms[room] = parent
            
            try:
                iter = self.hamTree[parent].iterchildren()
                while iter:
                    row = iter.next()
                    ip = self.hamTree.get_value(row.iter, 2)
                    if ip not in self.hamCommunicator.online:
                        self.hamTree.remove(row.iter)
                    else:
                        added[ip] = True
                        row[3] = self.hamCommunicator.online[ip]
                        row[1] = self.hamCommunicator.names[ip]
            except StopIteration:
                pass

            for nick in self.hamCommunicator.rooms[room]:
                if nick.ip not in added:
                    self.hamTree.append(parent,
                            [None, nick.name,
                                nick.ip, self.hamCommunicator.online[nick.ip]])

        iter = self.hamTree.get_iter_first()
        try:
            while iter:
                if not self.hamTree.iter_is_valid(iter):
                    break
                room = self.hamTree.get_value(iter, 1)
                if room not in self.hamCommunicator.rooms:
                    self.hamTree.remove(iter)
                    del self.rooms[room]
                else:
                    iter = self.hamTree.iter_next(iter)
        except StopIteration:
            pass


        if clear:
            self.xml.get_widget("hamView").expand_all()
        
    def GUIUpdater(self):
        self.timer += 1
        if self.timer >= self.interval:
            self.updateGUI()
            self.timer = 0
        try:
            percentage = float(self.timer) / float(self.interval-1)
            if percentage > 0:
                percentage -= .001
            self.xml.get_widget("refreshprogressbar").set_fraction(percentage)
        except:
            self.xml.get_widget("refreshprogressbar").pulse()

        return True


    def updateGUI(self, interval=None):
        gtk.gdk.threads_enter()
        self.hamCommunicator.readStatus()
        self.hamCommunicator.readRooms()
        self.xml.get_widget("nicklabel").set_markup("<b><span size=\"xx-large\">%s</span></b>"%self.hamCommunicator.nickname)
        self.xml.get_widget("statuslabel").set_markup("<span>%s</span>"%(self.hamCommunicator.status))
        self.xml.get_widget("iplabel").set_markup("<b><span size=\"xx-large\">%s</span></b>"%self.hamCommunicator.identity)

        self.toggling_icon = True
        if self.hamCommunicator.status == ONLINE:
            self.xml.get_widget("connect_toggle").set_active(True)
            self.connectionTip.set_tip(self.xml.get_widget("connect_toggle"), "Disconnect")
        elif self.hamCommunicator.status == OFFLINE:
            self.xml.get_widget("connect_toggle").set_active(False)
            self.connectionTip.set_tip(self.xml.get_widget("connect_toggle"), "Connect")
        self.toggling_icon = False
        
        self.updateHamView()
        self.updateOnline()
        gtk.gdk.threads_leave()
        return self.interval == interval

    def updateOnline(self):
        for row in self.hamTree:
            if row[3]:
                row[0] = gtk.STOCK_YES
            else:
                row[0] = gtk.STOCK_NO
            for childrow in row.iterchildren():
                if childrow[3]:
                    childrow[0] = gtk.STOCK_YES
                else:
                    childrow[0] = gtk.STOCK_NO

    def output(self, text):
        end_iter = self.outputBuffer.get_end_iter()
        self.outputBuffer.insert(end_iter, text)
        end_mark = self.outputBuffer.create_mark(None, self.outputBuffer.get_end_iter(), True)
        self.xml.get_widget("outputView").scroll_to_mark(end_mark, 0)


    def new_network(self, obj):
        self.action = NEW
        self.xml.get_widget("newlabel").set_label("<b>Create New Network</b>")
        self.xml.get_widget("newname").set_text('')
        self.xml.get_widget("newpass").set_text('')
        self.xml.get_widget("newok").set_property('sensitive', False)
        self.xml.get_widget("newdialog").present()

    def del_network(self, obj):
        self.action = DELETE
        self.xml.get_widget("dellabel").set_label("<b>Delete Network</b>")
        model, row = self.selection.get_selected()
        self.xml.get_widget("delactionlabel").set_label("Delete %s Network?" %
                model.get_value(row, 1))
        self.xml.get_widget("deldialog").present()

    def join_network(self, obj):
        self.action = JOIN
        self.xml.get_widget("newlabel").set_label("<b>Join Network</b>")
        self.xml.get_widget("newname").set_text('')
        self.xml.get_widget("newpass").set_text('')
        self.xml.get_widget("newok").set_property('sensitive', False)
        self.xml.get_widget("newdialog").present()

    def leave_network(self, obj):
        self.action = LEAVE
        self.xml.get_widget("dellabel").set_label("<b>Leave Network</b>")
        model, row = self.selection.get_selected()
        self.xml.get_widget("delactionlabel").set_label("Leave %s Network?" %
                model.get_value(row, 1))
        self.xml.get_widget("deldialog").present()

    def kick_network(self, obj):
        self.action = KICK
        self.xml.get_widget("dellabel").set_label("<b>Kick from Network</b>")
        model, row = self.selection.get_selected()
        self.xml.get_widget("delactionlabel").set_label("Kick %s from Network?" %
                model.get_value(row, 1))
        self.xml.get_widget("deldialog").present()

    def hide_dialog(self, data):
        data.hide()

    def new_entry_changed(self, data):
        if self.xml.get_widget("newname").get_text().strip() != '' and \
            self.xml.get_widget("newpass").get_text().strip() != '':
            self.xml.get_widget("newok").set_property('sensitive', True)
        else:
            self.xml.get_widget("newok").set_property('sensitive', False)
         

    def selection_changed(self, selection):
        model, iter = selection.get_selected()
        if not iter:
            return
        if model.iter_parent(iter):
            self.xml.get_widget("newnetwork").set_property('sensitive', True)
            self.xml.get_widget("delnetwork").set_property('sensitive', False)
            self.xml.get_widget("joinnetwork").set_property('sensitive', True)
            self.xml.get_widget("leavenetwork").set_property('sensitive', False)
            self.xml.get_widget("kicknetwork").set_property('sensitive', True)
            # it's a user.
        else:
            # it's a room
            self.xml.get_widget("newnetwork").set_property('sensitive', True)
            self.xml.get_widget("delnetwork").set_property('sensitive', True)
            self.xml.get_widget("joinnetwork").set_property('sensitive', True)
            self.xml.get_widget("leavenetwork").set_property('sensitive', True)
            self.xml.get_widget("kicknetwork").set_property('sensitive', False)

    def connect_toggled(self, obj):
        if self.toggling_icon:
            return
        if self.hamCommunicator.status == ONLINE:
            self.hamCommunicator.sendCommand("logout")
        elif self.hamCommunicator.status == OFFLINE:
            self.hamCommunicator.sendCommand("login")
        return True


    def refresh_clicked(self, obj):
        gobject.idle_add(self.updateGUI)

    def refresh_changed(self, obj): 
        interval = int(obj.get_value())
        self.interval = interval

    def action_requested(self, obj):
        if not self.xml.get_widget("newok").get_property('sensitive'):
            return
        self.xml.get_widget("newdialog").hide()
        self.xml.get_widget("deldialog").hide()

        if self.action == NEW:
            room = self.xml.get_widget("newname").get_text().strip()
            passw = self.xml.get_widget("newpass").get_text().strip()
            self.hamCommunicator.sendCommand("new", room, passw)

        if self.action == DELETE:
            model, iter = self.selection.get_selected()
            room = model.get_value(iter, 1).strip('[]')
            self.hamCommunicator.sendCommand("delete", room)

        if self.action == LEAVE:
            model, iter = self.selection.get_selected()
            room = model.get_value(iter, 1).strip('[]')
            self.hamCommunicator.sendCommand("go-offline", room)
            self.hamCommunicator.sendCommand("leave", room)

        if self.action == JOIN:
            room = self.xml.get_widget("newname").get_text().strip()
            passw = self.xml.get_widget("newpass").get_text().strip()
            self.hamCommunicator.sendCommand("join", room, passw)
            self.hamCommunicator.sendCommand("go-online", room)

        if self.action == KICK:
            pass

        
        print self.hamCommunicator.lastOutput

def main():
    app = HamGUI()
    gtk.main()

GLADE_DATA= 'x\x9c\xed]Ks\xdb6\x10\xbe\xf7W\xa0<t\xda\x83b\xcbn2ij+\xf5CN\xdc\xd8\x96\xc7R\x9a\xfa\xc4\x81HXb\x04\x01,\tZR\xa6?\xbe\xcb\x87eJ\x02I\x90"\x15\xaa\xd6!\x1e\xf1\x81\xc5\xeeb\xf1\xed.\x80e\x8e\xdeO\xc7\x14=\x12\xc7\xb58;\xd6\x9a\xaf\xf65D\x98\xc1M\x8b\r\x8e\xb5\xcf\xbd\x8b\xc6[\r\xb9\x023\x13S\xce\xc8\xb1\xc6\xb8\xf6\xbe\xf5\xc3\xd1\x8f\xe7\x9d\xb3\xde\xfdm\x1b\r(6I\xc3b\x828\x0f\xd8 \xa8{\xdf\xed\xb5\xaf\x91\x16\xde?x\xb5\xff\xca\x14\xa6\xe6\xb7h4>\x10F\x1c,\x88\x89&\x96\x18\x86M\x0f\xd1!\xbcs\x808C\xd7\xf0\xef\x9c\x18\xa8\xd9D\xcd\xb7\xef\x0e\x7f{\xd7|\x8b\x0e\xf6\xf7\xdf\xa0\xfe\x0c1\xf6\x8d\x0f\xb1\xf3\xc7\xd9\x15\xbeo4\x80\xdcR\xbf\xad\x1f\x10:\x9aX\xe6\x80\x08dP\xec\xba\xc7\xda\x071\xfab1\x93O4d\x99\xc7\xda\x18[,\xba\xf6_\x86\xd7m\x87\xdb\xc4\x11@\x1c\x8fA2a\tJ4$\x1c\xcc\\\x8a\x05\xeeS\xb89#\xae\xd6\xfa\x88\xc7\xd8\x18ZG{O-\xe4\x04L\xf2\x80=*\xf4!\xb1\x06C\xa1\xb5\x0e^\xef\xaf4q\xad\x01\xc3t\xde\x80\x12At\xf2H\x98\xd0\xd0\x10\x94L\x89s\xac\xfd\xe3Y"\xba\xb9\x175\x83\xde\xa9\x19\xfe\x96\xc9\xf9\xd7)\x9f\x86R>\xf6\xf9\xb4\xa9=\xbd\xb9\xca\xe3\xa3\xe5Z \x98\xd6\xea9\x1eY\xe6n\xb5+\x95\xee\x0e\xb4\xf8\xdb\x05\xba\x945\xeas\xc7$\x8e\x0e]\x8b\xa1\xd6z\xad\xda\xcc\xb5\xb1\x01\x96\x9b\xd6bE>\xb9\x8cW\xb8Oh(\xa4e\xd3\xe0b\xb9QAQe\r\xc3\x0ed\xb6\x17<I&u\xb4\x17r\xbe$\xe4\x9eD\xca\x02\x923\xcb\x18\xd5W\xf6UR\x9eK\xf41vF\x9e\x9d\xc5\x86\\o>El\x8c\xc0\x82\xb2\xfb\xb2\xb9k\t\x00M\xad\xd5L\xebFJ\xaf\xac\xf1\x01`\x16\x9e[\xdf\x11*S\xcb\x07ehY\xc6\x90\x9c\x99eF\xc8\xd4\x06|\xd6Z\x17\x98\xba\t\xca\x93\xb0\xb1\xc2\x82\n\xbc\xdepA\xfa\x9c\x8f\xa29\x18]5K\x81YE\x1b[D\xf8\xc3R\x8dK\xca\x82\x9c\x8d\xae\xe1pJ\x89\x19\xf7\xe3nto\x12\xdc[\xd6\xca\xda\xec\xc9\x1a\x0f\xc3>\xfb\xd8\xd1mN-c\xa6\xb5>\xf4>\xe9\xb7\x9d\xab\xcb\xb3{\xfd\xe4s\xafs}\xd2\xbb<\xcbK\xf6\xb1$\xb2\x89\n\x95+\xb5\xe7\x10\xf2\x97E"u\x0e\xf18\xb8Hj\xbf\xa6*\x931\xe0\xe9i\xb29$6Ll\x94\xc7\xb6>\xceM|\x98`\xe2k\x0b\x9f\x1e\xd4\xa4\x00\x9a\xbcyvp\x93\xad\x07\xb9.n\x1d>p\x88\xeb\x9eb\'T\x89C\x1e\xe0zhG\xf7\xc1Dkh \xf9\xe5\xec\xda\x16;\xf5\x84\x00\x7f\x12\x17Sw\xe1>\xa4&\xd5\xc9\xb8J\xc0\xc0L\x7f\xe0\x86\xe7\x16\'\x81\xcd\xaf\x9e+\xc6~\x9e\xd0j\xee\xa3&z\xb3\xef\xff\xf5\x7f\xae$\x1e*\xf4\x04\xe7TX\xb6\xd4\xd1\x7f\xb6MH\xdc\xd0\xa5\x9fo=B\x02\xf3\xb3K\x0c\xceL\xf7\x17\xa5\x8e\x16\xb2\x1eh\xee\x11\xdd\x80tg@\xccX\xda\xb34\x18\xf37\xf6\x8a\x19NZ\x94\x91\xa4\x01\x05\'\x9fN\xe0\xc1\xa2t\x8d\xe6J1\xe5\xb3\xf8\xa9\xf2\x15B\xd6,\xa5\xad\xa5\xb0\xc2\xd2\xa6HZ\x81+X\xceg\x93\xd8_\xcb\x15\xcc\xb1<\xdb\x0b\xe4u\xef|0\xa0$\x8er0Q\x191\x84.\x82\'\x95\x82\xdc\xc2D\x0f\xfb\x8bO\xf1ENR\xa6v\x96\xd8r\xd1/\xc7x@\xa2\x9c\xdd\xff\x99\x14\x1c\x96&\xaf\x8c\x88+\xb81\xd2Z\x031jD\xe2\x16\xa5dA{\xdd\xb5\xbe\x01C\x87j4\xb2\x101\xc3\x9dn?\xa4\x16\xc7\xc4\xfc\x13->\xc5\xfa\xc1\xef$\xe4\x90\x89\xb5v\x00\x91\xe6\xad\xefBW\x9a\x7f\xca\x1a\x90\x86\x8c\xa4^\xf9\xe9I\xc5S6M\x85\xa5\xa8QF$6e\x1du\xcd\xc9(\xed\xa6\xec\xf6DA\xf9g\xfc5a\xde<E\x82\xb8\xdb\x83\xbc(\xcd\xc7\xac?\xe7\x0b\xcc(\x9f\xc9KA\xc6\xcf\\Zp\xf5]\\a\xf2\xba\xe1\r\x11\x13\xee\x8c\x8a\x12\xf6\xd7y=\x06\xb94\xb5X^\x1e3U\x1a\xbc%U\xeb\xb3J\xb3\xd4)c\xba\x90J\xd5Y\x96\xb3\x1d\xe0\xeb\xa2I02a\xa1\xfa\x15\x84(U\x10\x19\xb14\x1b\x99\xacK|\r;\x99\x93\\\xf0\x8f\xd8\x10\xd6#$\xc21\x07\x19\xe9R\x07\xb5\xaa8\xc9\x05\xda\xc1\xc0\xa2`\x13\x13\xfah\x04\x97\x91#T\x1c\x9b\x0c\x9f\xea\xdbj\xc3\x9f\xff\r\xa5\x80x\x81n\x99\xa3.#\x18\xecp\xc28\x7f8\xf7\xd7</oz\xed;\xfd\xba\xd3\xbb\xec\xdc\xe8\xd7\'\xddO\xe8_$y\xf2\x11\xae\xe2\x8fO?\xf7zp\xfb\xf6\xae\xdd\xedJ\xee\xdf\xb5\xaf\xda\'\xddv\xfcI; w\x03\xe4.\xee\x83\xfbe\xc8\x12\x8b \xb0i\xe6\xa7\x98\x1d\x05,\xbe\xad\x8e\x08\xca\x84\x95\x89\x96\x0cG&\xa1[\x01G\xe7\xc1f\xfd6!\x12h\xb6\xf6\x88\x94\x15\xef/\xd0\xdd!\x92\xba,1D2\x0bY\xee\xcb\x01\xa5.\xb1\xb1\x83\x05w\x16\x81\xc9}\xba\xad\x1aC\xcf{(\xcfNk\xa6)\t|\x8f\x00_\xb6\x02\xbf?\x01\xa3\xdb\x84\xde\xbebk\x0f\xdfI\xdb\xa6R\xba;\xf8V\x97%\xbe\x8aL\tvv\xe8\x1d\xbdY\x18\xbdU#\x8d\x17\x85\xde_\xb9\xbf\xd7\xba\x05\xe8\xfd\'0\xbaM\xe8\xed+\xb6\xf6\xe8\xfd\xeb\x0e\xbd\xabG\xef|{\x801\x9a/\x04\xbf%\xa8\x04\x0e\x0f\x8ca\x1b`\xe9\xca\xe7t\x9bp)Pm\xed\x81\xe9\xf5\x0e\x98\xaa_\x15\xb0\xdc\x17\x80Mj\x04\x15\x88\xd5p\x9fV\xe9l\xfcs\xf7\xff\x9b\xd3b\x19\xd2\xe6?-\xb6~\x99\x02\xf8\x04]Q\xba\x8aKB\x02_%\x0b\xec6P\r\x12\xed\xea\xbaU\x16\x84\x88\x99\r<C\xafyX\xce1<\xf2\xc6J\'\x1a\xaa\x18\xd9\xec\n\x08Y^[\xdaX\x97P\xf9PA\xd5C\x9e\xb3\x9d=2\x15\xcf\xd5\x0e\xdc\x13\xb6\'R\n\x1eJ=\xe4IL+\x98\x1e\x05\x11o\xe2`[\x1fs\x93\x84\x1a\xfarwr\xab\x7f\xe9\xdc\x9dg\xc1_\xce\xb2\x89\r\x95\xc2\xd5\x194C\xb3\xb8\xfa~ut\x9d\x80\x81\xda\xe1f\xf5\x03\xbb\x11\xb8-^\xf5\x97\xa9\x80\xec\x9a\xbf\xc5\xcec\x0f\xe3\x0fV\xad\xf3\xdc\xc2\x94\x0f\xe6\'v\xcc\xf02"\xb2\xc4eF\xb1\xb4z\xa1{\xc2\xb1\xace\x02\x80I\x98\xca\xcd\x7f\xc5\x81`W\x9f/\xc5&\x0ciBz\xe9W\x1e\xcegcZ\x89b\xa8\x9b\xc6\xfa\xa5\xef[\x94\xf8\xe5\xab\'P\xa9:\xbdp\x80P\xa8\xd0\x07\xffg9\xf5\xa62\x04\xd4\xa7\x98Z\x03\x98S\x89UJ+\xd2\r1D:z\x88a\xbe/\xec~<9\xef|\x01%\xdd\xb4\xd7-u=\xf1y\t\x8a\xa8\x02\xd9\xf1\xd3\xa5l\xb7\xb74O \xb8\xad\xdb\xd84\x83!{\x93\xcb\x87\x90\x07Q\xb0\xa9\xe3\x7f\x96B\xb5m\x9e\x10+\x88q\x02\xed\x05`\xb2\x91\x9a\\\xa6;|\xe2\x16\xa8\xa2d\xba\xc1\xa97fE\xdaB\x97zuU\x98\xcbY\xdcF\xcb\tR\xb2:x\xben\x15g\x91\xe5\x8d\x99\xcem\xdf\xf7\x02\x0b\xf5:\x06\xbe<Ni\x9b\xe0\x1b\x1c\xa7[\xe0\x0f\xbc\xb7\xc2\xe9\xc3\xf2\xc7\xca\xc73,\x046\x86J\x87\xf6e!\x8c\x10|<\xa7\xa1\xb0\xa0\xb5E\x06\xd3f\xc2\x99=\x1f\xbe\xf6\xfdl\x95\x16\xb3X\xd1\xb3Rg\xeb\x1fT&>G\xd9\x15\xb6\xcb\xc4$\x1b\x0ba\xf0\xc5\x15\x8e\xaa\x94ou\x81/\\\xcb\xecB\x97\xf8\x12\xac\xce\x86\xdb;\xab\x93\t\xbe\x95V\xf7\x92\x00\xb7\xa4\x15\xad\xd2>\x8aE&\xdf\xf1\x8bK?Q\xf1{\xff\xa7\x81\xf8=L\xd2\xfc\xcb\xbd\xe0:\x0f\xf9M~\'+L\xda\xc2\xc4\xcf\xdf\xe1\xdd\xfa\x05\x9d\xc4\x05\x0b\x1f\xa78\xd3\xb1C\xf0\x82m\xc8>8\x10\xd4\x0b/\xaf`\xc4\x08T\x92}\xd7yAC\xc6/\xc53\xee\t\xdd\x153\x1a\xe5\xfda\'\xa7\x9d\xbf\x81h\xe2:\xb8\xea\x9c\x8e\x17p\xc3\xa4603\xbe\xd7\xac\x0eN\n\x05\xfd\xe7\x9d\xc6\xd1\x8e~&+\x19e\xdea\xe7sw\x8ax\xff+1D|\xe9q\xc5\xc3V\x80\xb2K#\xc2e\xa7\x7f64\x1a<\xf5\xb0x\x85#\x91\x1d\xda\xd4\xee\xdb\x85\x95~UO\xc2=P\x8e\xad\x05\xde\x9e\x9c}J\x86\x83\x8d,\xd0\x9b\x84\x96\xb9@_\xdf\xf5\xf5\x83\xdd\xfazY\xeb\xeb\x95|6w\x8b\xd6\xd7+=\xb7\xf0\xbfZ_\x8f\xe5\x1e\x004a\x90\x98\x94\x81\xac\xa5E\xa9>\x92\x0bb9\x82\xf0\x0cA\xb0J\xe9\x0cM0\x13Hp\x14\x16\x1b\xa2\xe9t\x8a\xa23\xa7\xef\xf3v\xa9\x9e\x9d\x04\xad\xeb\x95\x1d\x82\xfc\xb5\xc9\x0e\x0fv\xd9a:G\xdb\x9a\x1dV\xe2;\xea\xec\x8ee\xfcn0;\x84I\xbd\xcb\x0e%\xd9\xe1s\xdc\xbb\xe9\xec\x10z\xdee\x87\xbb\xecP\xc6\xfd\xc6\xb3\xc3\xa3\xbd\x95\xff\x8c\xe4?\xedj\xd0\xde'


if __name__ == "__main__":
    main()

