#! /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)
#        data = open("ham.glade").read()
        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_NETWORK
            else:
                row[0] = gtk.STOCK_DISCONNECT
            for childrow in row.iterchildren():
                if childrow[3]:
                    childrow[0] = gtk.STOCK_CONNECT
                else:
                    childrow[0] = gtk.STOCK_DISCONNECT

    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\xb0<t\xda\x83b[n\xd26\xb1\x95\xfa\xa1$nl\xcbc)u}\xe2@$,1\x82\x08\x96\x04-\xa9\xd3\x1f\xdf%H\xcb\x94\x08\x92\xe0K\xa6\x1a]<\xe2\x03\x8b\xdd\xc5\xee\x87]\x10\x0b\x1f\xbd\x9fO\x89\xf2\x88\x1d\xd7\xa4\xd6\xb1z\xf0j_U\xb0\xa5S\xc3\xb4F\xc7\xea\x97\xc1\x87\xd6\xaf\xaa\xe22d\x19\x88P\x0b\x1f\xab\x16U\xdfw\xbe;\xfa\xfe\xbcw6\xb8\xbf\xe9*#\x82\x0c\xdc2-\x86\x9d\x07\xa4c\xa5\x7f\xdf\x1ft\xaf\x145\xb8\xdf~\xb5\xff\xca`\x86\xea\xb7h\xb5>b\x0b;\x88aC\x99\x99l\x1c4=T\x0e\xe1\x9d\xb6B-\xe5\x0e\x1e\x9cc]i\xef+\x07\xbf\xbd=h\xbf=\xfc\x05~\xef\xbfQ\x86\x0b\xc5\xb2\xfe\xa1c\xe4\xfc~v\x89\xee[- \xb7\xd6o\xe7;E9\x9a\x99\xc6\x083E\'\xc8u\x8f\xd5\x8flrgZ\x06\x9d\xa9\x8ai\x1c\xabSdZ\xe1\xb5\xff2\xbcn;\xd4\xc6\x0e\x03\xe2h\n\x921\x93\x11\xac*\xccA\x96K\x10CC\x027\x17\xd8U;\x9f\xd0\x14\xe9c\xf3h\xef\xa9\x85\x98\x80\x81\x1f\x90G\x986\xc6\xe6h\xcc\xd4N\xfb\xf5~\xac\x89k\x8e,D\x96\r\x08fX\xc3\x8f\xd8b\xaa2\x06%\x13\xec\x1c\xab\x7f{&\x0bo\xee\x85\xcd\xa0wb\x04\xbfEr\xfeyJ\xe7\x81\x94\x8fC:?P\x9f\xde\x8c\xf3\xf8h\xba&\x08\xa6v\x06\x8e\x87\xd7\xb9\x8bw%\xd3][\x8d\xbe]\xa0KQ\xa3!u\x0c\xech\xd05\x1b\xab\x9d\xd7\xb2\xcd\\\x1b\xe9`\xb9i-b\xf2\x89e\xbcDCL\x02!M\x9b\xf0\x8b\xf5F\x05E\x155\x0c:\x10\xd9\x1e\x7f\x92L\xeah/\xe0|M\xc8=\x81\x94\x05$\xb7L}\xd2\\\xd9\xe3\xa4<\x17kS\xe4L<;\x8b\r\xb1\xde|\x8aH\x9f\x80\x05e\xf7eS\xd7d\x00\x9aj\xe7 \xad\x1b!\xbd\xaa\xc6\x07\x80\x99ynsG\xa8J-\xb7\xab\xd0\xb2\x88!13\xeb\x8c\xe0\xb9\r\xf8\xacv> \xe2&(O\xc0F\x8c\x05\x19x\xbd\xa6\x0c\x0f)\x9d\x84>\x18^\x1dT\x02\xb3\x926\xb6\x8a\xf0\x87\x95\x1a\x97\x90\x051\x1b}\xdd\xa1\x84`#:\x8f\xbb\xe1\xbd\x19\xbf\xb7\xae\x95\xd2\xec\x89\x1a\x8f\x83>\x87\xc8\xd1lJL}\xa1v>\x0e>k7\xbd\xcb\x8b\xb3{\xed\xe4\xcb\xa0wu2\xb88\xcbK\xf6\xb1"\xb2\x89\n\x15+u\xe0`\xfc\xa7\x89Cu\x8e\xd1\x94_$\xb5/\xa9\xcad\x0cxz\x9al\x0e\x89\r\x13\x1b\xe5\xb1\xad.wh\xec\x04j\xc0\xe1U=\xf6\x94s\x84>-\xbdo\x9c\xe0}\x95\xb0%"\xb0\x1aw\xa5`n2\x89\xec\x18L^7b\xfd\xdc8t\xe4`\xd7=E\xe1\xe89\xf8\x01\xae\xc7vx\x1f<*Eg\x95\xe8-\xcb\xae\x8372d+"{\xdf6\xadS\x8f1\x98\x12\xa3\xa2k.\xdc\x87\xec\xaa~\xb9\xe3Dtdi\x0fT\xf7\xdcrd\x90\xf1\xd5s\xd9\xd4O{:\x07\x90\x02*o\xf6\xfd\xbf\xfe\xcfX\x1e%K\x93QJ\x98i\x0bc\x97/\xb6\x01\xb9\xa8r\xe1\xa7\x90\x8f\x90\x93\xfd\xe8b\x9dZ\x86\xfb\x93tg+\xc9\x1c\x90\xf0\xb0\xa6C\x167\xc2F$\x9b[\x1b\xa0\xe5\x1b{\xe5\x8c+-\x88J\xd3\x88D\x1c\x93M\xe4\xc1$\xa4$\t\xa9\xf0yU%\x99\xf2f8\\\xe1\x99(?\x82Gbt\x1e"\'M+\xbcu\xd5\x18\x9e\x1c\xae\xdf\x06\xb6\x086\xff@\xcb\xce\xdc2\x06\x18s\xc7\x85\x8d\xc3\xa4A3\x19\x9e\xca\xf0\x90\xdaI\xa1\xf0!\x8b\xf3R.S\xd8\xceS$\xad$\xdeY\r*\xd6\x17m\x92\xd8/\x157/#\x81\xcc8"w\x0cKG#\x82\xa3\xf3 @\xb7\x85u\xa61\xfe\xa4Vw[\x81\xfd\xa0\xbf(\xe0\xafr\x92\n\xf4E\x02\x80\x8b)\x1a\xe1pa\xca\xff\x99\x06-\x95\xc8+"\xe22\xaaO\xd4\xce\x88MZ\xa1\xb8E)\x99\xd0^s\xcd\x7f\x80\xa1\xc3\r\x05^\xd5\xe3Z\xeeI\xb5\xd4\x84Z\x1c\x13\xf3;Z\xd4\xc5\x86\xfcw\x12r\x88\xc4*=\x95\xa5\xc5o\xe1d\x96\xdfeu\xc8\xb5\'\xc2\x18\xed\xe9I\xcd.\x9b\xa6\xc2J\xd4("\x12qYG^s"J;\x97-\x10\x03\x97\x8a\x7f7\xe9\xf1W\xd8\xf2\x96\x895dc\x1ed\xd3\xf5\x86\xaf\x05<\xcag\xf2\x02\x02\xc8g.\xfdp\xf2E\xa6\xc2\xe4h\xfb\x1a\xb3\x19u&E\t\xfb\x1f3<\x7fM\x8a\x98V^\x1e3U\xca\xdf\x12\xaa\xf5Y\xa5Y\xea\x141]H\xa5\xf2,\x8b\xd9\xe6\xf8\xbaj\x12\x16\x9eY\x81\xfa%\x84\xa8T\x10\x11\xb14\x1b\x99\x95%^\xc2N\x96$W\xe6G\xa43\xf3\x111\x1c\x99 C]j\xa0V\x99Ir\x856\x1fX\x85\x7f\xa9\x87>Z\xfc2\x9c\x08%\xc7&cN\xf5m\xb5\xe5\xfb\x7fK* ^\xa1[\xe5\xa8\x8b\x08\xf2\xcf\xf80\xce\x1f\xcf\xfd\x85\xfd\x8b\xebA\xf7V\xbb\xea\r.z\xd7\xda\xd5I\xff\xb3\xf2\xaf"x\xf2\t\xae\xa2\x8fO\xbf\x0c\x06p\xfb\xe6\xb6\xdb\xef\x0b\xee\xdfv/\xbb\'\xfdn\xf4I\x97\x93\xbb\x06r\x1f\xee\xf9\xfd*d\x89D\x10\xc80\xf2S\x94Y\xd4\x8a\xbe-\x8f\x08\xd2\x84\xa5\x89V\x0cG\x06&[\x01G\xe7|G\xca6!\x12h\xb6\xf1\x88\x94\x15\xef\xaf\xd0\xdd!\x92\xbc,\x11D2\nY\xee\xb7\x03J}l#\x071\xea\xac\x02\x93\xfbt[6\x86^\xf6P\x9d\x9d6LS\x02\xf8\x9e\x00\xbel\x05~\x7f\x06F\xb7\t\xbd}\xc56\x1e\xbe\xd3>\xc0\xc7\xe8\xee\xe0[^\x96\xe8*2\xc1\xc8\xd9\xa1w\xf8fa\xf4\x96\x8d4\xbe)\xf4\xfeJ\xfd/\xef[\x80\xde\x7f\x00\xa3\xdb\x84\xde\xbeb\x1b\x8f\xde?\xef\xd0\xbb~\xf4\xce\xf7\r0B\xf3\x1b\xc1o\x01*\xc1\x84\x07\xc6\xb0\r\xb0t\xe9s\xbaM\xb8\xc4U\xdbx`z\xbd\x03\xa6\xfaW\x05L\xf7\x1b\xc0&9\x82\x12\xc4\x1a\xf8\x9dV\xaa\x00\xe4\xb9\xfb\xff\xcdn\xb1\x0ci\xf3\xef\x16+_\x8b\x03s\x82&)]\xcduO|\xae\x12\x05v\x1b(y\n\xbf\xea\xbauV=\x05\xfb$\xa1\xd7<,\xe7\x18\x1eqc\xa9\x1d\ru\x8clv\x99\x8f(\xaf\xadl\xac+(\xef\xa9\xa1\xb4\'\xcf\xde\xce\x01\x9e\xb3\xe7\x92\x1e\xea1\xdbc)U=\x95n\xf2\xc4\x86\xc9\xdd\xa3 \xe2\xcd\x1cdkSj\xe0@Cw\xb7\'7\xda]\xef\xf6<\x0b\xfer\xd6\x06m\xa8\xde\xb3\xc9\xa0\x19\x98\xc5\xe5\xcb\x15\x8b\xf68\x03\x8d\xc3\xcd\xfa\x07v#p[\xbc\xb45S\x01\xd9\x85\xad\xab\x9dG\x1eF\x1f\xc4\xad\xf3\xdcD\x84\x8e\x96;v\x8c\xe02$\xb2\xc6e\xc6\x89\x00\xf2\xa79$l\xcbZ\'\x00\x98\x84\x88\xd8\xfcc\x13\x08r\xb5\xe5Rl\xc2\x90&\xa4\x97~y\xed\xd2\x1b\xd3\xeap\x03\xdd\xb4\xca\x9f\xef\xb0E\x89_\xbez\x02\x99\xd2\xea\x0f\x0e\x10\n\x14\xfa\xe0\xff\xac\xa6\xa8Z\x84\x80\xda\x1c\x11s\x04>\x95X\xbb\x16\x93n\x8c \xd2\xd1\x02\x0c\xf3\xe7\xc2\xfe\xa7\x93\xf3\xde\x1d(\xe9\xba[\xb6\x9e\xfb\xc4\xe7\x85\x97\xd6q\xd9\xd1\xd3\xa5\xe8koe3\x01\xa3\xb6f#\xc3\xe0C\xf6&\xd7\x1c\x82\x1fX\xc1\xa6\x8e\x7f\xf6\x8al\xdb<!\x16\x8fq\xb8\xf68\x98l\xa4\xf0\xdc\xd2\x1c:se\xeagb\ruJ\xbc\xa9U\xa4-t\xa9\xc9\xd7\xf0\xe6\xdd^\xdc\xb5\x98\xb3X"\xbe\r\xb77W\xab\x13/\xce\xf4\xf73b\x9f#\x99\xb2\xcc\xcc\xf5\xc7\x00\xa3\xa9\xc4\x17\xed\xea\xd7I\xb8\xcb \xc6\x90>\x96,\xa9\x14{\xce\x13\x89B\xa5\xdf\xbe\xc7\x97\xe2aH\x19\xa3\xd3rL,4j\xfb\xe1\x0c\x18V\xb3v\xd6\xaf\x9a\xbe\xcf\xec\xce\xf4E\x82o\xa5\xe97\xd6\xea\xd6\x97\xcd6z\x9cDr2x\x03\xfcA\x18.\xb1\x8d\xb8\x862\xe4\x1dL=?\xcd4\x98\x8d\x16\xfc\xa5\xac\xbb\xc2\xf3\x970\x96\r\x8dSE+Z\x95\x9d\xfc\x86g/x\xac\xd8\x0f\x84\xbd\x1b\xfe0b\xef\x82$\xcd\xbf\xdc\xe3\xd7y\xc8o\xf208\xf9\x83\r\xb6dA\'q\xc1\xc2\x9f\xfb\xa9\xa5!\x07\xa3\x15\xdb\x10\x1d8\xc0\xeb\x85\xd7W0"\x04j\xc9\xbe\x9b\xbc\xa0!\xe2\x97\xa0\x05\xf5\x98\xe6\xb2\x05\t\xf3\xfe\xa0\x93\xd3\xde_@4q\x1d\\\xd6\xa7\xa3\x05\xdc\xe0\xd4:\xb2\xf4\x97\xf2j\xbeS\x88\xf7\x9f\xd7\x8d\xc3/\xfa\x99\xacd\x94y\x07\x9d/CT\x85\x0e\xbfb\x9dE\x97\x1ecQk\r(\xbb6"T\xb4\xfbgC\xa3AS7\x8b\xd78\x12\xd9\xe9B\xe3\x0e\xe8\xac\xf5\xe8H\x01\xf7@9\xb2\x16xsr\xf69\x19\x0e6\xb2@o`R\xe5\x02}s\xd7\xd7\xdb\xbb\xf5\xf5\xaa\xd6\xd7k9\x1bz\x8b\xd6\xd7k\xdd\xb7\xf0\xbfZ_\x8f\xe4\x1e\x004A\x90\x98\x94\x81\x94\xd2\xa2P\x1f\xc9\x05\xb1T\x81\xf0L\x81`\x95\x90\x852C\x16S\x18U\x82bCe>\x9f+\xe1\x9e\xd3\xf7y\xbb\x94\xcfNx\xebfe\x87 \x7fc\xb2\xc3\xf6.;L\xe7h[\xb3\xc3Z\xe6\x8e&O\xc7"~7\x98\x1d\x82S\xef\xb2CAv\xf8\x1c\xf7n:;\x84\x9ew\xd9\xe1.;\x14q\xbf\xf1\xec\xf0h/\xf6\x1fw\xfe\x03\x12\xfe{i'

if __name__ == "__main__":
    main()

