# 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: HornActioner.py,v 1.38 2002/02/27 17:45:58 alf Exp $"

from NpmManager import NpmManager
from HornModel import HornModel
from MemoryModel import MemoryModel
from MemoryController import MemoryController
from EngineController import EngineController
from narval.communication import RPCFactory 
from narval.Engine import InteractionInterface
from narval.Engine import DebugInterface
from narval.lib import FALSE, TRUE, select_many
from narval.gui import unicode_to_gtk

class HornRPCHandler:
    HornEventDispatcher = None
    NAME = 'HornEventDispatcher'
    EXPORTED_METHODS = {
        # memory listener
        'addElement':     'HornEventDispatcher',
        'removeElement':  'HornEventDispatcher',
        'replaceElement': 'HornEventDispatcher',
        'getElementList': 'HornEventDispatcher',
        'getElementsList':'HornEventDispatcher',
        # plan listener
        'fireTransition':         'HornEventDispatcher',
        'transitionStatusChanged':'HornEventDispatcher',
        'stepStatusChanged':      'HornEventDispatcher',
        'planStatusChanged':      'HornEventDispatcher',
        'instanciateRecipe':      'HornEventDispatcher',
        'planRemoveElement':      'HornEventDispatcher',
        'planAddElement':         'HornEventDispatcher',
        # engine listener
        'postQueueEvent':    'HornEventDispatcher',
        'popQueueEvent':     'HornEventDispatcher',
        'postSchedulerEvent':'HornEventDispatcher',
        'popSchedulerEvent': 'HornEventDispatcher'
        }    

RPC_PROTO = 'pyro'

class CommonActioner:
    """ common actioner to  cli and gui """

    _DICT_ = {}

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

        if not self.__dict__ :
            self._npm = NpmManager()
            self.hmodel = HornModel()
            # time_id
            self.time_id = None
            from HornEventDispatcher import HornEventDispatcher
            self.evt_dispatcher = HornEventDispatcher()
            HornRPCHandler.HornEventDispatcher = self.evt_dispatcher
            self.mmodel = MemoryModel()
    
    def start_local(self):
        self.disconnect_from_narval()
        self.mmodel.loadLocalData()

    ## connexions functions ###################################################
        
    def connect_to_narval(self,host,port,rpc_proto=RPC_PROTO):
        if self.hmodel.narval is not None:
            print "Already connected to Narval"
            return FALSE
        else:
            rpc_port = self._setup_local_rpc_server(9513, rpc_proto)
            try:
                self.hmodel.set_narval(RPCFactory.create_proxy(
                    RPCFactory.encode_uri(rpc_proto, host, port, 'EngineEventDispatcher')))
                from socket import gethostname
                self.horn_uri = RPCFactory.encode_uri(rpc_proto, gethostname(),
                                                      rpc_port,
                                                      'HornEventDispatcher')
                self.hmodel.narval.addRemoteMemoryListener(self.horn_uri)
                self.hmodel.narval.addRemotePlanListener(self.horn_uri)
                self.hmodel.narval.addRemoteEngineListener(self.horn_uri)
                # create a new MemoryController link to narval memory
                mem_ctrl = MemoryController()
                self.evt_dispatcher.add_listener('memory', mem_ctrl)
                self.evt_dispatcher.add_listener('plan', mem_ctrl)
                # create an EngineController link to narval
                eng_ctrl = EngineController()
                self.evt_dispatcher.add_listener('memory', eng_ctrl)
                self.evt_dispatcher.add_listener('plan', eng_ctrl)
                self.evt_dispatcher.add_listener('engine', eng_ctrl)
                # load narval memory
                l = self.hmodel.narval.getElementsList()
                self.mmodel.loadNarvalMemory(l)
                return TRUE
                
            except: # socket.error :
                import traceback
                traceback.print_exc()
                self.rpc_server.stop()
                # notify that narval is not connected
                self.hmodel.set_narval(None)
                return FALSE

    def disconnect_from_narval(self):
        print 'DISCONNECT'
        EngineController().close_logfile()
        if self.hmodel.narval is not None:
            try:
                self.hmodel.narval.removeRemoteMemoryListener(self.horn_uri)
                self.hmodel.narval.removeRemotePlanListener(self.horn_uri)
                self.hmodel.narval.removeRemoteEngineListener(self.horn_uri)
            except:
                import traceback
                traceback.print_exc()
            # break link between narval memory and memory controller
            mem_ctrl = MemoryController()
            self.evt_dispatcher.remove_listener('memory', mem_ctrl)
            self.evt_dispatcher.remove_listener('plan', mem_ctrl)
            #break link between narval memory and engine controller
            eng_ctrl = EngineController()
            self.evt_dispatcher.remove_listener('memory', eng_ctrl)
            self.evt_dispatcher.remove_listener('plan', eng_ctrl)
            self.evt_dispatcher.remove_listener('engine', eng_ctrl)
            # empty memory, stop rpc server
            self.mmodel.empty_memory()
            self.rpc_server.stop()
            self.hmodel.set_narval(None)
            #self.mmodel.notify_observers()

    ## install / uninstall ####################################################
    def install(self,file):
        self._npm.install(file)
    def uninstall(self,file):
        self._npm.uninstall(file)
    def list_packages(self) :
        return self._npm.list_packages()
        
    ## implement DebugInterface ###############################################
    def debugSuspend(self):
        self.hmodel.narval.debugSuspend()
    def debugContinue(self):
        self.hmodel.narval.debugContinue()
    def debugStepOneStep(self):
        self.hmodel.narval.debugStepOneStep()

    ## Interface that defines Engine methods used by (Graph|CmdLine)UI ########
    def getMemory(self):
        return self.mmodel.get_memory()        
    def getElement(self,id):
        return self.hmodel.narval.getElement(id)
    def getElementsList(self):
        return self.hmodel.narval.getElementsList()
    def getElementList(self,id):
        return self.hmodel.narval.getElementList(id)
    def selectElements(self,xpath):
        print 1
        l = select_many(self.mmodel.get_memory(), xpath)
        print 2
        from cStringIO import StringIO
        s = StringIO()
        print 3
        for elt in l:
            s.write(elt.as_xml())
        print 4
        return s.getvalue()
    def addElement(self, elementXml):
        self.hmodel.narval.addElement(elementXml)
    def removeElement(self,elementId):
        self.hmodel.narval.removeElement(elementId)
    def replaceElement(self, elementId, elementXml):
        self.hmodel.narval.replaceElement(elementId, elementXml)
    def startPlan(self,recipeName):
        self.hmodel.narval.startPlan(recipeName)
    def saveRecipes(self):
        self.hmodel.narval.saveRecipes()
    def saveTransforms(self):
        self.hmodel.narval.saveTransforms()
    def shutdown(self):
        self.hmodel.narval.shutdown()

    ## rpc callbacks ##########################################################
