#!/usr/bin/python
# Copyright (C) Andrew Mitchell 2006

#  This program is free software; you can redistribute it and/or modify
#  it under the terms of the GNU General Public License as published by
#  the Free Software Foundation; either version 2 of the License, or
#  (at your option) any later version.

#  This program is distributed in the hope that it will be useful,
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

#  You should have received a copy of the GNU General Public License
#  along with this program; if not, write to the Free Software
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
############################################################################

import sys, os, string, fcntl, copy, posix
import apt, apt_pkg
from apt import SizeToStr
from apt.progress import OpProgress, FetchProgress, InstallProgress


import gtk
import gtk.glade
import vte
import gnome.ui
import gnomecanvas
import gobject

VERBOSE=True

from AuthTool.authtools import Authtools, Debconf
# Single dialog with auth method selector

class AuthtoolGTK:

    def __init__(self):

        self.authtools = Authtools()
        self.initial_method = self.authtools.get_current()

        self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
        self.window.connect('delete_event', self.destroy)
        self.window.set_title("Network Authentication Properties")
        self.window.set_border_width(12)
        
        self.vbox = gtk.VBox(False, 6)
        self.window.add(self.vbox)        
        self.liststore = gtk.ListStore(gobject.TYPE_STRING)
        
        auth_methods = gtk.ComboBox(self.liststore)
        cell = gtk.CellRendererText()
        auth_methods.pack_start(cell, True)
        auth_methods.add_attribute(cell, 'text', 0)

        
        self.methods = self.authtools.get_methods()
        self._methods={}
        count = 0
        for key in self.methods:
            self._methods[self.methods[key]] = key
            if self.authtools.method_display(key):
                self.liststore.append( [self.methods[key]] )
                if key == self.authtools.get_current():
                    auth_methods.set_active(count)
                count += 1

        #auth_methods.set_popdown_strings(auth_list)
        auth_methods.connect('changed', self.combo_auth_method)
        
        cbox = gtk.HBox(False,0)
        clabel = gtk.Label("This computer is: ")
        clabel.set_justify(gtk.JUSTIFY_RIGHT)
        cbox.pack_start(clabel, False)
        cbox.pack_end(auth_methods)
        self.vbox.pack_start(cbox, False, False, 6)

        # button = None
