#
# NotRelated.py - Plugin for Gramps
#
# Copyright (C) 2007 Stephane Charette
#
# 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: NotRelated.py 10215 2008-03-07 17:36:15Z ldnp $

"Find people who are not related to the selected person"

#------------------------------------------------------------------------
#
# standard python modules
#
#------------------------------------------------------------------------
import os
from gettext import gettext as _

#------------------------------------------------------------------------
#
# GNOME/GTK modules
#
#------------------------------------------------------------------------
import gtk
import gobject

#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from PluginUtils import register_report
from ReportBase import ReportUtils, CATEGORY_VIEW, MODE_GUI
from Editors import EditPerson, EditFamily
from QuestionDialog import WarningDialog
import ManagedWindow
import Utils

#------------------------------------------------------------------------
#
# 
#
#------------------------------------------------------------------------
class NotRelated(ManagedWindow.ManagedWindow) :

    def __init__(self, dbstate, uistate) :
        person = dbstate.get_active_person()
        self.name = person.get_primary_name().get_regular_name()
        self.title = _('Not related to "%s"') % self.name
        ManagedWindow.ManagedWindow.__init__(self, uistate, [], self.__class__)
        self.dbstate = dbstate
        self.uistate = uistate
        self.db = dbstate.db
        glade_file = "%s/NotRelated.glade" % os.path.dirname(__file__)
        topDialog = gtk.glade.XML(glade_file, "top", "gramps")
        topDialog.signal_autoconnect({"destroy_passed_object" : self.close})

        window = topDialog.get_widget("top")
        title = topDialog.get_widget("title")
        self.set_window(window, title, self.title)

        self.markercombo = topDialog.get_widget("markercombo")
        self.markerapply = topDialog.get_widget("markerapply")
        self.markercombo.set_sensitive(False)
        self.markerapply.set_sensitive(False)
        self.markerapply.connect('clicked', self.applyMarkerClicked)

        # start the progress indicator
        self.progress = Utils.ProgressMeter(self.title,_('Starting'))

        # setup the columns
        self.model = gtk.TreeStore(
            gobject.TYPE_STRING,    # 0==name
            gobject.TYPE_STRING,    # 1==person gid
            gobject.TYPE_STRING,    # 2==parents
            gobject.TYPE_STRING,    # 3==marker
            gobject.TYPE_STRING)    # 4==family gid (not shown to user)

        # note -- don't assign the model to the tree until it has been populated,
        # otherwise the screen updates are terribly slow while names are appended
        self.treeView = topDialog.get_widget("treeview")
        col1 = gtk.TreeViewColumn(_('Name'),    gtk.CellRendererText(), text=0)
        col2 = gtk.TreeViewColumn(_('ID'),      gtk.CellRendererText(), text=1)
        col3 = gtk.TreeViewColumn(_('Parents'), gtk.CellRendererText(), text=2)
        col4 = gtk.TreeViewColumn(_('Marker'),  gtk.CellRendererText(), text=3)
        col1.set_resizable(True)
        col2.set_resizable(True)
        col3.set_resizable(True)
        col4.set_resizable(True)
        col1.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col2.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col3.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col4.set_sizing(gtk.TREE_VIEW_COLUMN_AUTOSIZE)
        col1.set_sort_column_id(0)
#        col2.set_sort_column_id(1)
#        col3.set_sort_column_id(2)
        col4.set_sort_column_id(3)
        self.treeView.append_column(col1)
        self.treeView.append_column(col2)
        self.treeView.append_column(col3)
        self.treeView.append_column(col4)
        self.treeSelection = self.treeView.get_selection()
        self.treeSelection.set_mode(gtk.SELECTION_MULTIPLE)
        self.treeSelection.set_select_function(self.selectIsAllowed, full=True)
        self.treeSelection.connect('changed', self.rowSelectionChanged)
        self.treeView.connect('row-activated', self.rowActivated)

        # initialize a few variables we're going to need
        self.numberOfPeopleInDatabase    = self.db.get_number_of_people()
        self.numberOfRelatedPeople       = 0
        self.numberOfUnrelatedPeople     = 0

        # create the sets used to track related and unrelated people
        self.handlesOfPeopleToBeProcessed       = set()
        self.handlesOfPeopleAlreadyProcessed    = set()
        self.handlesOfPeopleNotRelated          = set()

        # build a set of all people related to the selected person
        self.handlesOfPeopleToBeProcessed.add(person.get_handle())
        self.findRelatedPeople()

        # now that we have our list of related people, find everyone
        # in the database who isn't on our list
        self.findUnrelatedPeople()

        # populate the treeview model with the names of unrelated people
        if self.numberOfUnrelatedPeople == 0:
            title.set_text('Everyone in the database is related to %s' % self.name)
        else:
            self.populateModel()
            self.model.set_sort_column_id(0, gtk.SORT_ASCENDING)
            self.treeView.set_model(self.model)
