#!/usr/bin/env python
"""
jxconfig.py

Program to set the JX Client configuration properties

"""

import sys
import os
try:
    import gtk
except RuntimeError:
    print "Runtime Error: Unable to initialize graphical environment."
    raise SystemExit
import re
import gtk.glade
from jxproperties import JXProperties, JXPropertiesError



# These must match combobox settings for logging
LOG_SETTINGS = ["ERROR", "DEBUG", "INFO", "ALL"]


if sys.platform == "win32":
    GLADE_FILE = os.path.join(os.environ['PROGRAMFILES'], "Amador", "glade", "jxconfig.glade")
else:
    GLADE_FILE = '/usr/share/amador/glade/jxconfig.glade'
# compiled regular expressions
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")
# this is pretty simplistic for a valid hostname but good enough for our purposes
cre_validHostname = re.compile(r'\w+(\.\w+)+')


def validIP(addr):
    return cre_validIP.match( addr )

def validHostname(hostname):
    return cre_validHostname.match(hostname)

def validPort(port):
    if not port.isdigit():
        return False
    val = int(port)
    if val < 1024 or val > 65535:
        return False
    return True

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 JXConfig:
    # define the default order for the focus of the fields
    focus_order = {
        "locationEntry" : "standAloneCheckBox",
        "standAloneCheckBox" : "posIPEntry",
        "posIPEntry" : "posIPPortEntry",
        "posIPPortEntry" : "WHI_IPEntry",
        "WHI_IPEntry" : "WHI_PortEntry",
        "WHI_PortEntry" : "posReqIPEntry",
        "posReqIPEntry" : "posReqPortEntry",
        "posReqPortEntry" : "loglevelCombo",
        "loglevelCombo" : "locationEntry"
    }

    def __init__(self):
        self.wtree = gtk.glade.XML( GLADE_FILE, "mainwindow")
        callbacks = {
            "on_locationEntry_activate" : self.textentry,
            "on_locationEntry_focus_in_event" : self.focus_in,
            "on_locationEntry_focus_out_event" : self.textentry,
            "on_standAloneCheckBox_toggled" : self.checkbox_toggle,
            "on_posIPEntry_activate" : self.textentry,
            "on_posIPEntry_focus_in_event" : self.focus_in,
            "on_posIPEntry_focus_out_event" : self.textentry,
            "on_posIPPortEntry_activate" : self.textentry,
            "on_posIPPortEntry_focus_in_event": self.focus_in,
            "on_posIPPortEntry_focus_out_event" : self.textentry,
            "on_posReqIPEntry_activate" : self.textentry,
            "on_posReqIPEntry_focus_in_event" : self.focus_in,
            "on_posReqIPEntry_focus_out_event" : self.textentry,
            "on_posReqPortEntry_activate" : self.textentry,
            "on_posReqPortEntry_focus_in_event": self.focus_in,
            "on_posReqPortEntry_focus_out_event" : self.textentry,
            "on_WHI_IPEntry_activate" : self.textentry,
            "on_WHI_IPEntry_focus_in_event" : self.focus_in,
            "on_WHI_IPEntry_focus_out_event" : self.textentry,
            "on_WHI_PortEntry_activate" : self.textentry,
            "on_WHI_PortEntry_focus_in_event" : self.focus_in,
            "on_WHI_PortEntry_focus_out_event" : self.textentry,
            "on_loglevelCombo_changed" : self.loglevel_change,
            "on_radiobuttonLinux_toggled" : self.radio_toggle,
            "on_radiobuttonWindows_toggled" : self.radio_toggle,
            "on_buttonquit_clicked" : self.quit,
            "on_buttoncancel_clicked" : self.cancel,
            "on_buttonsave_clicked" : self.save,
            "on_mainwindow_delete_event" : self.delete_event,
            "on_mainwindow_key_press_event" : self.key_press_event,
            "on_mainwindow_destroy" : gtk.main_quit
        }
        self.win = self.wtree.get_widget("mainwindow")
        self.winprop = None
        self.linuxprop = None
        # Set this flag after dialog input error so text will be highlighted for correction
        self._select_text = False
        self._save_text = None
        if sys.platform.startswith("linux"):
            ostype = "linux"
        elif sys.platform.startswith("win") or sys.platform == "cygwin":
            ostype = "windows"
            self.wtree.get_widget("radiobuttonWindows").set_active(True)
            self.wtree.get_widget("radiobuttonLinux").set_sensitive(False)
            if sys.platform == "win32":
                # enables images on buttons!
                gtk.settings_get_default().set_long_property("gtk-button-images", True, "main")
        else:
            dialog_msg("Sorry, unsupported Operating System", self.win, gtk.MESSAGE_ERROR)
            raise SystemExit
        try:
            self.activeprop = JXProperties(ostype)
            self.activeprop.read()
            if ostype == "linux":
                self.linuxprop = self.activeprop
            else:
                self.winprop = self.activeprop
        except (JXPropertiesError, IOError), err:
            dialog_msg(str(err), self.win, gtk.MESSAGE_ERROR)
            raise SystemExit
        # save log levels.  Currently just ERROR and DEBUG (are there more?)
        widget = self.wtree.get_widget('loglevelCombo')
        self.loglevels = widget.get_model()
        self.init_fields( self.activeprop )
        self.wtree.signal_autoconnect( callbacks )

    def init_fields(self, jxprop):
        """
        Initialize the fields and save as attributes all the entry fields so that
        the valid_data() routine can easily access them.
        """
        # currently these fields are not used
        self.wtree.get_widget("httpIPEntry").set_sensitive(0)
        self.wtree.get_widget("httpPortEntry").set_sensitive(0)
        # for easy access to properties attribute
        prop = jxprop.properties
        widget_list = []
        widget = self.wtree.get_widget("locationEntry")
        widget_list.append(widget)
        widget.set_text(prop['ClientProperties'][0])
        widget = self.wtree.get_widget("posIPEntry")
        widget_list.append(widget)
        widget.set_text(prop['POSProperties'][0])
        widget = self.wtree.get_widget("posIPPortEntry")
        widget_list.append(widget)
        widget.set_text(prop['POSProperties'][1])
        widget = self.wtree.get_widget("posReqIPEntry")
        widget_list.append(widget)
        widget.set_text(prop['POSRequestProperties'][0])
        widget = self.wtree.get_widget("posReqPortEntry")
        widget_list.append(widget)
        widget.set_text(prop['POSRequestProperties'][1])
        widget = self.wtree.get_widget("WHI_IPEntry")
        widget_list.append(widget)
        widget.set_text(prop['RemotingServerProperties'][0])
        widget = self.wtree.get_widget("WHI_PortEntry")
        widget_list.append(widget)
        widget.set_text(prop['RemotingServerProperties'][1])
        widget = self.wtree.get_widget("loglevelCombo")
        #if prop['Log4j'][0] == "DEBUG":
        #    widget.set_active(1)
        #else:
        #    widget.set_active(0)
        try:
            i = LOG_SETTINGS.index(prop['Log4j'][0])
        except ValueError:
            i = 0
        widget.set_active(i)
        # save these widgets for valid_data() routine
        self.widgets = widget_list

    def fillout(self):
        gtk.main()

    def valid_data(self):
        """
        Make sure the data fields are sane values
        """
        for widget in self.widgets:
            value = widget.get_text()
            if not value:
                dialog_msg("Sorry, field cannot be blank", self.win)
                widget.grab_focus()
                return False
            wname = widget.get_name()
            if wname.endswith("IPEntry"):
                if (value[0].isdigit() and not validIP(value)) or not validHostname(value):
                    dialog_msg("Invalid IP or Hostname entry: %s" % value, self.win)
                    # for reasons i don't understand this doesn't work
                    ##widget.select_region( 0, -1 )
                    widget.grab_focus()
                    self._select_text = True        # see focus_in callback
                    return False
            elif wname.endswith("PortEntry"):
                if not validPort(value):
                    dialog_msg("Invalid Port number or out of range: %s" % value, self.win)
                    # this doesn't work
                    ##widget.select_region( 0, -1 )
                    widget.grab_focus()
                    self._select_text = True        # see focus_in callback
                    return False
        return True

    ############## callbacks ##############
    def textentry(self, widget, event=None):
        """
        NOTE: 'event' is ONLY defined for a focus_out event when user
        leaves the field
        """
        wname = widget.get_name()
        if event:
            value = widget.get_text().strip()
            if not value:
                return
            props = self.activeprop.properties
            # set ix (index) to either 0 (False) if changing first entry or 1 (True)
            # if changing second entry
            if wname == 'locationEntry':
                fname = 'ClientProperties'
                # NOTE: this index should ALWAYS be 0
                ix = 0
            elif wname in ('posIPEntry', 'posIPPortEntry'):
                fname = 'POSProperties'
                ix = (wname == 'posIPPortEntry')
            elif wname in ('posReqIPEntry', 'posReqPortEntry'):
                fname = 'POSRequestProperties'
                ix = (wname == 'posReqPortEntry')
            elif wname in ('WHI_IPEntry', 'WHI_PortEntry'):
                fname = 'RemotingServerProperties'
                ix = (wname == 'WHI_PortEntry')
            else:
                raise ValueError("Unexpected widget: %s", wname)

            # NOTE: if the value hasn't changed this does nothing
            self.activeprop.update(fname, ix, value)
        else:
            nextwidget = self.wtree.get_widget( self.focus_order[wname])
            nextwidget.grab_focus()
            # fix to prevent gray highlighting that sometimes happens
            nextwidget.select_region(0,0)

    def focus_in(self, widget, event):
        if self._select_text:
            # after dialog error, select the bad input for correction
            # (is there a better way to do this?)
            widget.select_region(0,-1)
            self._select_text = False
        else:
            # save field text incase user hits escape so it can be restored
            text = widget.get_text()
            if text:
                self._save_text = text

    def checkbox_toggle(self, widget ):
        wname = widget.get_name()
        if wname == 'standAloneCheckBox':
            if widget.get_active():
                dialog_msg( "Sorry, this option is not supported (yet?).",
                            self.win, gtk.MESSAGE_ERROR)
                # Turn the check box back off
                widget.set_active(False)

    def radio_toggle(self, widget):
        if not widget.get_active():
            return                    # ignore
        wname = widget.get_name()
        if wname == 'radiobuttonLinux':
            self.activeprop = self.linuxprop
            osver = "Linux"
        elif wname == 'radiobuttonWindows':
            if self.winprop is None:
                try:
                    self.winprop = JXProperties('windows')
                    self.winprop.read()
                except JXPropertiesError, err:
                    dialog_msg(str(err), self.win, gtk.MESSAGE_ERROR)
                    return
            self.activeprop = self.winprop
            osver = "Windows thin client"
        else:
            return
        dialog_msg(" Switching to %s settings. " % osver, self.win, gtk.MESSAGE_INFO)
        self.init_fields( self.activeprop )

    def quit(self, widget):
        if (self.linuxprop and self.linuxprop.changes()) or (self.winprop and self.winprop.changes()):
            resp = dialog_msg("Quit without saving changes?", self.win, gtk.MESSAGE_QUESTION,
                                   gtk.BUTTONS_YES_NO)
            if resp == gtk.RESPONSE_NO:
                return
        gtk.main_quit()

    def cancel(self, widget):
        if (self.linuxprop and self.linuxprop.changes()) or (self.winprop and self.winprop.changes()):
            resp = dialog_msg("Discard changes?", self.win, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO)
            if resp == gtk.RESPONSE_NO:
                return
        gtk.main_quit()

    def save(self, widget):
        savemsg = ''
        if not self.valid_data():
            return
        if self.linuxprop and self.linuxprop.changes():
            try:
                self.linuxprop.write()
            except (JXPropertiesError, IOError), err:
                dialog_msg(str(err), self.win, gtk.MESSAGE_ERROR)
                return
            else:
                savemsg += "Linux"
        if self.winprop and self.winprop.changes():
            try:
                self.winprop.write()
            except (JXPropertiesError, IOError), err:
                dialog_msg(str(err), self.win, gtk.MESSAGE_ERROR)
                return
            else:
                if savemsg:
                    savemsg += " and "
                savemsg += "Windows"
        if savemsg:
            dialog_msg("%s settings have been saved." % savemsg, self.win, gtk.MESSAGE_INFO)
        else:
            dialog_msg("No changes to save.", self.win, gtk.MESSAGE_WARNING)

    def loglevel_change(self, widget):
        i = widget.get_active()
        logstr = self.loglevels[i][0]
        self.activeprop.update('Log4j', 0, logstr)

    def delete_event(self, event, junk):
        self.quit(junk)
        # Must return True in case user backs out of window close because change wasn't saved
        return True

    def key_press_event(self, widget, event):
        if gtk.gdk.keyval_name(event.keyval) == 'Escape':
            # restore original input text for this field
            widget = widget.get_focus()
            ##print "saved text is", self._save_text
            widget.set_text(self._save_text)

# END class JXConfig



if __name__ == '__main__':
    JXConfig().fillout()
