# Gramps - a GTK+/GNOME based genealogy program
#
# Copyright (C) 2000-2007  Donald N. Allingham
# Copyright (C) 2008-2009  Brian G. Matherly
# Copyright (C) 2010       Jakim Friant
#
# 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: Calendar.py 18651 2011-12-24 19:08:19Z paul-franklin $
#
#------------------------------------------------------------------------
#
# python modules
#
#------------------------------------------------------------------------
from gen.ggettext import gettext as _
from gen.ggettext import ngettext
from functools import partial
import datetime
import time
 
#------------------------------------------------------------------------
#
# GRAMPS modules
#
#------------------------------------------------------------------------
from gen.display.name import displayer as _nd 
from Errors import ReportError
import Relationship
from gen.plug.docgen import (FontStyle, ParagraphStyle, GraphicsStyle,
                             FONT_SERIF, PARA_ALIGN_CENTER,
                             PARA_ALIGN_LEFT, PARA_ALIGN_RIGHT)
from gen.plug.docgen.fontscale import string_trim
from gen.plug.menu import (BooleanOption, StringOption, NumberOption, 
                         EnumeratedListOption, FilterOption, PersonOption)
from gen.plug.report import Report
from gen.plug.report import utils as ReportUtils
from gen.plug.report import MenuReportOptions
from Utils import probably_alive
from DateHandler import displayer as _dd
import GrampsLocale
import gen.lib
 
import libholiday
from libholiday import g2iso
 
#------------------------------------------------------------------------
#
# Constants
#
#------------------------------------------------------------------------
pt2cm = ReportUtils.pt2cm
cm2pt = ReportUtils.cm2pt
 
#------------------------------------------------------------------------
#
# Calendar
#
#------------------------------------------------------------------------
class Calendar(Report):
    """
    Create the Calendar object that produces the report.
    """
    def __init__(self, database, options, user):
        Report.__init__(self, database, options, user)
        menu = options.menu
        self._user = user
        get_value = lambda name: menu.get_option_by_name(name).get_value()
 
        self.year = get_value('year')
        self.name_format = get_value('name_format')
        self.country = get_value('country')
        self.anniversaries = get_value('anniversaries')
        self.start_dow = get_value('start_dow')
        self.maiden_name = get_value('maiden_name')
        self.alive = get_value('alive')
        self.birthdays = get_value('birthdays')
        self.text1 = get_value('text1')
        self.text2 = get_value('text2')
        self.text3 = get_value('text3')
        self.filter_option = menu.get_option_by_name('filter')
        self.filter = self.filter_option.get_filter()
        pid = get_value('pid')
        self.center_person = database.get_person_from_gramps_id(pid)
        if (self.center_person == None) :
            raise ReportError(_("Person %s is not in the Database") % pid )
 
    def get_name(self, person, maiden_name = None):
        """ Return person's name, unless maiden_name given, 
            unless married_name listed. 
        """
        # Get all of a person's names:
        primary_name = person.get_primary_name()
        married_name = None
        names = [primary_name] + person.get_alternate_names()
        for name in names:
            if int(name.get_type()) == gen.lib.NameType.MARRIED:
                married_name = name
                break # use first
        # Now, decide which to use:
        if maiden_name is not None:
            if married_name is not None:
                name = gen.lib.Name(married_name)
            else:
                name = gen.lib.Name(primary_name)
                surname = gen.lib.Surname()
                surname.set_surname(maiden_name)
                name.set_surname_list([surname])
        else:
            name = gen.lib.Name(primary_name)
        name.set_display_as(self.name_format)
        return _nd.display_name(name)
 
    def draw_rectangle(self, style, sx, sy, ex, ey):
        """ This should be in BaseDoc """
        self.doc.draw_line(style, sx, sy, sx, ey)
        self.doc.draw_line(style, sx, sy, ex, sy)
        self.doc.draw_line(style, ex, sy, ex, ey)
        self.doc.draw_line(style, sx, ey, ex, ey)
 
