# 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.
"""
Memory Model using the borg pattern
"""
__revision__ = "$Id: MemoryModel.py,v 1.54 2002/02/27 17:45:58 alf Exp $"

import os,sys
from os.path import join
from xml.dom.ext import PrettyPrint
from HornAbstraction import as_xml
from narval.lib import AL_NS, NO_NS, select_one, SelectException
from narval.bibal import load_recipes, load_modules, load_transforms
from narval.gui import unicode_to_gtk
from narval.utils.dp import Observable

_E_TYPE = 0
_E_GROUP = 1
_E_NAME = 2
_E_DOM = 3

E_ID = 0
E_TYPE = 1
E_GROUP = 2
E_NAME = 3

class MemoryModel(Observable):
    """
    the Model of the Memory
    Memory can be local or distant narval's memory
    """

    _DICT_ = {}

    def __init__(self):
        self.__dict__ = self._DICT_
        if not self.__dict__ :
            Observable.__init__(self)
            self._current_step = None
            self._current_transition = None
            self._current_plan = None
            # listeners for the queue
            self.listeners = {}
            self.full_memory = 0
            self.eid_count = 0
            self.empty_memory()

    # initial memory load functions ###########################################

    def loadLocalData(self):
        # empty memory first...
        self.empty_memory()
        #... and fill it
        load_recipes(self.fact, self._local_add)
        load_modules(self.fact, self._local_add)
        load_transforms(self.fact, self._local_add)
        print unicode_to_gtk(_('*** local data has been loaded in memory ***'))
        self.notify_observers()

    def loadNarvalMemory(self,elts_list):
        # empty memory first...
        self.empty_memory()
        #print 'LOAD', elts_list
        if elts_list:
            print unicode_to_gtk(_("Retrieving Narval's memory..."))
            # ... and fill it
            self._fill_memory(elts_list)
        self.notify_observers()

    def get_memory(self):
        if not self.full_memory:
            from HornActioner import HornActioner
            ha = HornActioner()
            for eid in self.eid_d.keys():
                if not self.eid_d[eid][_E_DOM]:
                    print 'GET ELEMENT FROM NARVAL', eid
                    xml = ha.getElement(eid)
                    print xml
                    node = self.fact.element_from_string(xml)
                    self._memory.documentElement.appendChild(node)
                    self.eid_d[eid][_E_DOM] = node
            self.full_memory = 1
        return self._memory
    
    ## memory functions ########################################################
    def add_element(self,e_list):
        if e_list:
            eid, type, group, name = e_list
            self.eid_d[eid] = [type, group, name, None]
            #print 'MMODEL ADD', eid, type, group, name
            self._add_element(eid, type, group, name)
            self.full_memory = 0
            self.changes.append(('add', e_list))
            self.notify_observers()
            

    def remove_element(self,eid):
        try:
            type, group, name, dom = self.eid_d[eid]
        except:
            print 'element',eid,'not found'
            return
        #print 'MMODEL REM', eid, type, group, name, dom
        del self.eid_d[eid]
        if type in ('recipe', 'action', 'transform', 'plan') and \
           eid == self.type_d[type][group][name]:
            del self.type_d[type][group][name]
        else:
            self.type_d[type][name].remove(eid)
        if dom:
            dom.parentNode.removeChild(dom)
            self.fact.release_domtree(dom)
        self.changes.append(('rem', [eid, type, group, name]))
        self.notify_observers()

    ## messages received from Narval because we're a MemoryListener ############
    def step_changed(self, planId, stepId, status):
        # ignore changes if not plan in local memory
        plan = self._select_local_by_id(planId)
        if plan:
            try:
                step = select_one(plan,'al:step[@id="%s"]' % stepId)
                step.setAttributeNS(NO_NS, 'state', status)
                self.changes.append(('change', 'step', planId, step))
                self.notify_observers()
            except SelectException:
                import traceback
                traceback.print_exc()
                print unicode_to_gtk(_("step %s not found in plan %s")
                                     %(stepId, planId))

    def transition_changed(self, planId, transitionId, status):
        # ignore changes if not plan in local memory
        plan = self._select_local_by_id(planId)
        if plan:
            try:
                transition = select_one(plan, 'al:transition[@id="%s"]'
                                        % (transitionId))
                transition.setAttributeNS(NO_NS, 'state', status)
                self.changes.append(('change', 'transition',planId,transition))
                self.notify_observers()
            except SelectException:
                import traceback
                traceback.print_exc()
                print unicode_to_gtk(_("transition %s not found in plan %s")
                                     %(transitionId, planId))

    def plan_changed(self, planId, status):
        # ignore changes if not plan in local memory
        plan = self._select_local_by_id(planId)
        if plan:
            plan.setAttributeNS(NO_NS, 'state', status)
            self.changes.append(('change', 'plan', planId,plan))
            self.notify_observers()
        #else:
        #    print 'MM plan changed:', planId, type(planId)
        #    print unicode_to_gtk(_('plan %s not found') % planId)

    
    # Cookbook methods ########################################################
    def get_cookbooks(self):
        return self.type_d['recipe'].keys()

    def add_recipe_to_cookbook(self,name,cookbook):
        if name and cookbook:
            self.eid_count += 1
            eid = unicode_to_gtk(str(self.eid_count))
            recipe = self._memory.createElementNS(AL_NS, 'al:recipe')
            recipe.setAttributeNS(NO_NS, 'name', name)
            recipe.setAttributeNS(NO_NS, 'group', cookbook)
            recipe.setAttributeNS(AL_NS, 'al:eid', eid)
            recipe.as_xml = lambda n=recipe, en='UTF-8': as_xml(n, en)

            self._memory.documentElement.appendChild(recipe)
            self.eid_d[eid] = ['recipe', cookbook, name, recipe]
            self.type_d['recipe'].setdefault(cookbook, {})[name] = eid
            self.changes.append(('add', [eid, 'recipe', cookbook, name]))
            self.notify_observers()

    def get_xmls_cookbook(self, name):
        """get recipes from cookbook as xml string"""
        from StringIO import StringIO
        s = StringIO()
        s.write('<?xml version="1.0" encoding="UTF-8"?>\n<cookbook name="%s">'%name)
        for recipe in self.type_d['recipe'][name].keys():
            eid = self.type_d['recipe'][name][recipe]
            PrettyPrint(self.eid_d[eid][_E_DOM], s, 'UTF-8')
        s.write("</cookbook>")
        xmls = s.getvalue()
        s.close()
        return xmls


    # recipe methods ##########################################################
    def save_recipe(self, recipe):
        # WARNING: recipe is a dom tree, not an eid
        try:
            from HornModel import HornModel
            eid = recipe.getAttributeNS(AL_NS, 'eid')
