# Copyright (c) 2000-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: MemoryDocument.py,v 1.18 2001/12/21 15:04:06 syt Exp $"

from narval.core import ChangeListeners
from narval.core.tags import tag_element, clear_tags
from narval.bibal import mk_time_stamp
from narval.lib import AL_NS, get_encoding
from string import split

class MemoryDocument(ChangeListeners.TransitionChangeListenerInterface,
                     ChangeListeners.StepChangeListenerInterface,
                     ChangeListeners.PlanChangeListenerInterface) :
    """ MemoryDocument, narval main memory"""

    def __init__(self,al=None) :
        self.Al = al
        self.eid_count = 1
        self.eid_ref_count = {}
        self.actions, self.transforms, self.recipes = {}, {}, {}
        self.plans = []
        self.elements = {}
        from ALAbstraction import ALEFImplementation
        self.fact = ALEFImplementation

    # Memory methods ###########################################################
    
    def add_elements(self,elements) :
        map(self.add_element,elements)

    def add_element(self,element) :
        """if the element has not already an eid (== not yet in memory)
        assign unique id to the element and append it to memory.
        """
        if hasattr(element, 'eid') and element.eid:
            eid = element.eid
            self.eid_ref_count[eid] += 1
        else :
            # assign new eid to element
            element.eid = eid = str(self.eid_count)
            if element.__dict__.has_key('eid') and element._dom:
                element._dom.setAttributeNS(AL_NS, 'al:eid', eid)
            element.timestamp = mk_time_stamp()
            self.eid_count += 1
            self.eid_ref_count[eid] = 1
            # do some caching processing
            self.elements[eid]=element
            type = element.TYPE
            if type == 'recipe':
                self.recipes[element.get_identity()] = eid
            elif type == 'action':
                self.actions[element.get_identity()] = eid
            elif type == 'transform':
                self.transforms[element.get_identity()] = eid
            elif type == 'plan':
                #self.plans.[element.get_identity()] = eid
                self.plans.append(element)
            # if debug mode, and element is an error, print it to stdout
            elif self.Al.debug and element.name == 'error' :
                print element.as_xml(get_encoding())
                
            element.memory = self
            # element added to memory: fire event.
            self.Al.memory_change('add',element)
            # this new element may trigger a transition. propagate.
            for plan in self.plans: #elements.values():
                plan.element_change(element)
        return eid, element

    def add_message_as_string(self,msg_str) :
        element = self.fact.element_from_string(msg_str)
        if element.name == 'message' :
            element.as_dom().setAttributeNS(AL_NS,'type','incoming')
            self.add_element(element)
        else :
            print "Warning: received a false message. Dropped it."

        
    def add_element_as_string(self,element_str) :
        self.add_element(self.fact.element_from_string(element_str))
        
    def remove_element(self,element) :
        if hasattr(element, 'persist') and element.persist == 'yes' \
               and element.outdated != 'yes' :
            return
        clear_tags(element)
        self.Al.memory_change('remove',element)
        del self.elements[element.eid]
        type = element.TYPE
        if type == 'recipe':
            del self.recipes[element.get_identity()]
        elif type == 'action':
            del self.actions[element.get_identity()]
        elif type == 'transform':
            del self.transforms[element.get_identity()]
        elif type == 'plan':
            self.plans.remove(element)
            element.remove_elements(element.p_elements)
        del element    

    def remove_element_by_id(self,eid) :
        try:
            self.remove_element(self.elements[eid])
        except KeyError :
            print 'Warning: No element with eid',eid,'in memory'

    def replace_element(self, old_element, new_element):
        new_element.eid = old_element.eid
        new_element.memory = self
        self.elements[old_element.eid] = new_element
        oldType = old_element.TYPE
        newType = new_element.TYPE
        if oldType != newType :
            if oldType == 'recipe':
                del self.recipes[old_element.get_identity()]
            elif oldType == 'action':
                del self.actions[old_element.get_identity()]
            elif oldType == 'transform':
                del self.transforms[old_element.get_identity()]
            elif oldType == 'plan':
                self.plans.remove(old_element)
                #del self.plans[old_element.get_identity()]
            if newType == 'recipe':
                self.recipes[new_element.get_identity()] = new_element
            elif newType == 'action':
                self.actions[new_element.get_identity()] = new_element
            elif newType == 'transform':
                self.transforms[new_element.get_identity()] = new_element
            elif newType == 'plan':
                self.plans.append(new_element)
                #self.plans.[new_element.get_identity()] = new_element
            
        self.Al.memory_change('replace',new_element,old_element)
        del old_element

    def replace_element_as_string(self, eid, element_str) :
        try:
            element = self.elements[eid]
            new_element = self.fact.element_from_string(element_str)
            self.replace_element(element, new_element)
        except KeyError :
            print 'Warning: No element with eid',eid,'in memory'

    def delete_ref_to_element(self,element) :
        eid = element.eid
        self.eid_ref_count[eid] = self.eid_ref_count[eid] - 1
        if self.eid_ref_count[eid] < 1 :
            self.remove_element(element)
                    
    def are_active_plans(self) :
        for plan in self.plans:
            if plan.state not in ('done','failed'):
                return 1
        return 0

    def mk_error(self,type,msg) :
        from xml.sax.saxutils import escape  
        return self.fact.element_from_string('<al:error xmlns:al="%s" type="%s">%s</al:error>'%(
            AL_NS, type, escape(msg)))

    def get_input(self, input):
        cands = input.match_elements(self.elements.values())
        for cand in cands:
            eid = cand.eid
            self.eid_ref_count[eid] = self.eid_ref_count[eid] + 1
        return cands
    
    def get_elements(self) :
        return self.elements.values()

    def get_recipe(self, recipeName) :
        try:
            group, name = split(recipeName, '.')
            return self.elements.get(self.recipes.get((group,name)))
        except ValueError :
            return None

    def get_recipes(self): # new
        return [self.elements[eid] for eid in self.recipes.values()]

    def get_transform(self, name) :
        try:
            group, name = split(name, '.')
            return self.elements.get(self.transforms.get((group,name)))
        except ValueError :
            return None

    def get_transforms(self): # new
        return [self.elements[eid] for eid in self.transforms.values()]

    def get_action(self, name) :
        try:
            group, name = split(name, '.')
            return self.elements.get(self.actions.get((group,name)))
        except ValueError :
            return None

    def get_actions(self): # new
        return [self.elements[eid] for eid in self.actions.values()]

    def get_element(self, eid):  # new
        try:
            return self.elements[eid]
        except:
            return None
        
    def as_xml(self, encoding='UTF-8'):
        from cStringIO import StringIO
        f = StringIO()
        f.write('<?xml version="1.0" encoding="%s"?>\n<memory>\n'%encoding)
        for e in self.elements.values():
            f.write(e.as_xml(encoding))
        f.write('</memory>')
        data = f.getvalue()
        f.close()
        return data

    # Proxy for Engine #########################################################

    def step_change(self,*args,**kargs) :
        return self.Al.step_change(*args, **kargs)

    def plan_change(self,*args,**kargs) :
        return self.Al.plan_change(*args, **kargs)

    def transition_change(self,*args,**kargs) :
        return self.Al.transition_change(*args, **kargs)

    def transition_wait_time(self,transition,date):
        self.Al.schedule_event(('time-condition',transition),when=date,absoluteDate=1)

    def start_plan(self,recipe_name,parent_plan,parent_step,elements) :
        self.Al.post_event( ('startPlan', recipe_name,
                             (parent_plan, parent_step), []) )

    def check_date(self,*args,**kargs) :
        return self.Al.check_date(*args, **kargs)

    def create_thread(self,*args,**kargs) :
        self.Al.create_thread(*args, **kargs)
        