#            self.treeView.set_row_separator_func(self.iterIsSeparator)
            self.treeView.expand_all()

        # done searching through the database, so close the progress bar
        self.progress.close()

        self.show()


    def iterIsSeparator(self, model, iter) :
        # return True only if the row is to be treated as a separator
        if self.model.get_value(iter, 1) == '':  # does the row have a GID?
            return True
        return False


    def selectIsAllowed(self, selection, model, path, isSelected) :
        # return True/False depending on if the row being selected is a leaf node
        iter = self.model.get_iter(path)
        if self.model.get_value(iter, 1) == '': # does the row have a GID?
            return False
        return True


    def rowSelectionChanged(self, selection) :
        state = selection.count_selected_rows() > 0
        self.markercombo.set_sensitive(state)
        self.markerapply.set_sensitive(state)


    def rowActivated(self, treeView, path, column) :
        # first we need to check that the row corresponds to a person
        iter = self.model.get_iter(path)
        personGid = self.model.get_value(iter, 1)
        familyGid = self.model.get_value(iter, 4)

        if familyGid != '': # do we have a family?
            # get the parent family for this person
            family = self.db.get_family_from_gramps_id(familyGid)
            if family:
                try:
                    EditFamily(self.dbstate, self.uistate, [], family)
                except Errors.WindowActiveError:
                    pass

        elif personGid != '': # do we have a person?
            # get the person that corresponds to this GID
            person = self.db.get_person_from_gramps_id(personGid)
            if person:
                try:
                    EditPerson(self.dbstate, self.uistate, [], person)
                except Errors.WindowActiveError:
                    pass


    def applyMarkerClicked(self, button) :
        progress    = None
        rows        = self.treeSelection.count_selected_rows()
        marker      = self.markercombo.get_active_text()

        # if more than 1 person is selected, use a progress indicator
        if rows > 1:
            progress = Utils.ProgressMeter(self.title,_('Starting'))
            progress.set_pass(_('Setting marker for %d people') % rows, rows)

        # start the db transaction
        transaction = self.db.transaction_begin()

        # iterate through all of the selected rows
        (model, paths) = self.treeSelection.get_selected_rows()
        for path in paths:
            if progress:
                progress.step()

            # for the current row, get the GID and the person from the database
            iter        = self.model.get_iter(path)
            personGid   = self.model.get_value(iter, 1)
            person      = self.db.get_person_from_gramps_id(personGid)

            # change the marker
            person.set_marker(marker)
            self.model.set_value(iter, 3, marker)

            # save this change
            self.db.commit_person(person, transaction)

        # commit the entire transaction
        self.db.transaction_commit(transaction, "mark not related")

        if progress:
            progress.close()


    def findRelatedPeople(self) :

        self.progress.set_pass(_('Finding relationships between %d people') % self.numberOfPeopleInDatabase, self.numberOfPeopleInDatabase)

        # as long as we have people we haven't processed yet, keep looping
        while len(self.handlesOfPeopleToBeProcessed) > 0:
            handle = self.handlesOfPeopleToBeProcessed.pop()