##     def process_queue(self):
##         import Queue
##         try:
##             event = HornModel().queue.get_nowait()
##             objects = self.listeners[event[0]]
##             for object in objects:
##                 if object: 
##                     apply(getattr(object,event[1]),event[2])
##         except Queue.Empty :
##             pass
##         return TRUE

    ## Private ################################################################
    def _setup_local_rpc_server(self, port, rpc_proto):
        connected = 0
        while not connected :
            try:
                self.rpc_server = RPCFactory.create_server(port, HornRPCHandler,
                                                           rpc_proto)
                self.rpc_server.start()
            except Exception, e:
                #import traceback
                #traceback.print_exc()
                from random import randrange
                port = randrange(1024,645535)
                print 'retry with port %d'%port
		raise
            else:
                connected = 1
        return port
    

class HornActioner(CommonActioner):
    """
    Lower class linking HornModel, RecipeEditorController and NpmManager.
    This class is written for gui using.
    """

    def __init__(self):
        self.__dict__ = CommonActioner._DICT_
        if not self.__dict__ :
            CommonActioner.__init__(self)
        
    def connect_to_narval(self, host, port, rpc_proto=RPC_PROTO):
        if CommonActioner.connect_to_narval(self, host, port, rpc_proto):
            # start process queue
            from gtk import timeout_add
            from distutils import util
            if util.get_platform()[:3] == 'win':
                global WSLEEPTIME
                WSLEEPTIME = 0.001
                self.time_id = timeout_add(400, self.sleep_and_process_queue)
            else:
                self.time_id = timeout_add(400, self.evt_dispatcher.process_queue)
            return TRUE

    def disconnect_from_narval(self):
        if self.hmodel.narval is not None:
            # stop process queue
            from gtk import timeout_remove
            timeout_remove(self.time_id)
        CommonActioner.disconnect_from_narval(self)

    def sleep_and_process_queue(self):
        import time
        time.sleep(WSLEEPTIME)
        return self.evt_dispatcher.process_queue()


