# 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.
__revision__ = "$Id: RecipeController.py,v 1.34 2002/01/04 14:13:05 syt Exp $"

from logilab.xmltools.XmlEditor import *
from narval.lib import AL_NS, NO_NS, select_many, select_one
from os.path import normpath,join
from RecipeCodes import *
from MemoryModel import MemoryModel
from DialogWindow import DialogWindow,unicode_to_gtk
from RecipeLayout import *
from HornAbstraction import HORNElementsFactory
#DTD =  parse_dtd_file(normpath(join(config.get_home(),'dtd/Narval.dtd')))
import re
STEP_REGEX = re.compile('^((step)|(transition))_(\d)+$')
    

class RecipeController:
    """
    Controller of recipe implementing Undo/Redo pattern
    """
    def __init__(self, model, window):
        self._model = model
        self._dialog_window = []
        self.has_changed = FALSE
        self.window = window # link to recipe window
        self._count = 0
        self.fact = HORNElementsFactory()

    def set_document(self, doc):
        self.fact.doc = doc

    # callback function #######################################################
    def callbacks(self,*args):
        if EXIT in args:
            for i in range(len(self._dialog_window)):
                self._dialog_window[i].destroy()
            if self.has_changed:
                dlg = DialogWindow(unicode_to_gtk(_("Question ?")))
                self._dialog_window.append(dlg)
                dlg.append_dialog(GtkLabel(unicode_to_gtk(_("Recipe has changed.\n\nDo you want to save it before closing window ?"))))
                dlg.ok_button.show()
                dlg.ok_button.children()[0].set_text(unicode_to_gtk(_("Yes")))
                dlg.cancel_button.show()
                dlg.cancel_button.children()[0].set_text(unicode_to_gtk(_("No")))
                dlg.ok_button.connect("clicked",self._yes_clicked)
                dlg.cancel_button.connect_object("clicked",self._no_clicked,dlg)
                dlg.show()
            else:
                self.window.destroy_all()
        elif SV_RECIPE in args:
            self.window.update_model()
            self._save_recipe()
        elif ACT_ADD_STEP in args:
            self.add_step()
            self.has_changed = TRUE
        elif ACT_REM_STEP in args:
            dlg = DialogWindow(unicode_to_gtk(_('Remove step')))
            lbl = GtkLabel(unicode_to_gtk(_("Select the step to remove :")))
            lbl.show()
            dlg.append_dialog(lbl)
            list_step = map(lambda x:utf8_to_latin1(x.value),select_many(self._model._recipe,'al:step/@id'))
            combo = GtkCombo()
            combo.set_popdown_strings(list_step)
            combo.entry.set_editable(FALSE)
            combo.show()
            dlg.append_dialog(combo)
            dlg.ok_button.show()
            dlg.ok_button.connect('clicked',self._remove_step,combo,dlg)
            dlg.cancel_button.show()
            dlg.cancel_button.connect_object("clicked",lambda w:w.destroy(),dlg)
            dlg.show()
            self.has_changed = TRUE
        elif ACT_ADD_CONNECT in args:
            pass
        elif ACT_REM_CONNECT in args:
            pass
        elif ACT_ADD_TRANS in args:
            self.add_transition()
            self.has_changed = TRUE
        elif ACT_REM_TRANS in args:
            dlg = DialogWindow(unicode_to_gtk(_('Remove transition')))
            lbl = GtkLabel(unicode_to_gtk(_("Select the transition to remove :")))
            lbl.show()
            dlg.append_dialog(lbl)
            list_trans = map(lambda x:utf8_to_latin1(x.value),\
                             select_many(self._model._recipe,'al:transition/@id'))
            combo = GtkCombo()
            combo.set_popdown_strings(list_trans)
            combo.entry.set_editable(FALSE)
            combo.show()
            dlg.append_dialog(combo)
            dlg.ok_button.show()
            dlg.ok_button.connect('clicked',self._remove_transition,combo,dlg)
            dlg.cancel_button.show()
            dlg.cancel_button.connect_object("clicked",lambda w:w.destroy(),dlg)
            dlg.show()
            self.has_changed = TRUE
        elif ACT_EDIT_PROP in args:
            self.recipe_properties()
            self.has_changed = TRUE
        elif UNDO in args:
            print unicode_to_gtk(_('not yet implemented'))
        elif REDO in args:
            print unicode_to_gtk(_('not yet implemented'))
        elif LAY_DEFAULT in args:
            self._model.set_layout_pattern(RecipeLayout())
        elif LAY_ONE in args:
            self._model.set_layout_pattern(RecipeLayout1())
        elif LAY_TWO in args:
            self._model.set_layout_pattern(RecipeLayout2())
        elif LAY_THREE in args:
            self._model.set_layout_pattern(RecipeLayout3())
        elif HELP in args:
            from HelpWindow import HelpWindow
            HelpWindow().show()
        elif ABOUT in args:
            from AboutWindow import AboutWindow
            AboutWindow().show()

    # properties of an element (step or transition) ###########################
    def element_properties(self,item):
        from RecipeGraphicView import RecipeTransition, RecipeStep
        if item.__class__ == RecipeTransition:
            try:
                element = select_one(self._model._recipe,'al:transition[@id="%s"]'%(item.id))
            except:
                print unicode_to_gtk(_('transition not found'))
                element = None
        elif item.__class__ == RecipeStep:
            try:
                element = select_one(self._model._recipe,'al:step[@id="%s"]'%(item.id))
            except:
                print unicode_to_gtk(_('step not found'))
                element = None
                
        # go to tree view and expand selected node
        if element:
            self.window.expand_node_in_tree(element)

    # recipe functions ########################################################
    def recipe_properties(self):
        from PropertyWindow import PropertyWindow
        dlg = PropertyWindow(self._model)
        dlg.show()
        dlg.ok_button.connect('clicked',self._recipe_prop_ok_clicked,dlg)
        self._dialog_window.append(dlg)

    def update_recipe(self,recipe):
        self._model.update_recipe(recipe)
        
    def get_count(self, recipe):
        """
        set _count to the correct number according to existing steps/transitions
        """
        node = recipe.firstChild
        while node:
            if node.nodeType == node.ELEMENT_NODE :
                m = STEP_REGEX.match(node.getAttributeNS(NO_NS, 'id'))
                if m:
                    self._count = max(int(m.group(4)), self._count)
            node = node.nextSibling
            
    def remove_step(self,id):
        self._model.remove_step(id)

    def add_step(self,*args):
        from AddStepWindow import AddStepWindow
        dlg = AddStepWindow()
        dlg.ok_button.connect("clicked",self._add_step_ok_clicked,dlg)
        dlg.show()
        self._dialog_window.append(dlg)

    # update state of step
    def update_step(self,planId,xml_step):
        if self._model._recipe.getAttributeNS(AL_NS,'eid') == planId and xml_step:
            self._model.update_step(xml_step.getAttributeNS(NO_NS,'id'), xml_step)

    # recipe transition functions

    def add_transition(self,*args):
        self._count += 1
        id = "transition_"+str(self._count)
        self._model.add_transition(id)

    def remove_transition(self,id):
        self._model.remove_transition(id)

    def update_transition(self,planId,xml_transition):
        if self._model._recipe.getAttributeNS(AL_NS,'eid') == planId and xml_transition:
            self._model.update_transition(xml_transition.getAttributeNS(NO_NS,'id'),xml_transition)

    # recipe connections functions

    def add_connection_between_steps(self,id_one,id_two):
        self._count = self._count+1
        id = "transition_"+str(self._count)
        self._model.add_transition(id,id_one,id_two)

    def add_connection_between_step_and_transition(self,id_step,id_transition):
        self._model.add_in_link_to_transition(id_transition,id_step)

    def add_connection_between_transition_and_step(self,id_transition,id_step):
        self._model.add_out_link_to_transition(id_transition,id_step)

    def received_drag_data(self, canvas, context, x, y, data, info, time):
        if data.format == 8 and self.window.editable:
            step = self.fact.element_from_string(data.data)
            # update graphic view pointer
            self._rgview._pointer = (x,y)
            self._count += 1
            self._model.add_step("step_%d" % self._count, step)
            self.fact.release_domtree(step)
            canvas.drag_finish(context, 1, 0, time)
        else:
            # dnd failed !
            print unicode_to_gtk(_('drag an drop failed'))
            canvas.drag_finish(context, 0, 0, time)


    ## Private ################################################################
    def _remove_step(self,*args):
        id = args[-2].entry.get_text()
        args[-1].destroy()
        self.remove_step(id)

    def _remove_transition(self,*args):
        id = args[-2].entry.get_text()
        args[-1].destroy()
        self.remove_transition(id)

    def _exit(self,*args):
        args[0].destroy()

    def _edit_element(self,*args):
        if args[0].localName=='step':
            self._model.update_step(args[0].getAttributeNS(NO_NS,'id'),args[1].editor.node)
        elif args[0].localName == 'transition':
            self._model.update_transition(args[0].getAttributeNS(NO_NS,'id'),args[1].editor.node)
            
        args[1].destroy()
    
    def _save_recipe(self,*args):
        recipe = self._model._recipe
        from MemoryController import MemoryController
        MemoryController().save_recipe(None, recipe)
        self.has_changed = FALSE

    def _yes_clicked(self,*args):
        self.callbacks(SV_RECIPE)
        self.callbacks(EXIT)

    def _no_clicked(self,*args):
        self.args[-1].destroy()
        self.window.destroy_all()

    def _add_step_ok_clicked(self,*args):
        try:
            target = args[1].group_name+'.'+args[1].element_name
            self._count = self._count + 1
            id = 'step'+ str(self._count)
            while select_many(self._model._recipe,'al:step[@id="%s"]'%(id)):
                self._count = self._count + 1
                id = 'step'+str(self._count)
            from cStringIO import StringIO
            buf = StringIO('<al:step xmlns:al="%s" id="%s" target="%s" type="%s"/>'\
                           %(AL_NS, id, target.encode('UTF-8'), args[1].type_name))
            node = self.fact.element_from_stream(buf)
            buf.close()
            self._model.add_step(id, node)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('node not found, cannot add step'))
        args[1].destroy()

    def _recipe_prop_ok_clicked(self,*args):
        try:
            recipe = self._model._recipe
            recipe.setAttributeNS(NO_NS,'name',args[1].name_entry.get_text())
            if args[1].autorestart.get_active():
                recipe.setAttributeNS(NO_NS,'restart','yes')
            else:
                recipe.setAttributeNS(NO_NS,'restart','no')
            recipe.setAttributeNS(AL_NS,'al:timestamp', args[1].decay_entry.get_text())
            print '2', dir(recipe), id(recipe)
            self._model.update_recipe(recipe)
        except:
            import traceback
            traceback.print_exc()
        args[1].destroy()