#         for key in self.methods:
#             if self.authtools.method_display(key):
#                 button = gtk.RadioButton(button, self.methods[key])
#                 button.connect("toggled", self.button_auth_method, self.methods[key])
#                 button.set_active( (self.authtools.get_current() == key ))
#                 self.vbox.pack_start(button, False, False, 5)
#                 button.show()


        self.button_box = gtk.HBox(False, 0)

        help_btn = gtk.Button(stock=gtk.STOCK_HELP)
        help_btn.connect_object("clicked", self.help, self.window,None)
        
        ok_btn = gtk.Button(stock=gtk.STOCK_OK)
        ok_btn.connect_object("clicked", self.apply_changes, self.window,None)
        
        close_btn = gtk.Button(stock=gtk.STOCK_CANCEL)
        close_btn.connect_object("clicked", self.close_application, self.window,None)

        self.button_box.pack_start(help_btn, False, True, 6)
        self.button_box.pack_end(ok_btn, False, True, 6)
        self.button_box.pack_end(close_btn, False, True, 6)


        self.vbox.pack_end(self.button_box, False, False, 0)

        close_btn.set_flags(gtk.CAN_DEFAULT)
        close_btn.grab_default()
        close_btn.show()


        #self.configframe = gtk.Frame("Authentication Configuration")
        #self.configframe.set_shadow_type()
        self.configtable = gtk.Table(rows=1,columns=2)
        #self.configframe.add(self.configtable)

        # for those methods that supply their own UI:
        self.configbox = gtk.VBox(False,0)
        
        self.vbox.add(self.configtable)

        # Find out the current auth method & display its config panel
        current = self.authtools.get_current()
        cfg = self.authtools.get_method_config(current)

        self.display_config(cfg, current)

        #self.window.set_default_size(640, 480)

        self.window.show_all()

    def help(self, widget, data=None):
        """Display help/about dialogue box"""
        #self.help_win = gtk.Dialog("Help", self.window, gtk.DIALOG_MODAL | gtk.DIALOG_DESTROY_WITH_PARENT,
        #                           (gtk.STOCK_CANCEL, gtk.RESPONSE_REJECT,
        #                            gtk.STOCK_OK, gtk.RESPONSE_ACCEPT))
        about = "Network Authentication properties\n\nConfigure the workstation to authenticate against various network services"
        #print about
        self.help_win = gtk.MessageDialog(self.window,
                                          0,
                                          gtk.MESSAGE_INFO,
                                          gtk.BUTTONS_OK,
                                          about)
        self.help_win.set_default_response(gtk.BUTTONS_OK)
        self.help_win.run()
        
        self.help_win.destroy()
        

    def apply_changes(self, widget, data=None):

        """Enable the configuration selected"""
        #find the selected radio button
        method = self.authtools.get_current()

        # Check if the required packages are installed
        packages = self.authtools.required_packages(method)
        missing = []
        
        cache = apt.Cache()
        for package in packages:
            #print dir(cache)
            pkg = cache[package]
            if not pkg.isInstalled:
                #print "%s is not installed" % package
                missing.append(package)
                #print missing

        if missing != []:
            # Need to install some packages
            #popup "do you want to install package" dialogue box
            info = "To enable this authentication method, additional packages are required:\n"
            for x in missing:
                info += "\n  %s" % x
            info += "\n\nInstall these packages?"

            self.pkg_win = gtk.MessageDialog(self.window,
                                          0,
                                          gtk.MESSAGE_QUESTION,
                                          gtk.BUTTONS_YES_NO,
                                          info)
            self.pkg_win.set_default_response(gtk.BUTTONS_YES_NO)

            self.pkg_win.connect_object('response', self.pkg_win_response, self.pkg_win, None)
            self.pkg_win.run()
            
            self.pkg_win.destroy()

            if self.install_pkg:
                self.install_packages(missing)
        
        # Disable previous configuration
        self.authtools.disable(self.initial_method)
        # Now enable new one
        self.authtools.enable(method, VERBOSE)
        self.authtools.commit_current()

        # Quit app on OK
        gtk.main_quit()

    def pkg_win_response(self, widget, response=None, help=None):
        if response == gtk.RESPONSE_YES:
            self.install_pkg = True
        elif response == gtk.RESPONSE_NO:
            self.install_pkg = False


    def combo_auth_method(self, widget, data=None):
        #print widget.get_active_iter()
        # combobox doesn't fire off 'changed' twice

        # clear up existing questions
        kids = self.configtable.get_children()
        for w in kids:
            self.configtable.remove(w)

        # use self.previous to deconfigure old

        # find new method
        # first time round, widget is gtk.Button
        if isinstance(widget, gtk.Button):
            print widget.get_label()
        method_str = self.liststore[widget.get_active()][0]
        method = self._methods[method_str]
        self.previous = method 
        print "Initial is ", self.initial_method
        #print "Will disable ", method
        print "Selected ",method_str
        self.authtools.set_current(method)


        # Get config questions for the auth method
        cfg = self.authtools.get_method_config(method)
        self.display_config(cfg, method)

        

    def button_auth_method(self, widget, data=None):
        if not widget.get_active():
            # bail out for now if it's the old option
            # set previous option
            method_str =  widget.get_label()
            method = self._methods[method_str]
            self.previous = method
            print "Initial is ", self.initial_method
            print "Will disable ", method
            
            # clear out the configtable
            kids = self.configtable.get_children()
            for widget in kids:
                self.configtable.remove(widget)
            return

        print "Selected ",widget.get_label()
        method_str =  widget.get_label()
        method = self._methods[method_str]
        self.authtools.set_current(method)

        # Check if the required packages are installed
        packages = self.authtools.required_packages(method)

        cache = apt.Cache()
        for package in packages:
            #print dir(cache)
            pkg = cache[package]
            if not pkg.isInstalled:
                print "%s is not installed" % package
                #popup "do you want to install package" dialogue box

        # Get config questions for the auth method
        cfg = self.authtools.get_method_config(method)
        self.display_config(cfg, method)
        #self.authtools.enable(method)


        

    def display_config(self, cfg, method):
        # using the config list of dictionaries of lists, draw up the widgets & hook up the callbacks
        # paint the main class first & then its dependencies
        #print cfg
        TYPE=0
        LABEL=1
        DEFAULT=2

        widgets = {}
        labels = {}
        w = 0

        # method is the primary config class
        m = self.authtools.get_method_obj(method)
        print m
        if hasattr(m, 'gtk_config'):
            pass