### DEBUG DEBUG DEBUG
#            if len(self.handlesOfPeopleAlreadyProcessed) > 50:
#                break
###

            # see if we've already processed this person
            if handle in self.handlesOfPeopleAlreadyProcessed:
                continue

            person = self.db.get_person_from_handle(handle)

            # if we get here, then we're dealing with someone new
            self.progress.step()

            # remember that we've now seen this person
            self.handlesOfPeopleAlreadyProcessed.add(handle)

            # we have 3 things to do:  find (1) spouses, (2) parents, and (3) children

            # step 1 -- spouses
            for familyHandle in person.get_family_handle_list():
                family = self.db.get_family_from_handle(familyHandle)
                spouseHandle = ReportUtils.find_spouse(person, family)
                if spouseHandle:
                    if spouseHandle not in self.handlesOfPeopleAlreadyProcessed or spouseHandle not in self.handlesOfPeopleToBeProcessed:
                        self.handlesOfPeopleToBeProcessed.add(spouseHandle)

            # step 2 -- parents
            for familyHandle in person.get_parent_family_handle_list():
                family = self.db.get_family_from_handle(familyHandle)
                fatherHandle = family.get_father_handle()
                motherHandle = family.get_mother_handle()
                if fatherHandle:
                    if fatherHandle not in self.handlesOfPeopleAlreadyProcessed or fatherHandle not in self.handlesOfPeopleToBeProcessed:
                        self.handlesOfPeopleToBeProcessed.add(fatherHandle)
                if motherHandle:
                    if motherHandle not in self.handlesOfPeopleAlreadyProcessed or motherHandle not in self.handlesOfPeopleToBeProcessed:
                        self.handlesOfPeopleToBeProcessed.add(motherHandle)

            # step 3 -- children
            for familyHandle in person.get_family_handle_list():
                family = self.db.get_family_from_handle(familyHandle)
                for childRef in family.get_child_ref_list():
                    childHandle = childRef.ref
                    if childHandle:
                        if childHandle not in self.handlesOfPeopleAlreadyProcessed or childHandle not in self.handlesOfPeopleToBeProcessed:
                            self.handlesOfPeopleToBeProcessed.add(childHandle)


    def findUnrelatedPeople(self) :

        # update our numbers
        self.numberOfRelatedPeople   = len(self.handlesOfPeopleAlreadyProcessed)
        self.numberOfUnrelatedPeople = self.numberOfPeopleInDatabase - self.numberOfRelatedPeople

        if self.numberOfUnrelatedPeople > 0:
            # we have at least 1 "unrelated" person to find

            if self.numberOfUnrelatedPeople == 1:
                self.progress.set_pass(_('Looking for 1 person'), self.numberOfPeopleInDatabase)
            else:
                self.progress.set_pass(_('Looking for %d people') % self.numberOfUnrelatedPeople, self.numberOfPeopleInDatabase)

            # loop through everyone in the database
            for handle in self.db.get_person_handles(False):

                self.progress.step()

                # if this person is related, then skip to the next one
                if handle in self.handlesOfPeopleAlreadyProcessed:
                    continue

### DEBUG DEBUG DEBUG
#                if len(self.handlesOfPeopleNotRelated) > 10:
#                    break
###

                # if we get here, we have someone who is "not related"
                self.handlesOfPeopleNotRelated.add(handle)


    def populateModel(self) :

        if self.numberOfUnrelatedPeople == 1:
            self.progress.set_pass(_('Looking up the name for 1 person'), 1)
        else:
            self.progress.set_pass(_('Looking up the names for %d people') % self.numberOfUnrelatedPeople, self.numberOfUnrelatedPeople)

        # loop through the entire list of unrelated people
        for handle in self.handlesOfPeopleNotRelated:
            self.progress.step()
            person      = self.db.get_person_from_handle(handle)
            primaryname = person.get_primary_name()
            surname     = primaryname.get_surname()
            name        = primaryname.get_name()
            gid         = person.get_gramps_id()
            marker      = person.get_marker()

            # find the names of the parents
            familygid   = ''
            parentNames = ''
            parentFamilyHandle = person.get_main_parents_family_handle()
            if parentFamilyHandle:
                parentFamily = self.db.get_family_from_handle(parentFamilyHandle)
                familygid    = parentFamily.get_gramps_id()
                fatherName   = None
                motherName   = None
                fatherHandle = parentFamily.get_father_handle()
                if fatherHandle:
                    father      = self.db.get_person_from_handle(fatherHandle)
                    fatherName  = father.get_primary_name().get_first_name()
                motherHandle = parentFamily.get_mother_handle()
                if motherHandle:
                    mother      = self.db.get_person_from_handle(motherHandle)
                    motherName  = mother.get_primary_name().get_first_name()

                # now that we have the names, come up with a label we can use
                if fatherName:
                    parentNames += fatherName
                if fatherName and motherName:
                    parentNames += ' & '
                if motherName:
                    parentNames += motherName

            # get the surname node (or create it if it doesn't exist)

            # start with the root
            iter = self.model.get_iter_root()
            # look for a node with a matching surname
            while iter:
                if self.model.get_value(iter, 0) == surname:
                    break;
                iter = self.model.iter_next(iter)

            # if we don't have a valid iter, then create a new top-level node
            if not iter:
                iter = self.model.append(None, [surname, '', '', '', ''])

            # finally, we now get to add this person to the model
            self.model.append(iter, [name, gid, parentNames, marker, familygid])


    def build_menu_names(self, obj):
        return (self.title, None)

#-------------------------------------------------------------------------
#
#
#
#-------------------------------------------------------------------------
register_report(
    name = 'not_related',
    category = CATEGORY_VIEW,
    report_class = NotRelated,
    options_class = None,
    modes = MODE_GUI,
    translated_name = _("Not Related"),
    status = _("Stable"),
    author_name = "Stephane Charette",
    author_email = "stephanecharette@gmail.com",
    description = _("Find people who are not in any way related to the "
                    "selected person")
    )

