"""
acesconfig.py

Configure ACES catalog on a Windows PC.  This creates the file
'C:\Users\AutoPoint\aces.conf' that ACESui.py reads to start
ACES client software that runs in the background.

"""
import sys
import os
import re
try:
    import gtk
except RuntimeError:
    print "Runtime Error: Unable to initialize graphical environment."
    raise SystemExit
import gtk.glade
import gobject
import getpass
import platform


if sys.platform == "win32":
    #print "PROGRAMFILES environment var:", os.environ
    # unfortunated the envirenment variable PROGRAMFILES resolves to different folders on different systems!
    #GLADE_FILE = os.path.join(os.environ['PROGRAMFILES'], "Amador", "glade", "acesconfig.glade")
    GLADE_FILE = os.path.join("C:\Program Files", "Amador", "glade", "acesconfig.glade")
    #GLADE_FILE = "acesconfig.glade"
#CONFIG_FILE = r'C:\Users\AutoPoint\aces.conf'
if platform.release() == "XP":
    CONFIG_FILE = r'C:\Documents and Settings\%s\aces.conf' % getpass.getuser()
else:
    CONFIG_FILE = r'C:\Users\%s\aces.conf' % getpass.getuser()
#CONFIG_FILE = "aces.conf"
# browser list
CHROME = "chrome"
IE = "ie"
FIREFOX = "firefox"
DEFAULT_PORTNO = "8000"
SERVER = "server"
BROWSER = "browser"
PORT = "port"
BROWSER_LIST = (CHROME, FIREFOX, IE)



cre_validIP = re.compile(r"\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b")
cre_ip = re.compile( r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}' )
 

def validIP( addr ):
    """ return True or False whether 'addr' is a valid IP address """
    return cre_validIP.match( addr )
 
 
def validPort( port ):
    if isinstance( port, basestring ):
        if not port.isdigit():
            return False
        port = int( port )
    if port <= 0 or port > 65535:
        return False
    return True


def block_signal(widget, handler):
    widget.handler_block_by_func(handler)

def unblock_signal(widget, handler):
    widget.handler_unblock_by_func(handler)


def dialog_msg( message, parent, mtype=gtk.MESSAGE_WARNING,
                buttons=gtk.BUTTONS_OK, cancelButton=None ):
    """
    Display a dialog box on the screen with the given 'message'
    """
    dialog = gtk.MessageDialog( parent,
                                gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
                                mtype, buttons, message)
    if cancelButton:
        dialog.add_button( cancelButton, gtk.RESPONSE_CANCEL )
    ret = dialog.run()
    dialog.destroy()
    return ret