#             self.vbox.remove(self.configtable)
#             self.vbox.add(self.configbox)
#             m.gtk_config(self.configbox)
#             return
        else:
            self.vbox.remove(self.configbox)
            self.vbox.add(self.configtable)
            

        # Move the rest of this into IAuthConfig, where it belongs
        #Add widgets in the correct order
        # Use separating label between config sections
        
        methods=[]
        if cfg.has_key(method):
            methods = [method]
        if m.requires != [] and m.requires != None:
            for key in m.requires:
                if cfg.has_key(key):
                    methods.append(key)

        for key in methods:
            #work through the config options, creating widgets as needed
            # stick all the widgets in dictionaries/lists
            widgets[key] = {}
            labels[key] = {}
            
            for widget_id in cfg[key]:
                widget = cfg[key][widget_id]
                #print widget_id, widget
                if widget[TYPE] == 'title':
                    pass
                elif widget[TYPE] == 'string' or widget[TYPE] == 'password':
                    #add text entry to self.configtable
                    widgets[key][widget_id] = gtk.Entry(150)
                    if widget[TYPE] == 'password':
                        widgets[key][widget_id].set_visibility(False)
                    widgets[key][widget_id].set_text( widget[DEFAULT] )
                    labels[key][widget_id] = gtk.Label( "%s: " %widget[LABEL] )
                    labels[key][widget_id].set_justify(gtk.JUSTIFY_LEFT)
                    #print "Adding text entry"
                    #self.configtable.add(widgets[key][widget_id])

                    self.configtable.attach( labels[key][widget_id],
                                             0,1,
                                             w,w+1,
                                             gtk.SHRINK,
                                             gtk.SHRINK,
                                             6,6

                                             )
                    self.configtable.attach( widgets[key][widget_id],
                                             1,2,
                                             w,w+1,
                                             gtk.FILL|gtk.EXPAND|gtk.EXPAND,
                                             gtk.SHRINK,
                                             6,6

                                             )
                    #Connect them widgets up
                    widgets[key][widget_id].connect("changed", self.config_changed, (key,widget_id))
                    w += 1

        # 'More Options' should be displayed iff there are more options
        # self.morebutton = gtk.Button("More Options")
#         self.configtable.attach ( self.morebutton,
#                                   0,1,
#                                   w,w+1,
#                                   gtk.SHRINK,
#                                   gtk.SHRINK
#                                   )
        
        self.configtable.show_all()

    def config_changed(self, widget, data=None):
        #print "Config changed"
        # data contains the method & config option that was changed
        #print data, widget.get_text()

        # Call store() on the method in question
        m = self.authtools.get_method_obj(data[0])
        m.store(data[1], widget.get_text() )

    def close_application(self, widget, event, data=None):
        self.authtools.set_current(self.initial_method)
        self.authtools.commit_current()
        gtk.main_quit()
        return False
    
    def destroy(self, widget, data=None):
        #self.authtools.set_current(self.initial_method)
        #self.authtools.commit_current()
        gtk.main_quit()


    def install_packages(self, packages):
        cache = apt.Cache()

        #print "Installing..."
        # update the cache
        fprogress = GuiFetchProgress()
        iprogress = TermInstallProgress()
        
        # update the cache
        # cache.Update(fprogress)
        # cache = apt_pkg.GetCache(progress)
        # depcache = apt_pkg.GetDepCache(cache)
        # depcache.ReadPinFile()
        # depcache.Init(progress)

        # show the interface
        while gtk.events_pending():
            gtk.main_iteration()
            
        for item in packages:
            pkg = cache[item]
            if not pkg.isInstalled:
                pkg.markInstall()
                
        try:
            # This is why it's a singleton
            db = Debconf()
            db.unlock()
            cache.commit(fprogress, iprogress)
            cache.commit()
            
            # Get debconf back from the dead
            db.reinit()
        except IOError:
            self.err_dlg = gtk.MessageDialog(self.window, 0,
                                                     gtk.MESSAGE_ERROR,
                                                     gtk.BUTTONS_OK,
                                                     "Error: %s" % sys.exc_info()[1])
            self.err_dlg.set_default_response(gtk.BUTTONS_OK)
            self.err_dlg.run()
            self.err_dlg.destroy()





    def main(self):
        gtk.main()