### The rest of these all have to deal with calendar specific things
 
    def add_day_item(self, text, month, day, format="CAL-Text"):
        """ Add an item to a day. """
        month_dict = self.calendar.get(month, {})
        day_list = month_dict.get(day, [])
        day_list.append((format, text))
        month_dict[day] = day_list
        self.calendar[month] = month_dict
 
    def __get_holidays(self):
        """ Get the holidays for the specified country and year """
        holiday_table = libholiday.HolidayTable()
        country = holiday_table.get_countries()[self.country]
        holiday_table.load_holidays(self.year, country)
        for month in range(1, 13):
            for day in range(1, 32):
                holiday_names = holiday_table.get_holidays(month, day) 
                for holiday_name in holiday_names:
                    self.add_day_item(holiday_name, month, day, "CAL-Holiday")
 
    def write_report(self):
        """ The short method that runs through each month and creates a page. """
        # initialize the dict to fill:
        self.calendar = {}
 
        # get the information, first from holidays:
        if self.country != 0:
            self.__get_holidays()
 
        # get data from database:
        self.collect_data()
        # generate the report:
        self._user.begin_progress( _('Calendar Report'), 
                                   _('Formatting months...'), 12)
        for month in range(1, 13):
            self._user.step_progress()
            self.print_page(month)
        self._user.end_progress()
 
    def print_page(self, month):
        """
        This method actually writes the calendar page.
        """
        style_sheet = self.doc.get_style_sheet()
        ptitle = style_sheet.get_paragraph_style("CAL-Title")
        ptext = style_sheet.get_paragraph_style("CAL-Text")
        pdaynames = style_sheet.get_paragraph_style("CAL-Daynames")
        pnumbers = style_sheet.get_paragraph_style("CAL-Numbers")
        ptext1style = style_sheet.get_paragraph_style("CAL-Text1style")
 
        self.doc.start_page()
        width = self.doc.get_usable_width()
        height = self.doc.get_usable_height()
        header = 2.54 # one inch
        self.draw_rectangle("CAL-Border", 0, 0, width, height)
        self.doc.draw_box("CAL-Title", "", 0, 0, width, header)
        self.doc.draw_line("CAL-Border", 0, header, width, header)
        year = self.year
        title = "%s %d" % (_dd.long_months[month].capitalize(), year)
        font_height = pt2cm(ptitle.get_font().get_size())
        self.doc.center_text("CAL-Title", title, width/2, font_height * 0.25)
        cell_width = width / 7
        cell_height = (height - header)/ 6
        current_date = datetime.date(year, month, 1)
        spacing = pt2cm(1.25 * ptext.get_font().get_size()) # 158
        if current_date.isoweekday() != g2iso(self.start_dow + 1):
            # Go back to previous first day of week, and start from there
            current_ord = (current_date.toordinal() -
                           ((current_date.isoweekday() + 7) -
                            g2iso(self.start_dow + 1)) % 7)
        else:
            current_ord = current_date.toordinal()
        for day_col in range(7):
            font_height = pt2cm(pdaynames.get_font().get_size())
            self.doc.center_text("CAL-Daynames", 
                                 GrampsLocale.long_days[(day_col+
                                                         g2iso(self.start_dow + 1))
                                                        % 7 + 1].capitalize(), 
                                 day_col * cell_width + cell_width/2, 
                                 header - font_height * 1.5)
        for week_row in range(6):
            something_this_week = 0
            for day_col in range(7):
                thisday = current_date.fromordinal(current_ord)
                if thisday.month == month:
                    something_this_week = 1
                    self.draw_rectangle("CAL-Border", day_col * cell_width, 
                                        header + week_row * cell_height, 
                                        (day_col + 1) * cell_width, 
                                        header + (week_row + 1) * cell_height)
                    last_edge = (day_col + 1) * cell_width
                    self.doc.center_text("CAL-Numbers", str(thisday.day), 
                                         day_col * cell_width + cell_width/2, 
                                         header + week_row * cell_height)
                    list_ = self.calendar.get(month, {}).get(thisday.day, [])
                    list_.sort() # to get CAL-Holiday on bottom
                    position = 0.0 
                    for (format, p) in list_:
                        lines = p.count("\n") + 1 # lines in the text
                        position += (lines  * spacing)
                        current = 0
                        for line in p.split("\n"):
                            # make sure text will fit:
                            numpos = pt2cm(pnumbers.get_font().get_size())
                            if position + (current * spacing) - 0.1 >= cell_height - numpos: # font daynums
                                continue
                            font = ptext.get_font()
                            line = string_trim(font, line, cm2pt(cell_width + 0.2))
                            self.doc.draw_text(format, line, 
                                              day_col * cell_width + 0.1, 
                                              header + (week_row + 1) * cell_height - position + (current * spacing) - 0.1)
                            current += 1
                current_ord += 1
        if not something_this_week:
            last_edge = 0
        font_height = pt2cm(1.5 * ptext1style.get_font().get_size())
        x = last_edge + (width - last_edge)/2
        y = height - font_height
        self.doc.center_text("CAL-Text1style", self.text1, x, y * 3) 
        self.doc.center_text("CAL-Text2style", self.text2, x, y * 2) 
        self.doc.center_text("CAL-Text3style", self.text3, x, y * 1) 
        self.doc.end_page()
 
    def collect_data(self):
        """
        This method runs through the data, and collects the relevant dates
        and text.
        """
        db = self.database
        people = db.iter_person_handles()
        self._user.begin_progress(_('Calendar Report'), 
                                  _('Applying Filter...'), 
                                  db.get_number_of_people())
        people = self.filter.apply(self.database, people, 
                                   self._user.step_progress)
        rel_calc = Relationship.get_relationship_calculator()
        self._user.end_progress()
 
        self._user.begin_progress(_('Calendar Report'), 
                                 _('Reading database...'), len(people))
        for person_handle in people:
            self._user.step_progress()
            person = db.get_person_from_handle(person_handle)
            birth_ref = person.get_birth_ref()
            birth_date = None
            if birth_ref:
                birth_event = db.get_event_from_handle(birth_ref.ref)
                birth_date = birth_event.get_date_object()
 
            if (self.birthdays and birth_date is not None and birth_date.is_valid()):
                year = birth_date.get_year()
                month = birth_date.get_month()
                day = birth_date.get_day()
 
                prob_alive_date = gen.lib.Date(self.year, month, day)
 
                nyears = self.year - year
                # add some things to handle maiden name:
                father_lastname = None # husband, actually
                if self.maiden_name in ['spouse_first', 'spouse_last']: # get husband's last name:
                    if person.get_gender() == gen.lib.Person.FEMALE:
                        family_list = person.get_family_handle_list()
                        if family_list:
                            if self.maiden_name == 'spouse_first':
                                fhandle = family_list[0]
                            else:
                                fhandle = family_list[-1]
                            fam = db.get_family_from_handle(fhandle)
                            father_handle = fam.get_father_handle()
                            mother_handle = fam.get_mother_handle()
                            if mother_handle == person_handle:
                                if father_handle:
                                    father = db.get_person_from_handle(father_handle)
                                    if father:
                                        father_lastname = father.get_primary_name().get_surname()
                short_name = self.get_name(person, father_lastname)
                alive = probably_alive(person, db, prob_alive_date)
 
                if  not self.alive or alive:
                    if nyears == 0:
                        text = _('%(person)s, birth%(relation)s') % {
                            'person' : short_name,
                            'relation' : ""}
                    else:
                        text = (ngettext('%(person)s, %(age)d%(relation)s',
                                          '%(person)s, %(age)d%(relation)s', nyears)
                                 % {'person'   : short_name,
                                    'age'      : nyears,  
                                    'relation' : ""})
                    self.add_day_item(text, month, day)
            if self.anniversaries:
                family_list = person.get_family_handle_list()
                for fhandle in family_list: 
                    fam = db.get_family_from_handle(fhandle)
                    father_handle = fam.get_father_handle()
                    mother_handle = fam.get_mother_handle()
                    if father_handle == person.get_handle():
                        spouse_handle = mother_handle
                    else:
                        continue # with next person if the father is not "person"
                                 # this will keep from duplicating the anniversary
                    if spouse_handle:
                        spouse = db.get_person_from_handle(spouse_handle)
                        if spouse:
                            spouse_name = self.get_name(spouse)
                            short_name = self.get_name(person)
                            # TEMP: this will handle ordered events
                            # GRAMPS 3.0 will have a new mechanism for start/stop events
                            are_married = None
                            for event_ref in fam.get_event_ref_list():
                                event = db.get_event_from_handle(event_ref.ref)
                                et = gen.lib.EventType
                                rt = gen.lib.EventRoleType
                                if event.type in [et.MARRIAGE, 
                                                  et.MARR_ALT] and \
                                (event_ref.get_role() == rt.FAMILY or 
                                event_ref.get_role() == rt.PRIMARY ):
                                    are_married = event
                                elif event.type in [et.DIVORCE, 
                                                    et.ANNULMENT, 
                                                    et.DIV_FILING] and \
                                (event_ref.get_role() == rt.FAMILY or 
                                event_ref.get_role() == rt.PRIMARY ):
                                    are_married = None
                            if are_married is not None:
                                for event_ref in fam.get_event_ref_list():
                                    event = db.get_event_from_handle(event_ref.ref)
                                    event_obj = event.get_date_object()
 
                                    if event_obj.is_valid():
                                        year = event_obj.get_year()
                                        month = event_obj.get_month()
                                        day = event_obj.get_day()
 
                                        prob_alive_date = gen.lib.Date(self.year, month, day)
 
                                        nyears = self.year - year
                                        if nyears == 0:
                                            text = _('%(spouse)s and\n %(person)s, wedding') % {
                                                     'spouse' : spouse_name, 
                                                     'person' : short_name, 
                                                    }
                                        else:
                                            text = (ngettext("%(spouse)s and\n %(person)s, %(nyears)d", 
                                                             "%(spouse)s and\n %(person)s, %(nyears)d", nyears)
                                                    % {'spouse' : spouse_name, 
                                                       'person' : short_name, 
                                                       'nyears' : nyears})
 
                                        alive1 = probably_alive(person, self.database,
                                                    prob_alive_date)
                                        alive2 = probably_alive(spouse, self.database,
                                                    prob_alive_date)
                                        if ((self.alive and alive1 and alive2) or not self.alive):
                                            self.add_day_item(text, month, day)
        self._user.end_progress()
 
