# Copyright (c) 2001 LOGILAB S.A. (Paris, FRANCE).
# http://www.logilab.fr/ -- mailto:contact@logilab.fr
#
# 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.,
# 59 Temple Place - Suite 330, Boston, MA  02111-1307
# USA.

"""
HornGui is the user graphical interface to Narval
"""

__revision__ = "$Id: HornGuiController.py,v 1.58 2002/01/28 11:27:51 syt Exp $"

from gtk import *
from HornActioner import HornActioner, RPC_PROTO
from HornCodes import *
from MemoryCodes import *
from narval.lib import *
from HornView import HornView
from EngineController import EngineController
from MemoryController import MemoryController
from MemoryModel import MemoryModel
from DialogWindow import DialogWindow, unicode_to_gtk, update_gui
from HelpWindow import HelpWindow
from AboutWindow import AboutWindow
from narval import config

class HornGuiController:
    """
    Graphical controller of Horn using singleton pattern
    """

    _DICT_ = {}

    def __init__(self):
        self.__dict__ = self._DICT_

        if not self.__dict__ :
            self.port = None
            self.host = None
            self.mcontroller = MemoryController() 
            
    def start(self, host, port, home, type):
        hactioner = HornActioner()
        nothing = FALSE

        if host is not None and port is not None:
            hactioner.connect_to_narval(host,port, type)
        elif home is not None:
            hactioner.start_local()
        else:
            nothing = TRUE
        self.port = port
        self.host = host
        self.type = type
        
        view = HornView()
        # launch preferences
        from PrefController import PrefController
        from PrefModel import PrefModel
        PrefController()
        pm = PrefModel()
        # start model and attach view
        pm.attach_observer(view)
        view.show_all()
        if nothing:
            HornView().view_to_message(unicode_to_gtk(
                _('Please connect to Narval or load a local "Narval Home".')))
        
    def callbacks(self, *args):
        if EXIT in args:
            MemoryModel().empty_memory()
            HornActioner().disconnect_from_narval()
            HornView().destroy()
            mainquit()
            print unicode_to_gtk(_("*** goodbye !!! ***"))
            import sys
            sys.exit(0)

        # File menu
        elif INSTALL_PKG in args:
            self._install_dlg()
        elif UNINSTALL_PKG in args:
            self._uninstall_dlg()
            
        # Options menu
        elif PREFERENCES in args:
            from PrefView import PrefView
            PrefView().show()
        elif HORN_MONITOR_PLAN in args:
            self.mcontroller.monitor_plans = args[1].active
        elif HORN_NARVAL_HOME in args:
            self._select_new_local_home()

        # Narval Control menu
        elif NARVAL_SUSPEND in args:
            HornActioner().debugSuspend()
        elif NARVAL_STEP in args:
            HornActioner().debugStepOneStep()
        elif NARVAL_CONTINUE in args:
            HornActioner().debugContinue()
        elif NARVAL_CONNECT in args:
            self._connect_dlg()
        elif NARVAL_DISCONNECT in args:
            HornActioner().disconnect_from_narval()
            update_gui()
            HornView().build_notebook()
            HornView().view_to_message(unicode_to_gtk(
                _('Please connect to Narval or load a local "Narval Home".')))
        elif NARVAL_SVRECIPES in args:
            HornActioner().saveRecipes()
        elif NARVAL_SVTRANSF in args:
            HornActioner().saveTransforms()

        # Narval Interact menu
        elif NARVAL_START_PLAN in args:
            d = DialogWindow(unicode_to_gtk(_('Start plan...')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the plan to start :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('recipe'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._start_plan,d,combo)
            d.show()
        elif NARVAL_ADD_ELEMENT in args:
            dlg = DialogWindow(unicode_to_gtk(_('Add element')))
            dlg.append_dialog(GtkLabel(unicode_to_gtk(_('Enter the XML element to add in memory :'))))
            txt = GtkText()
            txt.set_editable(TRUE)
            from narval.gui import ENCODING
            txt.insert_defaults('<?xml version="1.0" encoding="%s" ?>\n'%ENCODING)
            dlg.append_dialog(txt)
            dlg.ok_button.show()
            dlg.ok_button.set_flags(CAN_DEFAULT|HAS_DEFAULT)
            dlg.cancel_button.show()
            dlg.cancel_button.connect_object('clicked',lambda w:w.destroy(),dlg)
            dlg.ok_button.connect('clicked',self._add_element,dlg,txt)
            dlg.show()
        # Action menu
        elif ACT_SAVE_COOKBOOK in args:
            d = DialogWindow(unicode_to_gtk(_('Save cookbook...')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the cookbook to save :'))))
            combo = GtkCombo()
            combo.entry.set_editable(FALSE)
            books = map(utf8_to_latin1, MemoryModel().get_cookbooks())
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._save_cookbook,d,combo)
            d.show()
        elif ACT_SAVEALL_COOKBOOK in args:
            self.mcontroller.save_all_cookbook()
        elif ACT_ADD_RECIPE in args:
            d = DialogWindow(unicode_to_gtk(_('New recipe')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the cookbook which will own the recipe :'))))
            combo = GtkCombo()
            books = map(utf8_to_latin1, MemoryModel().get_cookbooks())
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Enter the name of the recipe'))))
            entry = GtkEntry()
            d.append_dialog(entry)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._create_new_recipe,d,entry,combo)
            d.show()
        elif ACT_EDIT_RECIPE in args:
            d = DialogWindow(unicode_to_gtk(_('Edit recipe')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the recipe to edit :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('recipe'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_recipe,d,combo,"edit")
            d.show()
        elif ACT_SAVE_RECIPE in args:
            d = DialogWindow(unicode_to_gtk(_('Save recipe')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the recipe to save :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('recipe'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_recipe,d,combo,"save")
            d.show()      
        elif ACT_REM_RECIPE in args:
            d = DialogWindow(unicode_to_gtk(_('Remove recipe')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the recipe to remove :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('recipe'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_recipe,d,combo,"remove")
            d.show()
        elif ACT_EDIT_ACTION in args:
            d = DialogWindow(unicode_to_gtk(_('Edit action...')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the action to edit :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('action'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_action,d,combo,'edit')
            d.show()
        elif ACT_REM_ACTION in args:
            d = DialogWindow(unicode_to_gtk(_('Remove action...')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the action to remove :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('action'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_action,d,combo,'remove')
            d.show()
        elif ACT_EDIT_TRANSF in args:
            d = DialogWindow(unicode_to_gtk(_('Edit transform...')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the transformation to edit :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('transform'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_transformation,d,combo,'edit')
            d.show()
        elif ACT_REM_TRANSF in args:
            d = DialogWindow(unicode_to_gtk(_('Remove transformation...')))
            d.append_dialog(GtkLabel(unicode_to_gtk(_('Select the transformation to remove :'))))
            combo = GtkCombo()
            books = map(lambda e_l:utf8_to_latin1('%s.%s'%(e_l[2], e_l[3])),
                        MemoryModel().get_elements_list('transform'))
            combo.set_popdown_strings(books)
            d.append_dialog(combo)
            d.ok_button.show()
            d.cancel_button.show()
            d.cancel_button.connect_object('clicked',lambda w:w.destroy(),d)
            d.ok_button.connect_object('clicked',self._cb_transformation,d,combo,'remove')
            d.show()
        elif ACT_CLEAN_EVTQ in args:
            EngineController().clean_event()
        elif ACT_CLEAN_SCHQ in args:
            EngineController().clean_scheduler()
        elif ACT_CLEAN_MESS in args:
            EngineController().clean_messages()

        # Help menu
        elif HELP in args:
            win = HelpWindow()
            win.show()
        elif ABOUT in args:
            win = AboutWindow()
            win.show()

    ## PRIVATE ################################################################

    # unsinstall dialog window
    
    def _uninstall_dlg(self):
        fs = GtkFileSelection(unicode_to_gtk(_("Select the manifest file")))
        fs.set_position(WIN_POS_CENTER)
        from narval.bibal import config
        fs.set_filename(config.get_home()+"/")
        fs.complete("*.manifest")
        fs.connect_object("destroy",lambda x:x.destroy(),fs)
        fs.ok_button.connect_object("clicked",self._uninstall,fs)
        fs.cancel_button.connect_object("clicked",lambda x:x.destroy(),fs)
        fs.show()
         
    def _uninstall(self,fs):
        from NpmManager import NpmManager
        manif = fs.get_filename()
        NpmManager().uninstall(manif)
        fs.destroy()
        dlg = DialogWindow(unicode_to_gtk(_("Question ?")))
        dlg.append_dialog(GtkLabel(unicode_to_gtk(_("Reload local data ?"))))
        dlg.ok_button.show()
        dlg.ok_button.connect_object('clicked',self._ok_to_reload,dlg)
        dlg.cancel_button.show()
        dlg.cancel_button.connect_object('clicked',lambda x:x.destroy(),dlg)
        dlg.show()

    def _ok_to_reload(self,*args):
        args[0].destroy()
        HornView().view_to_message(unicode_to_gtk(_("Horn is loading local data, please wait a few seconds")))
        HornView().arrow_to_watch()
        HornActioner().start_local()
        HornView().build_notebook()

    # install dialog window
   
    def _install_dlg(self):
        fs = GtkFileSelection(unicode_to_gtk(_("Select package to install")))
        fs.set_position(WIN_POS_CENTER)
        fs.connect_object("destroy",lambda x:x.destroy(),fs)
        fs.ok_button.connect_object("clicked",self._install,fs)
        fs.cancel_button.connect_object("clicked",lambda x:x.destroy(),fs)
        fs.show()

    def _install(self,fs):
        pkg_name = fs.get_filename()
        from NpmManager import NpmManager
        NpmManager().install(pkg_name)
        fs.destroy()
        dlg = DialogWindow(unicode_to_gtk(_("Question ?")))
        dlg.append_dialog(GtkLabel(unicode_to_gtk(_("Reload local data ?"))))
        dlg.ok_button.show()
        dlg.ok_button.connect_object('clicked',self._ok_to_reload,dlg)
        dlg.cancel_button.show()
        dlg.cancel_button.connect('clicked',lambda x:x.destroy(),dlg)
        dlg.show()

    # connection dialog window

    def _connect_dlg(self):
        from DialogWindow import DialogWindow
        connect_dlg = DialogWindow(unicode_to_gtk(_("Connection to Narval")))
        hbox1 = GtkHBox()
        hostentry = GtkEntry()
        import socket
        hostentry.set_text(self.host or socket.gethostname())
        label = GtkLabel(unicode_to_gtk(_("Host : ")))
        hbox1.pack_start(label,expand=FALSE)
        hbox1.pack_start(hostentry)
        portentry = GtkEntry()
        self.port = self.port or 8787
        portentry.set_text(str(self.port)) 
        portentry.set_usize(50,0)
        label = GtkLabel(unicode_to_gtk(_(" Port : ")))
        hbox1.pack_start(label,expand=FALSE)
        hbox1.pack_start(portentry)
        # choose between pyro and xmlrpc
        typeentry = GtkCombo()
        self._rpc_protos = ['pyro', 'xmlrpc']
        typeentry.entry.set_editable(FALSE)
        typeentry.set_popdown_strings(self._rpc_protos)
        typeentry.entry.set_text(self._rpc_protos[0])
        typeentry.entry.set_usize(50,0)
        typeentry.disable_activate()

        label = GtkLabel(unicode_to_gtk(_(" Type : ")))
        hbox1.pack_start(label,expand=FALSE)
        hbox1.pack_start(typeentry)

        
        connect_dlg.append_dialog(hbox1)
        
        connect_dlg.ok_button.set_flags(CAN_DEFAULT|HAS_DEFAULT)
        connect_dlg.ok_button.connect("clicked",self._ok_clicked,hostentry,portentry,
        typeentry.entry)
        connect_dlg.ok_button.show()

        connect_dlg.cancel_button.connect_object("clicked",lambda x:x.destroy(),connect_dlg)
        connect_dlg.cancel_button.show()
        
        connect_dlg.show()


    def _ok_clicked(self,*args):
        self.host = args[1].get_text()
        self.port = args[2].get_text()
        self.type = args[3].get_text()
        args[0].get_toplevel().destroy()
        HornView().view_to_message(unicode_to_gtk(_("Horn is loading memory, please wait a few seconds.")))
        HornView().arrow_to_watch()
        if HornActioner().connect_to_narval(self.host,int(self.port),self.type):
            HornView().build_notebook()
        else:
            HornView().view_to_message(unicode_to_gtk(_('Please connect to Narval or load a local "Narval Home".')))
            HornView().watch_to_arrow()
        
    # Change NARVAL_HOME

    def _select_new_local_home(self):
        from DirectorySelector import DirectorySelector
        dlg = DirectorySelector(config.get_home())
        dlg.set_title(unicode_to_gtk(_('Choose the new Narval Home')))
        dlg.ok_button.connect_object('clicked',self._local_home_changed,
                                     dlg,dlg.selectionEntry)
        dlg.show_all()
 
    def _handle_wrong_home(self,home_selector,old_home):
        dlg = DialogWindow(unicode_to_gtk(_('Advice needed')))
        dlg.append_dialog(GtkLabel(unicode_to_gtk(_('The directory you entered is either non existent or does not contain required subdirectories.'))))
        but1 = GtkRadioButton(None,unicode_to_gtk(_('Create the missing directories (empty)')))
        but2 = GtkRadioButton(but1,unicode_to_gtk(_('Use data from %s to create the missing directories'))\
                              % config.get_share())
        but3 = GtkRadioButton(but1,unicode_to_gtk(_('Let me choose again')))
        dlg.append_dialog(but2)
        dlg.append_dialog(but1)
        dlg.append_dialog(but3)
        dlg.ok_button.connect('clicked',self._ok_home_clicked,but1,but2,but3,home_selector,old_home)
        dlg.ok_button.show()
        dlg.cancel_button.connect("clicked",mainquit)
        dlg.cancel_button.show()

        dlg.show()

    def _ok_home_clicked(self,*args):
        #print args
        if args[1].get_active() == 1:
            config.create_home_subdirs()
            self._local_home_changed(args[4])
        elif args[2].get_active()==1:
            from narval.bibal import check_for_home
            check_for_home()
            self._local_home_changed(args[4])
        else:
            self._select_new_local_home()
        args[0].get_toplevel().destroy()
        self.start()

    def _local_home_changed(self,selector,entry=None):
        if entry:
            path = entry.get_text()
            if not config.set_home(path):
                self._handle_wrong_home(selector,old_path)
                return
        if selector:
            selector.destroy()
        HornView().view_to_message(unicode_to_gtk(_("Horn is loading local data, please wait a few seconds")))
        HornView().arrow_to_watch()
        try:
            HornActioner().start_local()
            HornView().build_notebook()
        except:
            print _("Cannot load data from this NARVAL_HOME...")
            HornView().view_to_message(unicode_to_gtk(_('Please connect to Narval or load a local "Narval Home".')))
            HornView().watch_to_arrow()

    # cookbooks functions
    def _save_cookbook(self,*args):
        name = args[1].entry.get_text()
        args[0].destroy()
        self.mcontroller.save_cookbook(name)

    # recipe functions
    def _cb_recipe(self,*args):
        import string
        cookbook,recipe = string.split(args[1].entry.get_text(), '.')
        args[0].destroy()
        try:
            eid = MemoryModel().get_eid('recipe', cookbook, recipe)
            if args[-1]=='edit':
                self.mcontroller.edit_recipe(None, eid)
            elif args[-1]=='save':
                self.mcontroller.save_recipe(None, MemoryModel().select_by_id(eid))
            elif args[-1]=='remove':
                self.mcontroller.remove_recipe(None, eid)
        except Exception ,e:
            print e
            print unicode_to_gtk(_('cannot find recipe'))

    
    def _create_new_recipe(self,*args):
        self.mcontroller.add_recipe_to_cookbook(args[1].get_text(),
                                                  args[2].entry.get_text())
        args[0].destroy()

    # action functions

    def _cb_action(self,*args):
        name = args[1].entry.get_text()
        args[0].destroy()
        try:
            import string
            group, name = string.split(name,'.')
            node = MemoryModel().get_eid('action', group, name)
            if args[-1]=='edit':
                self.mcontroller.edit_element(None, node)
            elif args[-1]=='remove':
                self.mcontroller.remove_element(None, node)
        except SelectException:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('%s not found')%name)

    # transformation functions
        
    def _cb_transformation(self,*args):
        name = args[1].entry.get_text()
        args[0].destroy()
        try:
            import string
            group, name = string.split(name,'.')
            node = MemoryModel().get_eid('transform', group, name)
            if args[-1]=='edit':
                self.mcontroller.edit_element(None, node)
            elif args[-1]=='remove':
                self.mcontroller.remove_element(None, node)
        except SelectException:
            print unicode_to_gtk(_('%s not found')%name)


    # narval interaction functions
    def _add_element(self,*args):
        txt = args[2].get_chars(0,args[2].get_length())
        args[1].destroy()

        HornActioner().addElement(txt)

    def _start_plan(self,*args):
        name = args[1].entry.get_text()
        args[0].destroy()

        HornActioner().startPlan(name)
