#!-*- coding:utf-8 -*-"

import calendar
import datetime
import pkg_resources
import re
import time

from datetime import datetime, date, timedelta
from decimal import Decimal

from genshi.builder import tag
from genshi.filters import Transformer
from genshi.filters.transform import StreamBuffer
from genshi.template.markup import MarkupTemplate

from trac.core import *
from trac.mimeview.api import Mimeview, IContentConverter, Context
from trac.perm import IPermissionGroupProvider, IPermissionRequestor, PermissionSystem
from trac.ticket import Ticket
from trac.ticket.api import ITicketManipulator
from trac.ticket.api import ITicketChangeListener
from trac.ticket.query import Query
from trac.util.datefmt import to_timestamp, utc, format_date, parse_date
from trac.util.translation import _
from trac.web.api import IRequestHandler
from trac.web.api import ITemplateStreamFilter
from trac.web.chrome import INavigationContributor
from trac.web.chrome import ITemplateProvider
from trac.web.chrome import add_ctxtnav
from trac.web.chrome import add_link 
from trac.web.chrome import add_script
from trac.web.chrome import add_stylesheet
from trac.web.chrome import Chrome
from trac.web.chrome import INavigationContributor

# local imports
from dateutils import *
from dbutils import *
from utils import *

class TracWorkTimePlugin(Component):

    group_providers = ExtensionPoint(IPermissionGroupProvider)
    
    ###### class data
    #date_format = '%B %d, %Y'     # XXX should go to api ?
    date_format = '%Y/%m/%d'
    hours_regex = '(([0-9]+(\.[0-9]+)?)|([0-9]+:[0-5][0-9])) *hours' # for ticket comments: 1.5 hours or 1:30 hours
    singular_hour_regex = r'((^)|(\s))1 *hour((\W)|($))' # for singular hours: 1 hour

    fields = [dict(name='id', label='Ticket'), #note that ticket_time id is clobbered by ticket id
              dict(name='hours_worked', label='Hours Worked'),
              dict(name='worker', label='Worker'),
              dict(name='submitter', label='Work submitted by'),
              dict(name='time_started', label='Work done on'),
              dict(name='time_submitted', label='Work recorded on')]

    ###### methods and attributes for trac Interfaces
    implements(IRequestHandler,
               INavigationContributor,
               ITemplateProvider, IPermissionRequestor,
               ITemplateStreamFilter
               )


    ### methods for IPermissionRequestor
    def get_permission_actions(self):
        def to_list(key, prefix_perm):
            value = self.env.config.get('worktime', key, '')
            if value != '':
                value = [prefix_perm + x.strip().upper() for x in value.split(',')]
                return value
            return []
        
        report_view_groups = to_list('report_view_groups', 'WORKTIME_REPORT_VIEW_')
        modify_groups = to_list('modify_groups', 'WORKTIME_MODIFY_')
        
        return report_view_groups + modify_groups + ['WORKTIME_BURNDOWN_VIEW',
                'WORKTIME_MODIFY_SELF', 'WORKTIME_IMPORT_EXECUTE',
                ('WORKTIME_MODIFY', modify_groups + ['WORKTIME_MODIFY_SELF']),
                ('WORKTIME_REPORT_VIEW', report_view_groups),
                ('WORKTIME_ADMIN', ['WORKTIME_BURNDOWN_VIEW', 'WORKTIME_MODIFY', 'WORKTIME_REPORT_VIEW', 'WORKTIME_IMPORT_EXECUTE'])]

    ### methods for IRequestHandler
    def match_request(self, req):
        path = req.path_info.rstrip('/')
        if not path.startswith('/worktime'):
            return False
        if path.startswith('/worktime/report') or path.startswith('/worktime/burndown') \
            or path.startswith('/worktime/importer'):
            return False
        return True

    def process_request(self, req):
        path = req.path_info.rstrip('/')

        if path == '/worktime':
            self._add_ctxnav(req)
            return self._process_timeline(req)
        
        if path.startswith('/worktime/update'):
            return self._process_update(req)
        
    def _add_ctxnav(self, req):
        add_ctxtnav(req, u'作業時間入力')
        
        from report import TracWorkTimeReportModule
        if self.env.is_component_enabled(TracWorkTimeReportModule):
            add_ctxtnav(req, u'レポート', href=req.href.worktime('report'))
            
        from importer import TracWorkTimeImportModule
        if self.env.is_component_enabled(TracWorkTimeImportModule):
            add_ctxtnav(req, u'作業時間インポート', href=req.href.worktime('importer'))
        
        from burndown import TracWorkTimeBurndownChartModule
        if self.env.is_component_enabled(TracWorkTimeBurndownChartModule):
            add_ctxtnav(req, u'Burndown Chart', href=req.href.worktime('burndown/daily'))
          
    def _process_update(self, req):
        assert_modify_permission(self.env, req)
        
        data = {}
        try:
            hours = req.args.get('update_value').strip()
            if hours == '':
                hours = 0
            hours = str(Decimal(hours))
            
            ticket_id, date = req.args.get('element_id').split(':')
            worker = req.args.get('selected_user')
            submitter = req.authname
            
            update_worktime(self, ticket_id, worker, submitter, date, hours)
            new_value = hours
            
            data['worktime_sum'] = get_worktime_sum_by_ticket(self.env, ticket_id)
            data['ticket_id'] = ticket_id
            data['date'] = date
            
            # formatting...
            if new_value == '0':
                new_value = ''
            elif '.' not in new_value:
                new_value += '.0' 
            
        except:
            import traceback
            traceback.print_exc()
            new_value = 'ERROR'
        
        data['response'] = new_value
        return 'worktime_response.html', data, None
    
    def _process_timeline(self, req):
        assert_modify_permission(self.env, req)
     
        selected_user = req.args.get('selected_user', req.authname)
        show_my_ticket = req.args.get('show_my_ticket')
        selected_closed_month = req.args.get('selected_closed_month',
                                             self.env.config.get('worktime', 'show_closed_month_ticket', '1'))
        selected_created_month = req.args.get('selected_created_month',
                                             self.env.config.get('worktime', 'show_created_month', '1'))
        show_cc_ticket = req.args.get('show_cc_ticket',
                                      self.env.config.get('worktime', 'show_cc_ticket', 'on'))
        
        show_save_buttons = self.env.config.get('worktime', 'show_save_buttons', 'false')
        
        #print show_cc_ticket
        #checkboxが非チェックの場合は、hiddenで埋め込んだoffのみが送信されるためoffとそれ以外で比較する
        if show_cc_ticket == 'off':
            show_cc_ticket = False
        else:
            show_cc_ticket = True
        
        group_fields = self._get_group_fileds(req)
        
        cday = handle_current_date(req)
        next =  next_monthtop(cday, 1)
        prev =  prev_monthtop(cday, 1)

        first_date = cday.replace(day=1)
        days_term = (first_date.__add__(timedelta(40)).replace(day=1) - first_date).days
        
        tmp = first_date
        
        date_map = {}
        
        for day in range(days_term):
            datestring = format_date(parse_date(tmp.isoformat()), format='%Y-%m-%d')
            date_map[day] = datestring
            tmp = tmp.__add__(timedelta(days=1))
            
        if int(selected_closed_month) > -1:
            base_day = cday
            prev_month = int(selected_closed_month)
        else:
            base_day = None
            prev_month = 0
            
        if int(selected_closed_month) == -2:
            hide_closed_ticket = True
        else:
            hide_closed_ticket = False
        
        tickets, date_sum, ticket_sum = get_tickets(self.env, req, selected_user, group_fields, base_day=base_day,
                              prev_month=prev_month, selected_created_month=int(selected_created_month),
                              show_cc_ticket=show_cc_ticket, hide_closed_ticket=hide_closed_ticket)
        
        self.env.log.debug(tickets)
        
        #表示月のworkerの合計時間を算出
        sum_all = reduce(lambda x,y: x+y, ticket_sum.values(), 0)
        
        #他workerも含めた総合計時間を算出
        sum_workers_all = reduce(lambda x,y: x+y, [x['worktime_sum'] for x in tickets], 0)
        
        #見積り合計時間を算出
        sum_estimatedhours = reduce(lambda x,y: x+y, [x['estimatedhours'] for x in tickets], 0)
        
        ticket_fields, ticket_field_map = get_ticket_fileds(self.env)
            
        #画面表示データの準備
        data = {}
        
        self._get_users(req, data)
        self._get_holidays(req, data, cday)
        
        data.update({'today': date.today(), 'current': cday, 'prev': prev, 'next': next})
        data.update({'selected_closed_month': selected_closed_month,
                     'selected_created_month': selected_created_month,
                     'show_cc_ticket': show_cc_ticket,
                     'group_fields': group_fields})
        data.update({'show_save_buttons': show_save_buttons})
        data.update({'tickets': tickets})
        data.update({'sum_estimatedhours': sum_estimatedhours})
        data.update({'sum_workers_all': sum_workers_all, 'sum_all': sum_all, 'date_sum': date_sum, 'ticket_sum': ticket_sum})
        data.update({'selected_user': selected_user})
        data.update({'first_date': first_date, 'days_term': days_term})
        data.update({'parse_date': parse_date, 'format_date': format_date, 'calendar': calendar})
        data.update({'date_map': date_map})
        data.update({'ticket_fields': ticket_fields, 'ticket_field_map': ticket_field_map})
        
        for x in req.args:
            if x.startswith('group_field'):
                data.update({x:req.args.get(x)})
                
        return 'worktime.html', data, None
    
    def _get_holidays(self, req, data, cday):
        holidays = get_holidays(self, req, cday)
        data.update({'holidays':holidays})
    
    def _get_users(self, req, data):
        users = []
        if ('WORKTIME_MODIFY') in req.perm: 
            users = get_all_users(self.env)
        elif has_prefix_permission(self.env, req, 'WORKTIME_MODIFY_'):
            users = get_accessable_users(self.group_providers, self.env,
                                         req, 'WORKTIME_MODIFY_')
        elif 'WORKTIME_MODIFY_SELF' in req.perm: 
            users = [req.authname]
        
        data.update({'users' : users})

    def _get_group_fileds(self, req):
        group_fields_keys = [x for x in req.args if x.startswith('group_field')]
        group_fields_keys.sort()
        group_fields = [req.args.get(x) for x in group_fields_keys]
        group_fields = [x for x in group_fields if x != '']
        if len(group_fields) == 0:
            default_groups = self.env.config.get('worktime', 'group_fileds', 'component, type')
            group_fields = [x.strip() for x in default_groups.split(',')]
            for index, group_field in enumerate(group_fields):
                req.args['group_field%d' % (index + 1)] = group_field
        group_fields.append('summary')
        return group_fields
        
    ### methods for INavigationContributor
    def get_active_navigation_item(self, req):
        return 'worktime'

    def get_navigation_items(self, req):
        actions = PermissionSystem(self.env).get_user_permissions(req.authname)
        for action in actions:
            if action.startswith('WORKTIME'):
                yield ('mainnav', 'worktime',
                       tag.a(u'Work Time Management', href=req.href.worktime(), accesskey='H'))

    ### methods for ITemplateProvider
    def get_htdocs_dirs(self):
        return [('worktime',
                 pkg_resources.resource_filename(__name__, 'htdocs'))]

    def get_templates_dirs(self):
        from pkg_resources import resource_filename
        return [resource_filename(__name__, 'templates')]

    # ITemplateStreamFilter methods
    def filter_stream(self, req, method, filename, stream, data):
        match = re.match(r'/ticket/([0-9]+)$', req.path_info)
        if not match:
            return stream
        
        ticket_id = match.group(1)
            
        add_stylesheet(req, 'worktime/css/worktime.css')
        add_stylesheet(req, 'worktime/treetable/stylesheets/jquery.treeTable.css')
        
        template = MarkupTemplate(u"""
<div xmlns:py="http://genshi.edgewall.org/">
  <h2>Work Time Info</h2>
  <div id="worktime">
    <p>合計作業時間: %s 時間</p>
  </div>
</div>""" % get_worktime_sum_by_ticket(self.env, ticket_id))
        
        WORKTIME_PATH = '//div[@id="ticket"]'
        stream |= Transformer(WORKTIME_PATH).after(template.generate())
            
        return stream


