#
# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2006  Donald N. Allingham
#
# 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
#

# $Id: _GrampsInMemDB.py 9118 2007-10-09 19:29:20Z bmcage $

"""
Provides the common infrastructure for database formats that
must hold all of their data in memory.
"""

#-------------------------------------------------------------------------
#
# Python modules
#
#-------------------------------------------------------------------------
import time

#-------------------------------------------------------------------------
#
# GRAMPS modules
#
#-------------------------------------------------------------------------
from RelLib import *
from _GrampsDbBase import *

class GrampsInMemCursor(GrampsCursor):
    """
    Cursor class for in-memory database classes. Since the in-memory
    classes use python dictionaries, the python iter class is used
    to provide the cursor function.
    """
    def __init__(self,src_map):
        self.src_map = src_map
        self.current = iter(src_map)
        
    def first(self):
        self.current = iter(self.src_map)
        return self.next()

    def next(self):
        try:
            index = self.current.next()
            return (index,self.src_map[index])
        except StopIteration:
            return None

    def close(self):
        pass
        
#-------------------------------------------------------------------------
#
# GrampsInMemDB
#
#-------------------------------------------------------------------------
class GrampsInMemDB(GrampsDbBase):
    """GRAMPS database object. This object is a base class for other
    objects."""

    ID_INDEX = 1 # an index of the gramps id in the data tuple

    def __init__(self):
        """creates a new GrampsDB"""
        GrampsDbBase.__init__(self)
        self.person_map       = {}
        self.name_group       = {}
        self.family_map       = {}
        self.place_map        = {}
        self.source_map       = {}
        self.repository_map   = {}
        self.media_map        = {}
        self.event_map        = {}
        self.metadata         = {}
        self.filename         = ""
        self.id_trans         = {}
        self.pid_trans        = {}
        self.fid_trans        = {}
        self.eid_trans        = {}
        self.sid_trans        = {}
        self.rid_trans        = {}
        self.oid_trans        = {}
        self.undodb           = []

    def load(self,name,callback,mode="w"):
        self.full_name = name
        self.readonly = mode == "r"
        self.open_undodb()

        # Re-set the undo history to a fresh session start
        self.undoindex = -1
        self.translist = [None] * len(self.translist)
        self.abort_possible = True
        self.undo_history_timestamp = time.time()

    def transaction_commit(self,transaction,msg):
        GrampsDbBase.transaction_commit(self,transaction,msg)
        if transaction.batch:
            self.build_surname_list()

    def get_person_cursor(self):
        return GrampsInMemCursor(self.person_map)

    def get_family_cursor(self):
        return GrampsInMemCursor(self.family_map)

    def get_event_cursor(self):
        return GrampsInMemCursor(self.event_map)

    def get_place_cursor(self):
        return GrampsInMemCursor(self.place_map)

    def get_source_cursor(self):
        return GrampsInMemCursor(self.source_map)

    def get_repository_cursor(self):
        return GrampsInMemCursor(self.repository_map)

    def get_media_cursor(self):
        return GrampsInMemCursor(self.media_map)

    def close(self):
        self.close_undodb()

    def set_name_group_mapping(self,name,group):
        if group == None and self.name_group.has_key(name):
            del self.name_group[name]
        if group is not None :
            self.name_group[name] = group

    def build_surname_list(self):
        a = set()
        for person_id in iter(self.person_map):
            p = self.get_person_from_handle(person_id)
            a.add(unicode(p.get_primary_name().get_surname()))
        self.surname_list = list(a)
        self.sort_surname_list()

    def remove_from_surname_list(self,person):
        """
        Check whether there are persons with the same surname left in
        the database. If not then we need to remove the name from the list.
        The function must be overridden in the derived class.
        """
        name = str(person.get_primary_name().get_surname())
        count = 0
        do_remove = True
        
        for person_id in iter(self.person_map):
            p = self.get_person_from_handle(person_id)
            pn = str(p.get_primary_name().get_surname())
            if pn == name:
                count += 1
            if count > 1:
                do_remove = False
                break

        if do_remove:
            self.surname_list.remove(unicode(name))

    def _del_person(self,handle):
        person = self.get_person_from_handle(str(handle))
        del self.id_trans[person.get_gramps_id()]
        del self.person_map[str(handle)]

    def _del_source(self,handle):
        source = self.get_source_from_handle(str(handle))
        del self.sid_trans[source.get_gramps_id()]
        del self.source_map[str(handle)]

    def _del_repository(self,handle):
        repository = self.get_repository_from_handle(str(handle))
        del self.rid_trans[repository.get_gramps_id()]
        del self.repository_map[str(handle)]

    def _del_place(self,handle):
        place = self.get_place_from_handle(str(handle))
        del self.pid_trans[place.get_gramps_id()]
        del self.place_map[str(handle)]

    def _del_media(self,handle):
        obj = self.get_object_from_handle(str(handle))
        del self.oid_trans[obj.get_gramps_id()]
        del self.media_map[str(handle)]

    def _del_family(self,handle):
        family = self.get_family_from_handle(str(handle))
        del self.fid_trans[family.get_gramps_id()]
        del self.family_map[str(handle)]

    def _del_event(self,handle):
        event = self.get_event_from_handle(str(handle))
        del self.eid_trans[event.get_gramps_id()]
        del self.event_map[str(handle)]

    def get_trans_map(self,signal_root):
        """
        A silly method to get the secondary index map based on the
        signal name root for a given object type. The BDB backend
        manages this transparently, but we need to manually take
        care of secondary indices in the InMem backend.
        """
        trans_maps = {
            'person': self.id_trans,
            'family': self.fid_trans,
            'source': self.sid_trans,
            'event' : self.eid_trans,
            'media' : self.oid_trans,
            'place' : self.pid_trans,
            'repository': self.rid_trans}
        return trans_maps[signal_root]

    def undo_data(self, data, handle, db_map, signal_root):
        """
        The BDB backend manages secondary indices transparently,
        but we need to manually take care of secondary indices
        in the InMem backend.
        """
        trans_map = self.get_trans_map(signal_root)
        obj = db_map.get(handle)
        if data == None:
            self.emit(signal_root + '-delete', ([handle], ))
            del trans_map[obj[self.ID_INDEX]]
            del db_map[handle]
        else:
            if obj:
                signal = signal_root + '-update'
                if obj[self.ID_INDEX] != data[self.ID_INDEX]:
                    del trans_map[obj[self.ID_INDEX]]
            else:
                signal = signal_root + '-add'
            db_map[handle] = data
            trans_map[data[self.ID_INDEX]] = str(handle)
            self.emit(signal, ([handle], ))
    
    def _commit_inmem_base(self,obj,db_map,trans_map):
        if self.readonly or not obj or not obj.get_handle():
            return False
        gid = obj.gramps_id
        old_data = db_map.get(obj.handle)
        if old_data:
            old_id = old_data[self.ID_INDEX]
            if old_id is not None and obj.gramps_id != old_id:
                del trans_map[old_id]
        trans_map[gid] = obj.handle
        return True

    def commit_person(self,person,transaction,change_time=None):
        if not self._commit_inmem_base(person,self.person_map,self.id_trans):
            return
        GrampsDbBase.commit_person(self,person,transaction,change_time)

    def commit_place(self,place,transaction,change_time=None):
        if not self._commit_inmem_base(place,self.place_map,self.pid_trans):
            return
        GrampsDbBase.commit_place(self,place,transaction,change_time)

    def commit_family(self,family,transaction,change_time=None):
        if not self._commit_inmem_base(family,self.family_map,self.fid_trans):
            return
        GrampsDbBase.commit_family(self,family,transaction,change_time)

    def commit_event(self,event,transaction,change_time=None):
        if not self._commit_inmem_base(event,self.event_map,self.eid_trans):
            return
        GrampsDbBase.commit_event(self,event,transaction,change_time)

    def commit_media_object(self,obj,transaction,change_time=None):
        if not self._commit_inmem_base(obj,self.media_map,self.oid_trans):
            return
        GrampsDbBase.commit_media_object(self,obj,transaction,change_time)

    def commit_source(self,source,transaction,change_time=None):
        if not self._commit_inmem_base(source,self.source_map,self.sid_trans):
            return
        GrampsDbBase.commit_source(self,source,transaction,change_time)

    def commit_repository(self,repository,transaction,change_time=None):
        if not self._commit_inmem_base(repository,self.repository_map,
                                       self.rid_trans):
            return
        GrampsDbBase.commit_repository(self,repository,transaction,change_time)

    def get_person_from_gramps_id(self,val):
        handle = self.id_trans.get(str(val))
        if handle:
            data = self.person_map[handle]
            if data:
                person = Person()
                person.unserialize(data)
                return person
        return None

    def get_family_from_gramps_id(self,val):
        handle = self.fid_trans.get(str(val))
        if handle:
            data = self.family_map[handle]
            if data:
                family = Family()
                family.unserialize(data)
                return family
        return None

    def get_event_from_gramps_id(self,val):
        handle = self.eid_trans.get(str(val))
        if handle:
            data = self.event_map[handle]
            if data:
                event = Event()
                event.unserialize(data)
                return event
        return None

    def get_place_from_gramps_id(self,val):
        handle = self.pid_trans.get(str(val))
        if handle:
            data = self.place_map[handle]
            if data:
                place = Place()
                place.unserialize(data)
                return place
        return None

    def get_source_from_gramps_id(self,val):
        handle = self.sid_trans.get(str(val))
        if handle:
            data = self.source_map[handle]
            if data:
                source = Source()
                source.unserialize(data)
                return source
        return None

    def get_repository_from_gramps_id(self,val):
        handle = self.rid_trans.get(str(val))
        if handle:
            data = self.repository_map[handle]
            if data:
                repository = Repository()
                repository.unserialize(data)
                return repository
        return None

    def get_object_from_gramps_id(self,val):
        handle = self.oid_trans.get(str(val))
        if handle:
            data = self.media_map[handle]
            if data:
                obj = MediaObject()
                obj.unserialize(data)
                return obj
        return None

    def db_is_changed(self):
        return not self.readonly and \
              (len(self.undodb) > 0 or 
                not self.abort_possible)

