import re, sys, string


"""
karpathos.py

This module defines a number of public classes used by many Karpathos
Family Tree applications.  Quickly, these classes are:

Person --- represents a single person in a family graph.  Contains
edges that point at other people for spouses, children, and parents.
Also may contain information about birthdays, deathdays, etc.

FamilyGraph --- represents a collection of people.  This version contains
routines for two output formats (debug, ASCII tree) and no input formats.
Other formats are defined by overloaded FamilyGraph structures from other
modules, such as GEDCOM or papafile.
"""

builddebug = 0

class BadFormatError:
    def __init__(self, l):
        self.l = l
        return

    def __str__(self):
        return self.l
    
    pass

def printIndent (indent):
    for i in range(0, indent):
        print "",
        pass
    return

class Person:
    """
    Represents a person in our family graph (as it should more
    properly be called!)

    Every person is a graph node with the following kinds of edges:
    
    parent edges: points to this person's parents.  Should only
    point to two people.

    spouse edges: people you're married to, or significant others

    child edges: points to this person's kids.

    People also have some supplementary data.  This data can
    be read, but should be considered read only.  It is not created by
    the Person class, but by the overloading class (such as a GEDCOMPerson
    or PapaPerson).

    id           a unique id object or piece of data
    typecode     always the letter 'p'
    debug        a piece of debug information
    name         the full name of this person
    sex          'M', 'F', '?'
    birthstats   this is a dictionary with the following fields
                 birthstats['occurred'] = 1 if
                 other fields are valid, 0 else
                 birthstats['date'] = 'Day Month Year' if known,
                 otherwise ''.  If only some parts are known, only
                 those are included.  The Month is a 3 letter string as above.
                 birthstats['age'] = '?'; always '?' (See deathstats)           
    deathstats   this is a dictionary with the following fields
                 deathstats['occurred'] = 1 if
                 other fields are valid, 0 else
                 deathstats['date'] = 'Day Month Year' if known,
                 otherwise ''.  If only some parts are known, only
                 those are included.  The Month is a 3 letter string as above.
                 deathstats['age'] = 'age' at time of death, if known,
                 otherwise '?'.
    phonenum     The phone number as a string, or '' if unknown.
    comments     A list of comment strings
    seekrits     A list of non-public comment strings
    """

    def __init__(self, clone=None):
        """
        Initializes the basic linking data to empty.  The extra data
        such as name and phone number is assumed to be initialized by
        the subclass.  If the clone parameter is provided, however,
        that data is copied over and special clone fields are set on the
        two people.

        Later, after all of the new cloned Person objects have been
        instantiated, the cloneLinks() method can be called to copy over
        all the edges such as parents, spouses, and kids.
        """
        self.parents = []
        self.spouses = []
        self.kids = []
        self.debug = ""
        self.typecode = 'p'
        
        if clone:
            clone.clone = self
            self.clone = clone
            
            self.id = clone.id
            self.debug = clone.debug
            self.name = clone.name
            self.sex = clone.sex
            self.birthstats = clone.birthstats
            self.deathstats = clone.deathstats
            self.phonenum = clone.phonenum
            self.comments = clone.comments
            self.seekrits = clone.seekrits
        else:
            self.clone = None
            pass
        return

    def cloneLinks(self):
        """
        See above for details on person cloning.
        """
        for p in self.clone.parents: self.addParent (p.clone)
        for p in self.clone.spouses: self.addSpouse (p.clone)
        for p in self.clone.kids: self.addChild (p.clone)
        return
            

    def isPrimal (self):
        """
        Returns true if this person is a 'primal' ancestor.
        This is defined as one who has no parents, and either no
        spouses or a spouse with no recorded parents.
        """
        if self.parents:
            return 0
        else:
            for s in self.spouses:
                if s.parents:
                    return 0
                pass
            pass
        return 1

    def addParent (self, p):
        if not p in self.parents:
            self.parents.append(p)
        return

    def addSpouse (self, p):
        if not p in self.spouses:
            self.spouses.append(p)
        return

    def addChild (self, p):
        if not p in self.kids:
            self.kids.append(p)
        return

    def getTag (self):
        return "%s (%s)" % (self.name, self.sex)

    def __str__ (self):
        return self.getTag()

    def _newline(self, indent, person):
        #print self.id,
        i = 0

        if person: dispindent = indent[:-1]
        else: dispindent = indent

        for i in dispindent:
            if i:
                print "| ",
            else:
                print ". ",
                pass
            pass

        if person and indent: print "+-",
        return

    # 
    # Debug ASCII tree format dumping
    #

    def _prettyprintKids (self, kids, indent):
        # Now dump the kids we found
        for kid in kids[:-1]: kid.prettyprint(indent + [1])
        for kid in kids[-1:]: kid.prettyprint(indent + [0])
        return

    def prettyprint(self, indent=[]):
        if not self.mark:
            self.mark = 1
            
            self._newline (indent, 1)
            print self.getTag()

            # Dump comments and seekrit
            for comment in self.comments:
                self._newline(indent, 0)
                print comment
                pass

            for spouse in self.spouses:
                kids = []
                for kid in self.kids:
                    if spouse in kid.parents: kids.append(kid)
                    pass
                
                if kids:
                    self._newline(indent, 0)
                    print "Children with %s:" % spouse.getTag()
                    self._prettyprintKids (kids, indent)
                    pass
                else:
                    self._newline(indent, 0)
                    print "Married to %s" % spouse.getTag()
                    pass
                pass
            pass
        return

    def dump(self, outfile):
        print >> outfile, "Person p[%s] debug=%s" % (self.id, self.debug)
        print >> outfile, "name=%s sex=%s" % (self.name, self.sex)
        print >> outfile, "comments=%s" % (self.comments)
        if self.parents:
            print >> outfile, "parents=%s" %str(map(lambda z: "p[%s]"%z.id,
                                                   self.parents))
            pass
        if self.kids:
            print >> outfile, "kids=%s" %str(map(lambda z: "p[%s]"%z.id,
                                                 self.kids))
            pass
        print >> outfile
        return

    pass # end class Person

class FamilyGraph:
    """
    Represents our family graph.  Basically a collection of People.
    Also has routines for parsing different kinds of inputs and
    populating the graph, as well as for printing it out.
    """
    def __init__(self):
        self.people = []
        return

    ##########################################################
    ### Output ###############################################
    ##########################################################

    def dump(self, outfile):
        """
        Prints an ugly debugging representation of each person
        """
        for p in self.people:
            p.dump(outfile)
            pass
        return

    def prettyprint(self):
        """
        Prints a pretty ASCII tree like
        Papa Bear + Mama Bear
        +- baby bear 1
        |  how cute
        +- teenage bear + leader of the pack
        |  +- illegitamate baby bear
        +- baby bear 2
        """

        # First create a mark on each person and make it zero
        for p in self.people: p.mark = 0
        
        for p in self.people:
            if p.isPrimal():
                for pp in self.people:
                    if not pp.isPrimal():
                        pp.mark = 0
                    pass
                p.prettyprint ()
                pass
            pass
        return

    pass # end class FamilyGraph