#------------------------------------------------------------------------
#
# CalendarOptions
#
#------------------------------------------------------------------------
class CalendarOptions(MenuReportOptions):
    """ Calendar options for graphic calendar """
    def __init__(self, name, dbase):
        self.__db = dbase
        self.__pid = None
        self.__filter = None
        MenuReportOptions.__init__(self, name, dbase)
 
    def add_menu_options(self, menu):
        """ Add the options for the graphical calendar """
        add_option = partial(menu.add_option, _("Report Options"))
        year = NumberOption(_("Year of calendar"), time.localtime()[0], 
                            1000, 3000)
        year.set_help(_("Year of calendar"))
        add_option("year", year)
 
        self.__filter = FilterOption(_("Filter"), 0)
        self.__filter.set_help(
               _("Select filter to restrict people that appear on calendar"))
        add_option("filter", self.__filter)
 
        self.__pid = PersonOption(_("Center Person"))
        self.__pid.set_help(_("The center person for the report"))
        add_option("pid", self.__pid)
        self.__pid.connect('value-changed', self.__update_filters)
 
        self.__update_filters()
 
        # We must figure out the value of the first option before we can
        # create the EnumeratedListOption
        fmt_list = _nd.get_name_format()
        name_format = EnumeratedListOption(_("Name format"), fmt_list[0][0])
        for num, name, fmt_str, act in fmt_list:
            name_format.add_item(num, name)
        name_format.set_help(_("Select the format to display names"))
        add_option("name_format", name_format)
 
        country = EnumeratedListOption(_("Country for holidays"), 0)
        holiday_table = libholiday.HolidayTable()
        countries = holiday_table.get_countries()
        countries.sort()
        if (len(countries) == 0 or 
            (len(countries) > 0 and countries[0] != '')):
            countries.insert(0, '')
        count = 0
        for c in countries:
            country.add_item(count, c)
            count += 1
        country.set_help(_("Select the country to see associated holidays"))
        add_option("country", country)
 
        start_dow = EnumeratedListOption(_("First day of week"), 1)
        for count in range(1, 8):
            # conversion between gramps numbering (sun=1) and iso numbering (mon=1) of weekdays below
            start_dow.add_item((count+5) % 7 + 1, GrampsLocale.long_days[count].capitalize()) 
        start_dow.set_help(_("Select the first day of the week for the calendar"))
        add_option("start_dow", start_dow) 
 
        maiden_name = EnumeratedListOption(_("Birthday surname"), "own")
        maiden_name.add_item("spouse_first", _("Wives use husband's surname (from first family listed)"))
        maiden_name.add_item("spouse_last", _("Wives use husband's surname (from last family listed)"))
        maiden_name.add_item("own", _("Wives use their own surname"))
        maiden_name.set_help(_("Select married women's displayed surname"))
        add_option("maiden_name", maiden_name)
 
        alive = BooleanOption(_("Include only living people"), True)
        alive.set_help(_("Include only living people in the calendar"))
        add_option("alive", alive)
 
        birthdays = BooleanOption(_("Include birthdays"), True)
        birthdays.set_help(_("Include birthdays in the calendar"))
        add_option("birthdays", birthdays)
 
        anniversaries = BooleanOption(_("Include anniversaries"), True)
        anniversaries.set_help(_("Include anniversaries in the calendar"))
        add_option("anniversaries", anniversaries)
 
        category_name = _("Text Options")
        add_option = partial(menu.add_option, _("Text Options"))
 
        text1 = StringOption(_("Text Area 1"), _("My Calendar")) 
        text1.set_help(_("First line of text at bottom of calendar"))
        add_option("text1", text1)
 
        text2 = StringOption(_("Text Area 2"), _("Produced with Gramps"))
        text2.set_help(_("Second line of text at bottom of calendar"))
        add_option("text2", text2)
 
        text3 = StringOption(_("Text Area 3"), "http://gramps-project.org/",)
        text3.set_help(_("Third line of text at bottom of calendar"))
        add_option("text3", text3)
 
    def __update_filters(self):
        """
        Update the filter list based on the selected person
        """
        gid = self.__pid.get_value()
        person = self.__db.get_person_from_gramps_id(gid)
        filter_list = ReportUtils.get_person_filters(person, False)
        self.__filter.set_filters(filter_list)
 
    def make_my_style(self, default_style, name, description, 
                      size=9, font=FONT_SERIF, justified ="left", 
                      color=None, align=PARA_ALIGN_CENTER, 
                      shadow = None, italic=0, bold=0, borders=0, indent=None):
        """ Create paragraph and graphic styles of the same name """
        # Paragraph:
        f = FontStyle()
        f.set_size(size)
        f.set_type_face(font)
        f.set_italic(italic)
        f.set_bold(bold)
        p = ParagraphStyle()
        p.set_font(f)
        p.set_alignment(align)
        p.set_description(description)
        p.set_top_border(borders)
        p.set_left_border(borders)
        p.set_bottom_border(borders)
        p.set_right_border(borders)
        if indent:
            p.set(first_indent=indent)
        if justified == "left":
            p.set_alignment(PARA_ALIGN_LEFT)       
        elif justified == "right":
            p.set_alignment(PARA_ALIGN_RIGHT)       
        elif justified == "center":
            p.set_alignment(PARA_ALIGN_CENTER)       
        default_style.add_paragraph_style(name, p)
        # Graphics:
        g = GraphicsStyle()
        g.set_paragraph_style(name)
        if shadow:
            g.set_shadow(*shadow)
        if color is not None:
            g.set_fill_color(color)
        if not borders:
            g.set_line_width(0)
        default_style.add_draw_style(name, g)
 
    def make_default_style(self, default_style):
        """ Add the styles used in this report """
        self.make_my_style(default_style, "CAL-Title", 
                           _('Title text and background color'), 20, 
                           bold=1, italic=1, 
                           color=(0xEA, 0xEA, 0xEA))
        self.make_my_style(default_style, "CAL-Numbers", 
                           _('Calendar day numbers'), 13, 
                           bold=1)
        self.make_my_style(default_style, "CAL-Text", 
                           _('Daily text display'), 9)
        self.make_my_style(default_style, "CAL-Holiday", 
                           _('Holiday text display'), 9,
                           bold=1, italic=1)
        self.make_my_style(default_style, "CAL-Daynames", 
                           _('Days of the week text'), 12, 
                           italic=1, bold=1, 
                           color = (0xEA, 0xEA, 0xEA))
        self.make_my_style(default_style, "CAL-Text1style", 
                           _('Text at bottom, line 1'), 12)
        self.make_my_style(default_style, "CAL-Text2style", 
                           _('Text at bottom, line 2'), 12)
        self.make_my_style(default_style, "CAL-Text3style", 
                           _('Text at bottom, line 3'), 9)
        self.make_my_style(default_style, "CAL-Border", 
                           _('Borders'), borders=True)