class GuiFetchProgress(gtk.Window, FetchProgress):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_border_width(12)
        self.vbox = gtk.VBox()
        self.vbox.show()
        self.add(self.vbox)
        self.progress = gtk.ProgressBar()
        self.progress.show()
        self.label = gtk.Label()
        self.label.show()
        self.vbox.pack_start(self.progress)
        self.vbox.pack_start(self.label)
        self.resize(300,100)
        
    def start(self):
        #print "start"
        self.progress.set_fraction(0.0)
        self.show()
        
    def stop(self):
        self.hide()
        
    def pulse(self):
        FetchProgress.pulse(self)
        self.label.set_text("Speed: %s/s" % apt_pkg.SizeToStr(self.currentCPS))
        #self.progressbar.set_fraction(self.currentBytes/self.totalBytes)
        while gtk.events_pending():
                gtk.main_iteration()
        return True

class TermInstallProgress(InstallProgress, gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_border_width(12)
        self.show()
        box = gtk.VBox()
        box.show()
        self.add(box)
        self.term = vte.Terminal()
        self.term.show()
        # check for the child
        self.reaper = vte.reaper_get()
        self.reaper.connect("child-exited",self.child_exited)
        self.finished = False

        box.pack_start(self.term)
        self.progressbar = gtk.ProgressBar()
        self.progressbar.show()
        box.pack_start(self.progressbar)
        
        (read, write) = os.pipe()
        self.writefd=write
        self.status = os.fdopen(read, "r")
        fcntl.fcntl(self.status.fileno(), fcntl.F_SETFL,os.O_NONBLOCK)
        #print "read-fd: %s" % self.status.fileno()
        #print "write-fd: %s" % self.writefd
        self.read = ""

    def child_exited(self,term, pid, status):
        #print "child_exited: %s %s %s %s" % (self,term,pid,status)
        self.apt_status = posix.WEXITSTATUS(status)
        self.finished = True

    def startUpdate(self):
        #print "start"
        self.show()
        
    def updateInterface(self):
        if self.status != None:
                try:
                    self.read += os.read(self.status.fileno(),1)
                except OSError, (errno,errstr):
                    # resource temporarly unavailable is ignored
                    if errno != 11: 
                        print errstr
                if self.read.endswith("\n"):
                    s = self.read
                    #print s
                    (status, pkg, percent, status_str) = string.split(s, ":")
                    #print "percent: %s %s" % (pkg, float(percent)/100.0)
                    self.progressbar.set_fraction(float(percent)/100.0)
                    self.progressbar.set_text(string.strip(status_str))
                    self.read = ""
        while gtk.events_pending():
            gtk.main_iteration()
        
    def finishUpdate(self):
        # Blocking on readline is not healthy
        #sys.stdin.readline()
        pass
    
    def run(self, pm):
        #print "fork"
        env = ["VTE_PTY_KEEP_FD=%s"%self.writefd]
        #print env
        pid = self.term.forkpty(envv=env)
        if pid == 0:
            res = pm.DoInstall(self.writefd)
            #print res
            sys.exit(res)
        #print "After fork: %s " % pid
        while not self.finished:
            self.updateInterface()
        return self.apt_status


if __name__ == '__main__':

    app = AuthtoolGTK()
    app.main()