class HornActionerCli(CommonActioner):
    """
    Horn Actioner Cli : class used by cli interface
    """

    def __init__(self):
        self.__dict__ = CommonActioner._DICT_
        if not self.__dict__: 
            CommonActioner.__init__(self)
            # counter index for cli recipe edition
            self._count = 0

    # CLI interaction #############################################

    def add_element_to_recipe(self,name,element,type,id=None):
        import string
        name = string.split(name,".")
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            if type == 'transition':
                trans = node.ownerDocument.createElementNS(AL_NS,'al:transition')
                if not id:
                    self._count = self._count + 1
                    id = "trans_"+str(self._count)
                trans.setAttributeNS(NO_NS,'id',id)
                trans.setAttributeNS(NO_NS,'id',id)
                node.appendChild(trans)
            else:
                step = node.ownerDocument.createElementNS(AL_NS,'al:step')
                step.setAttributeNS(NO_NS,'type',type)
                step.setAttributeNS(NO_NS,'target',element)
                if not id:
                    self._count = self._count + 1
                    id = "step_"+str(self._count)
                step.setAttributeNS(NO_NS,'id',id)
                node.appendChild(step)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('add element recipe is impossible'))

    def add_argument_to_step(self,name,id_step,argument):
        import string
        name = string.split(name,'.')
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            step = select_one(node,'al:step[@id="%s"]'%(id_step))
            arg = self.mmodel.fact.element_from_string(argument)
            step.appendChild(arg)
        except:
            print unicode_to_gtk(_('add argument is impossbile'))

    def add_attribute_to_recipe(self,recipe,tag,attr):
        import string
        name = string.split(recipe,'.')
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            node.setAttributeNS(NO_NS,tag,attr)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('add an attribute to recipe is impossible'))

    def add_condition_to_transition(self,recipe,id_trans,cond):
        import string
        name = string.split(recipe,".")
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            node = select_one(node, 'al:transition[@id="%s"]' % (id_trans))
            condition = self.mmodel.fact.element_from_string(cond)
            node.appendChild(condition)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('add condition is impossible'))

    def connect_elements(self,recipe,elt1,elt2):
        import string
        name = string.split(recipe,'.')
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            elt1_node = select_one(node,'*[@id="%s"]'%(elt1))
            elt2_node = select_one(node,'*[@id="%s"]'%(elt2))
            if elt1_node.localName == 'step' and elt2_node.localName == 'transition':
                in_node = elt2_node.ownerDocument.createElementNS(NO_NS,'in')
                in_node.setAttributeNS(NO_NS,'idref',elt1_node.getAttributeNS(NO_NS,'id'))
                elt2_node.appendChild(in_node)
            elif elt1_node.localName == 'transition' and elt2_node.localName == 'step':
                out_node = elt1_node.ownerDocument.createElementNS(NO_NS,'out')
                out_node.setAttributeNS(NO_NS,'idref',elt2_node.getAttributeNS(NO_NS,'id'))
                elt1_node.appendChild(out_node)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('connect elements is impossible'))

    def add_new_recipe(self, recipe, cookbook):
        MemoryController().add_recipe_to_cookbook(recipe, cookbook)

    def remove_element_to_recipe(self,recipe,id_elt):
        import string
        name = string.split(recipe,'.')
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            elt_node = select_one(node, '*[@id="%s"]' % (id_elt))
            # find connections before and destroy them
            if elt_node.localName == 'step':
                transitions = select_many(elt_node.parentNode,'al:transition')
                for transition in transitions:
                    links = select_many(transition,'*[idref="%s"]'%(id_elt))
                    for link in links:
                        transition.removeChild(link)
            elt_node.parentNode.removeChild(elt_node)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('remove element is impossible'))

    def remove_connection_to_recipe(self,recipe,id_elt1,id_elt2):
        import string
        name = string.split(recipe,'.')
        try:
            node = self.mmodel.select('recipe', name[0], name[1])
            elt_node = select_one(node, 'al:transition[id="%s"]'%(id_elt1))
            link = select_one(elt_node,'*[@idref="%s"]'%(id_elt2))
            elt_node.removeChild(link)
        except:
            import traceback
            traceback.print_exc()
            print unicode_to_gtk(_('remove connection is impossible'))