#            PrettyPrint(recipe)
#            print 'EID', eid
            if HornModel().narval is not None:
                from HornActioner import HornActioner
#                print recipe, dir(recipe)
                HornActioner().replaceElement(eid, recipe.as_xml())
            else:
                from MemoryController import MemoryController
                tmprecipe = self.select_by_id(eid)
                r = tmprecipe.parentNode.ownerDocument.importNode(recipe,1)
                tmprecipe.parentNode.replaceChild(r,tmprecipe)
                self.eid_d[eid][_E_DOM] = r
                MemoryController().save_cookbook(recipe.getAttributeNS(NO_NS, 'group'))
        except:
            import traceback
            traceback.print_exc()

    def remove_recipe(self, eid):
        from HornModel import HornModel
        if HornModel().narval is not None:
            from HornActioner import HornActioner
            HornActioner().removeElement(eid)
        else:
            try:
                tmprecipe = select_by_id(eid)
                tmprecipe.parentNode.removeChild(tmprecipe)
            except:
                print unicode_to_gtk(_('problem when recipe is removed')),recipe


    # public nodes selection methods ###########################################
    # those functions suppose we can't have a request for a 'not in memory' node
    def select_by_id(self, eid):
        # do we ever have this element ?
        if self.eid_d[eid][_E_DOM]:
            return self.eid_d[eid][_E_DOM]
        else:
            #print 'GET ELEMENT FROM NARVAL', eid
            from HornActioner import HornActioner
            xml = HornActioner().getElement(eid)
            if xml:
                #node = read_domtree_from_string(xml, self._memory).firstChild
                node = self.fact.element_from_string(xml)
                self._memory.documentElement.appendChild(node)
                self.eid_d[eid][_E_DOM] = node
                return node
            else:
                print 'Element %d not found in memory' % eid
            return node

    def select(self, type, group, name=None):
        assert type in ('transform', 'action', 'recipe', 'plan')
        # do we ever have this element ?
        if name:
            return self.select_by_id(self.type_d[type][group][name])
        elif group:
            l = []
            for name in self.type_d[type][group].keys():
                l.append(self.select_by_id(self.type_d[type][group][name]))
                
    def get_elements_list(self, type=None):
        l = []
        if not type:
            for type in ('recipe', 'action', 'transform', 'plan', 'error', 'element'):
                l += self.get_elements_list(type)
        else:
            if type in ('recipe', 'action', 'transform', 'plan'):
                for group in self.type_d[type].keys():
                    for name in self.type_d[type][group].keys():
                        l.append((self.type_d[type][group][name],
                                  type, group, name))
            else: # element or error
                for name in self.type_d[type].keys():
                    for eid in self.type_d[type][name]:
                        elt = self.eid_d[eid]
                        l.append((eid, type, elt[_E_GROUP], name))
        return l
    
    def get_element_list(self, eid):
        return [eid] + self.eid_d[eid][:-1]
    
    def get_eid(self, type, group, name):
        assert type in ('transform', 'action', 'recipe', 'plan')
        return self.type_d[type][group][name]

    
    def empty_memory(self):
        if hasattr(self, '_memory'):
            self.fact.release_domtree(self._memory)
        from narval.gui.HornAbstraction import HORNElementsFactory, Document
        self.fact = HORNElementsFactory()
        self._memory = self.fact.create_dom_document()
        root = self._memory.createElementNS(NO_NS, 'root')
        self._memory.appendChild(root)
        self.eid_d = {}
        self.type_d = {'recipe':{}, 'transform':{}, 'action':{}, 'plan':{},
                       'error':{}, 'element':{}}

    ## Private ################################################################
    def _add_element(self, eid, type, group, name):
        assert type in ('transform', 'action', 'recipe', 'plan', 'element', 'error')
        if type == 'element' or type == 'error':
            self.type_d[type].setdefault(name, []).append(eid)
        else:
            if not self.type_d[type].has_key(group) or \
                   not self.type_d[type][group].has_key(name):
                self.type_d[type].setdefault(group, {})[name] = eid
            
    def _select_local_by_id(self, eid):
        if self.eid_d.has_key(eid) and self.eid_d[eid][_E_DOM]:
            return self.eid_d[eid][_E_DOM]
        else: return None

    def _fill_memory(self,elts_list):
        for eid, type, group, name in elts_list:
            self.eid_d[eid] = [type, group, name, None]
            self._add_element(eid, type, group, name)
                    
    def _local_add(self, node):
        self.eid_count += 1
        eid = str(self.eid_count)
        node.setAttributeNS(AL_NS, 'al:eid', eid)
        self._memory.documentElement.appendChild(node)
        type = node.localName
        if hasattr(node, 'name'):
            name = unicode_to_gtk(node.name)
        else:
            name = unicode_to_gtk(node.getAttributeNS(NO_NS, 'name'))
        group = unicode_to_gtk(node.group)
        type = unicode_to_gtk(type)
        eid = unicode_to_gtk(eid)
        self.eid_d[eid] = [type, group, name, node]
        self.type_d[type].setdefault(group, {})[name] = eid
        
