#!/usr/bin/env python

'''
calendar.py
A calendar generator plugin for Inkscape, but also can be used as a standalone
command line application.

Copyright (C) 2008 Aurelio A. Heckert <aurium(a)gmail.com>

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
'''

__version__ = "0.2"

import inkex, simplestyle, re, calendar
from datetime import *

class SVGCalendar (inkex.Effect):

    def __init__(self):
        inkex.Effect.__init__(self)
        self.OptionParser.add_option("--tab",
          action="store", type="string",
          dest="tab")
        self.OptionParser.add_option("--month",
          action="store", type="int",
          dest="month", default=0,
          help="Month to be generated. If 0, then the entry year will be generated.")
        self.OptionParser.add_option("--year",
          action="store", type="int",
          dest="year", default=0,
          help="Year to be generated. If 0, then the current year will be generated.")
        self.OptionParser.add_option("--fill-empty-day-boxes",
          action="store", type="inkbool",
          dest="fill_edb", default=True,
          help="Fill empty day boxes with next month days.")
        self.OptionParser.add_option("--start-day",
          action="store", type="string",
          dest="start_day", default="sun",
          help='Week start day. ("sun" or "mon")')
        self.OptionParser.add_option("--weekend",
          action="store", type="string",
          dest="weekend", default="sat+sun",
          help='Define the weekend days. ("sat+sun" or "sat" or "sun")')
        self.OptionParser.add_option("--auto-organize",
          action="store", type="inkbool",
          dest="auto_organize", default=True,
          help='Automatically set the size and positions.')
        self.OptionParser.add_option("--months-per-line",
          action="store", type="int",
          dest="months_per_line", default=3,
          help='Number of months side by side.')
        self.OptionParser.add_option("--month-width",
          action="store", type="string",
          dest="month_width", default="6cm",
          help='The width of the month days box.')
        self.OptionParser.add_option("--month-margin",
          action="store", type="string",
          dest="month_margin", default="1cm",
          help='The space between the month boxes.')
        self.OptionParser.add_option("--color-year",
          action="store", type="string",
          dest="color_year", default="#888",
          help='Color for the year header.')
        self.OptionParser.add_option("--color-month",
          action="store", type="string",
          dest="color_month", default="#666",
          help='Color for the month name header.')
        self.OptionParser.add_option("--color-day-name",
          action="store", type="string",
          dest="color_day_name", default="#999",
          help='Color for the week day names header.')
        self.OptionParser.add_option("--color-day",
          action="store", type="string",
          dest="color_day", default="#000",
          help='Color for the common day box.')
        self.OptionParser.add_option("--color-weekend",
          action="store", type="string",
          dest="color_weekend", default="#777",
          help='Color for the weekend days.')
        self.OptionParser.add_option("--color-nmd",
          action="store", type="string",
          dest="color_nmd", default="#BBB",
          help='Color for the next month day, in enpty day boxes.')
        self.OptionParser.add_option("--month-names",
          action="store", type="string",
          dest="month_names", default='January February March April May June '+\
                              'July August September October November December',
          help='The month names for localization.')
        self.OptionParser.add_option("--day-names",
          action="store", type="string",
          dest="day_names", default='Sun Mon Tue Wed Thu Fri Sat',
          help='The week day names for localization.')
        self.OptionParser.add_option("--encoding",
          action="store", type="string",
          dest="input_encode", default='utf-8',
          help='The input encoding of the names.')

    def validate_options(self):
        #inkex.errormsg( self.options.input_encode )
        # Convert string names lists in real lists:
        m = re.match( '\s*(.*[^\s])\s*', self.options.month_names )
        self.options.month_names = re.split( '\s+', m.group(1) )
        m = re.match( '\s*(.*[^\s])\s*', self.options.day_names )
        self.options.day_names = re.split( '\s+', m.group(1) )
        # Validate names lists:
        if len(self.options.month_names) != 12:
          inkex.errormsg('The month name list "'+
                         str(self.options.month_names)+
                         '" is invalid. Using default.')
          self.options.month_names = ['January','February','March',
                                      'April',  'May',     'June',
                                      'July',   'August',  'September',
                                      'October','November','December']
        if len(self.options.day_names) != 7:
          inkex.errormsg('The day name list "'+
                         str(self.options.day_names)+
                         '" is invalid. Using default.')
          self.options.day_names = ['Sun','Mon','Tue','Wed','Thu','Fri','Sat']
        # Convert year 0 to current year:
        if self.options.year == 0: self.options.year = datetime.today().year
        # Year 1 starts it's week at monday, obligatorily
        if self.options.year == 1: self.options.start_day = 'mon'
        # Set the calendar start day:
        if self.options.start_day=='sun':
          calendar.setfirstweekday(6)
        else:
          calendar.setfirstweekday(0)
        # Convert string numbers with unit to user space float numbers:
        self.options.month_width  = inkex.unittouu( self.options.month_width )
        self.options.month_margin = inkex.unittouu( self.options.month_margin )

    # initial values:
    month_x_pos = 0
    month_y_pos = 0

    def calculate_size_and_positions(self):
        #month_margin month_width months_per_line auto_organize
        self.doc_w = inkex.unittouu(self.document.getroot().get('width'))
        self.doc_h = inkex.unittouu(self.document.getroot().get('height'))
        if self.options.auto_organize:
          if self.doc_h > self.doc_w:
            self.months_per_line = 3
          else:
            self.months_per_line = 4
        else:
          self.months_per_line = self.options.months_per_line
        #self.month_w = self.doc_w / self.months_per_line
        if self.options.auto_organize:
          self.month_w = (self.doc_w * 0.8) / self.months_per_line
          self.month_margin = self.month_w / 10
        else:
          self.month_w = self.options.month_width
          self.month_margin = self.options.month_margin
        self.day_w = self.month_w / 7
        self.day_h = self.month_w / 9
        self.month_h = self.day_w * 7
        if self.options.month == 0:
          self.year_margin = (( self.doc_w + self.day_w -
                               (self.month_w*self.months_per_line) -
                               (self.month_margin*(self.months_per_line-1))
                             ) / 2 ) #- self.month_margin
        else:
          self.year_margin = ( self.doc_w - self.month_w ) / 2
        self.style_day = {
          'font-size': str( self.day_w / 2 ),
          'font-family': 'arial',
          'text-anchor': 'middle',
          'text-align': 'center',
          'fill': self.options.color_day
          }
        self.style_weekend = self.style_day.copy()
        self.style_weekend['fill'] = self.options.color_weekend
        self.style_nmd = self.style_day.copy()
        self.style_nmd['fill'] = self.options.color_nmd
        self.style_month = self.style_day.copy()
        self.style_month['fill'] = self.options.color_month
        self.style_month['font-size'] = str( self.day_w / 1.5 )
        self.style_month['font-weight'] = 'bold'
        self.style_day_name = self.style_day.copy()
        self.style_day_name['fill'] = self.options.color_day_name
        self.style_day_name['font-size'] = str( self.day_w / 3 )
        self.style_year = self.style_day.copy()
        self.style_year['fill'] = self.options.color_year
        self.style_year['font-size'] = str( self.day_w * 2 )
        self.style_year['font-weight'] = 'bold'

    def is_weekend(self, pos):
        # weekend values: "sat+sun" or "sat" or "sun"
        if self.options.start_day=='sun':
          if self.options.weekend=='sat+sun' and pos==0: return True
          if self.options.weekend=='sat+sun' and pos==6: return True
          if self.options.weekend=='sat' and pos==6: return True
          if self.options.weekend=='sun' and pos==0: return True
        else:
          if self.options.weekend=='sat+sun' and pos==5: return True
          if self.options.weekend=='sat+sun' and pos==6: return True
          if self.options.weekend=='sat' and pos==5: return True
          if self.options.weekend=='sun' and pos==6: return True
        return False

    def in_line_month(self, cal):
        cal2 = []
        for week in cal:
          for day in week:
            if day != 0:
              cal2.append(day)
        return cal2

    def write_month_header(self, g, m):
        txt_atts = {'style': simplestyle.formatStyle(self.style_month),
                    'x': str( (self.month_w - self.day_w) / 2 ),
                    'y': str( self.day_h / 5 ) }
        try:
          inkex.etree.SubElement(g, 'text', txt_atts).text = unicode(self.options.month_names[m-1], self.options.input_encode)
        except:
          inkex.errormsg('You must select your correct system encode.')
          exit(1)
        gw = inkex.etree.SubElement(g, 'g')
        week_x = 0
        if self.options.start_day=='sun':
          for wday in self.options.day_names:
            txt_atts = {'style': simplestyle.formatStyle(self.style_day_name),
                        'x': str( self.day_w * week_x ),
                        'y': str( self.day_h ) }
            try:
              inkex.etree.SubElement(gw, 'text', txt_atts).text = unicode(wday, self.options.input_encode)
            except:
              inkex.errormsg('You must select your correct system encode.')
              exit(1)
            week_x += 1
        else:
          w2 = self.options.day_names[1:]
          w2.append(self.options.day_names[0])
          for wday in w2:
            txt_atts = {'style': simplestyle.formatStyle(self.style_day_name),
                        'x': str( self.day_w * week_x ),
                        'y': str( self.day_h ) }
            try:
              inkex.etree.SubElement(gw, 'text', txt_atts).text = unicode(wday, self.options.input_encode)
            except:
              inkex.errormsg('You must select your correct system encode.')
              exit(1)
            week_x += 1

    def create_month(self, m):
        txt_atts = {
          'transform': 'translate('+str(self.year_margin +
                                       (self.month_w + self.month_margin) *
                                        self.month_x_pos) +
                                ','+str((self.day_h * 4) +
                                       (self.month_h * self.month_y_pos))+')',
          'id': 'month_'+str(m)+'_'+str(self.options.year) }
        g = inkex.etree.SubElement(self.year_g, 'g', txt_atts)
        self.write_month_header(g, m)
        gdays = inkex.etree.SubElement(g, 'g')
        cal = calendar.monthcalendar(self.options.year,m)
        if m == 1:
          if self.options.year > 1:
            before_month = \
              self.in_line_month( calendar.monthcalendar(self.options.year-1, 12) )
        else:
          before_month = \
            self.in_line_month( calendar.monthcalendar(self.options.year, m-1) )
        if m == 12:
          next_month = \
            self.in_line_month( calendar.monthcalendar(self.options.year+1, 1) )
        else:
          next_month = \
            self.in_line_month( calendar.monthcalendar(self.options.year, m+1) )
        if len(cal) < 6: # add a line after the last week
          cal.append([0,0,0,0,0,0,0])
        if len(cal) < 6: # add a line before the first week (Feb 2009)
          cal.reverse()
          cal.append([0,0,0,0,0,0,0])
          cal.reverse()
        # How mutch before month days will be showed:
        bmd = cal[0].count(0) + cal[1].count(0)
        before = True
        week_y = 0
        for week in cal:
          week_x = 0
          for day in week:
            style = self.style_day
            if self.is_weekend(week_x): style = self.style_weekend
            if day == 0: style = self.style_nmd
            txt_atts = {'style': simplestyle.formatStyle(style),
                        'x': str( self.day_w * week_x ),
                        'y': str( self.day_h * (week_y+2) ) }
            if day==0 and not self.options.fill_edb:
              pass # draw nothing
            elif day==0:
              if before:
                inkex.etree.SubElement(gdays, 'text', txt_atts).text = str( before_month[-bmd] )
                bmd -= 1
              else:
                inkex.etree.SubElement(gdays, 'text', txt_atts).text = str( next_month[bmd] )
                bmd += 1
            else:
              inkex.etree.SubElement(gdays, 'text', txt_atts).text = str(day)
              before = False
            week_x += 1
          week_y += 1
        self.month_x_pos += 1
        if self.month_x_pos >= self.months_per_line:
          self.month_x_pos = 0
          self.month_y_pos += 1

    def effect(self):
        self.validate_options()
        self.calculate_size_and_positions()
        parent = self.document.getroot()
        txt_atts = {
          'id': 'year_'+str(self.options.year) }
        self.year_g = inkex.etree.SubElement(parent, 'g', txt_atts)
        txt_atts = {'style': simplestyle.formatStyle(self.style_year),
                    'x': str( self.doc_w / 2 ),
                    'y': str( self.day_w * 1.5 ) }
        inkex.etree.SubElement(self.year_g, 'text', txt_atts).text = str(self.options.year)
        if self.options.month == 0:
          for m in range(1,13):
            self.create_month(m)
        else:
          self.create_month(self.options.month)


if __name__ == '__main__':   #pragma: no cover
    e = SVGCalendar()
    e.affect()