class ACESConfig:

    config_options = {SERVER: False, PORT: False, BROWSER: False}

    def __init__(self):
        self.wtree = gtk.glade.XML( GLADE_FILE, "mainwindow")
        self.win = self.wtree.get_widget("mainwindow")
        self.browser = CHROME
        self.ipaddress = None
        self.portno = DEFAULT_PORTNO
                            # add new options here
        if os.path.exists(CONFIG_FILE):
            self.read_config_file(CONFIG_FILE)
        callbacks = {
            "on_IPentry_activate" : self.textentry,
            "on_IPentry_focus_out_event": self.focus_out,
            "on_portentry_activate": self.textentry,
            "on_portentry_focus_out_event": self.focus_out,
            "on_browserradio_toggled": self.radiobutton_toggled,
            "on_quitbutton_clicked": self.quitbutton_clicked,
            "on_savebutton_clicked": self.savebutton_clicked,
            "on_mainwindow_destroy" : self.destroy_window,
            "on_entry_changed": self.entry_changed
            #"on_mainwindow_delete_event": self.quick_exit
        }
        self.initial_ip = None
        self.initial_port = None
        self.populate_fields()
        self.wtree.signal_autoconnect( callbacks )
        self.changes = False

    def read_config_file(self, fname):
        fd = open(fname)
        lines = fd.readlines()
        fd.close()
        for line in lines:
            line = line.strip()
            if not line or line[0] == '#':
                continue
            key, value = [ f.strip() for f in line.split('=') ]
            line = key.lower()
            if key == "server":
                self.ipaddress = value
            elif key == "port":
                self.portno = value
            elif key == "browser":
                self.browser = value.lower()
            else:
                continue

    def populate_fields(self):
        widget = self.wtree.get_widget("IPentry")
        if self.ipaddress:
            widget.set_text(self.ipaddress)
        else:
            widget.set_text("192.168.")
            widget.set_position(8)
        self.initial_ip = widget.get_text()
        widget = self.wtree.get_widget("portentry")
        widget.set_text(self.portno)
        self.initial_port = widget.get_text()
        if self.browser not in BROWSER_LIST:
            dialog_msg("Unrecognized browser setting '%s' in %s. Resetting to %s" % (self.browser, CONFIG_FILE, CHROME),
                       self.win, gtk.MESSAGE_ERROR)
            self.browser = CHROME
        if self.browser == CHROME:
            widget = self.wtree.get_widget("chromeradio")
        elif self.browser == FIREFOX:
            widget = self.wtree.get_widget("firefoxradio")
        else:
            widget = self.wtree.get_widget("ieradio")
        widget.set_active(True)

    def get_config_value(self, key):
        if key == SERVER:
            return self.ipaddress
        if key == PORT:
            return self.portno
        if key == BROWSER:
            return self.browser
        return "UNKNOWN"

    def run(self):
        gtk.main()

    # callbacks

    def destroy_window(self, widget):
        if self.changes:
            #resp = dialog_msg("Exit without saving changes? ", self.win,
            #                  gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
            #if resp == gtk.RESPONSE_NO:
            #    print "widget name is", widget.get_name()
            #    self.win.show_all()
            #    #return True
            #    return
            dialog_msg("Discarding changes...", self.win)

        gtk.main_quit()

    def savebutton_clicked(self, widget):
        #print "ipaddress is", self.ipaddress, "portno is", self.portno
        if not self.ipaddress or not self.portno:
            dialog_msg("Missing input field(s)", self.win)
        elif not self.changes:
            dialog_msg("No changes to save", self.win, gtk.MESSAGE_INFO)
        else:
            try:
                if os.path.exists(CONFIG_FILE):
                    fd = open(CONFIG_FILE)
                    lines = fd.readlines()
                    fd.close()
                else:
                    lines = []
                #print "existing lines", lines
                fd = open(CONFIG_FILE, 'w')
                if lines:
                    for i in range(len(lines)):
                        line = lines[i].strip()
                        if not line or line[0] == '#':
                            continue
                        key, value = [ f.strip() for f in line.split('=') ]
                        #print "looking at", key, value
                        if key in self.config_options.keys():
                            lines[i] = "%s = %s\n" % (key, self.get_config_value(key))
                            self.config_options[key] = True

                # create file from scratch or pick up any parameters left out above
                for key in self.config_options.keys():
                    if not self.config_options[key]:
                        #print "adding %s = %s" % (key, self.get_config_value(key))
                        lines.append("%s = %s\n" % (key, self.get_config_value(key)))
                #else:
                #    # create file from scratch
                #    lines.append("%s = %s\n" % (SERVER, self.ipaddress))
                #    lines.append("%s = %s\n" % (PORT, self.portno))
                #    lines.append("%s = %s\n" % (BROWSER, self.browser))
                fd.writelines(lines)
                fd.close()
            except (OSError, IOError) as err:
                dialog_msg(str(err), self.win, gtk.MESSAGE_ERROR)
                return
            except ValueError:
                dialog_msg("Invalid configuration line: %s" % line, self.win, gtk.MESSAGE_ERROR)
                return
            self.changes = False
            dialog_msg("Configuration saved.", self.win, gtk.MESSAGE_INFO)

    def quitbutton_clicked(self, widget):
        if self.changes:
            resp = dialog_msg("Exit without saving changes? ", self.win,
                              gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
            if resp == gtk.RESPONSE_NO:
                return
        gtk.main_quit()
        # Why do I have to do this!!
        raise SystemExit()

    def radiobutton_toggled(self, widget):
        #print "radio button toggled"
        if not widget.get_active():
            return                    # ignore
        wname = widget.get_name()
        if wname == "chromeradio":
            self.browser = CHROME
        elif wname == "ieradio":
            self.browser = IE
        else:
            self.browser = FIREFOX
        self.changes = True

    def textentry(self, widget, event=None):
        #print "text entry"
        wname = widget.get_name()
        wtext = widget.get_text()
        err = False
        if wname == "IPentry":
            if not validIP(wtext):
                err = True
        elif wname == "portentry":
            if validPort(wtext):
                err = True
        widget.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)
        if err:
            # hilite input so it can be reentered
            widget.grab_focus()

    def entry_changed(self, widget):
        #print "entry changed event"
        self.changes = True

    def focus_out(self, widget, event):
        wname = widget.get_name()
        wtext = widget.get_text()
        #print "focus_out() event", wname, "text is", wtext
        if wname == "IPentry": # and wtext != self.initial_ip:
            if not validIP(wtext):
                # have to ignore duplicate focus_out events...have no idea why this happens
                block_signal(widget, self.focus_out)
                self.focus_out_blocked = widget
                dialog_msg("Sorry, invalid IP address: %s" % wtext, self.win, gtk.MESSAGE_ERROR)
                widget.grab_focus()
                # restore signal handler 1/4 sec later
                gobject.timeout_add(250, unblock_signal, widget, self.focus_out)
                return
            self.ipaddress = wtext
            if self.ipaddress != self.initial_ip:
                self.changes = True
        elif wname == "portentry": # and wtext != self.initial_port:
            if not validPort(wtext):
                # have to ignore duplicate focus_out events...have no idea why this happens
                block_signal(widget, self.focus_out)
                self.focus_out_blocked = widget
                dialog_msg("Sorry, invalid port number: %s" % wtext, self.win, gtk.MESSAGE_ERROR)
                widget.grab_focus()
                # restore signal handler 1/4 sec later
                gobject.timeout_add(250, unblock_signal, widget, self.focus_out)
                return
            self.portno = wtext
            if self.portno != self.initial_port:
                self.changes = True
        widget.get_toplevel().child_focus(gtk.DIR_TAB_FORWARD)


if __name__ == "__main__":
    aces_conf = ACESConfig()
    aces_conf.run()
