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

import csv
import pkg_resources

from decimal import Decimal as D
from datetime import datetime, date, timedelta
from StringIO import StringIO

from trac.core import *
from trac.mimeview.api import Mimeview, IContentConverter, Context
from trac.perm import IPermissionGroupProvider, PermissionSystem, PermissionError
from trac.util.datefmt import to_timestamp, utc, format_date, parse_date
from trac.util.text import to_unicode, unicode_urlencode
from trac.util.translation import _
from trac.web.api import IRequestHandler, RequestDone
from trac.web.api import ITemplateStreamFilter
from trac.web.chrome import ITemplateProvider
from trac.web.chrome import add_ctxtnav, add_link, add_script, add_stylesheet

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

REPORT_GROUP_FIELD = ['type', 'component', 'milestone', 'version']
TWOPLACES = D(10) ** -2 

class TracWorkTimeReportModule(Component):
    implements(IRequestHandler)
    
    group_providers = ExtensionPoint(IPermissionGroupProvider)

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

    def process_request(self, req):
        return self._process_report(req)

    def _send_convert_content(self, req, key, group, workers, table):
        filename = 'worktime_report.%s' % key
        if key == 'csv':
            return self._export_csv(req, mimetype='text/csv', filename=filename,
                                    group=group, workers=workers, table=table)
        elif key == 'tab':
            return self._export_csv(req, '\t',
                                    mimetype='text/tab-separated-values',
                                    filename=filename,
                                    group=group, workers=workers, table=table)

    def _export_csv(self, req, sep=',', mimetype='text/plain',
                    filename='worktime_report.csv', group='id', workers=[], table=[]):
        out = StringIO()
        writer = csv.writer(out, delimiter=sep)

        if group == 'id':
            self._write_table_for_ticket(writer, workers, table)
        else:
            self._write_table(writer, workers, table, group)
        
        data = out.getvalue()
        req.send_response(200)
        req.send_header('Content-Type', mimetype + ';charset=utf-8')
        req.send_header('Content-Length', len(data))
        if filename:
            req.send_header('Content-Disposition', 'filename=' + filename)
        req.end_headers()
        req.write(data)
        raise RequestDone
    
    def _write_table_for_ticket(self, writer, workers, table):
        #ヘッダ行
        if len(table) > 0:
            row = ['id', 'type', 'component', 'summary']
            row.extend(workers)
            writer.writerow(row)
        #Body
        for t in table:
            row = [t['value'], enc(t['type']), enc(t['component']), enc(t['summary'])]
            for worker in workers:
                hours = t.get('__worker__' + worker, 0)
                row.append(hours)
            writer.writerow(row)

    def _write_table(self, writer, workers, table, group):
        #ヘッダ行
        if len(table) > 0:
            row = [group]
            row.extend(workers)
            writer.writerow(row)
        #Body
        for t in table:
            row = [enc(t['value'])]
            for worker in workers:
                hours = t.get('__worker__' + worker, 0)
                row.append(hours)
            writer.writerow(row)

    def _process_report(self, req):
        data = {}
        
        selected_ticket_field = req.args.get('selected_ticket_field', 'id')
        selected_unit = req.args.get('selected_unit', 'hours')
        
        target_workers = self._get_target_workers(req)
        
        cday = handle_current_date(req)
            
        next =  next_monthtop(cday, 1)
        prev =  prev_monthtop(cday, 1)
        table = self._get_worktime_table_by_month(req, cday,
                                                  group=selected_ticket_field,
                                                  target_workers=target_workers)
        
        workers = [x['worker'] for x in table]
        workers = list(set(workers))
        
        table = self._to_table(req, table)

        #calc sum
        sum_by_workers, all_sum = self._calc_sum(req, table, workers)
        table = self._calc_unit(table, workers, sum_by_workers, selected_unit)
        
        #export csv or tab
        format = req.args.get('format')
        if format:
            self._send_convert_content(req, format, selected_ticket_field, workers, table)
        
        #return html
        self._add_ctxnavs(req)
        self._add_alternate_links(req, {'selected_ticket_field': selected_ticket_field})
        
        ticket_fields, ticket_field_map = get_ticket_fileds(self.env)
        
        data.update({'workers': workers,
                     'table': table,
                     'current': cday, 'prev': prev, 'next': next,
                     'ticket_fields': ticket_fields, 'selected_ticket_field': selected_ticket_field,
                     'selected_unit': selected_unit,
                     'ticket_field_map': ticket_field_map,
                     'sum_by_workers': sum_by_workers, 'all_sum': all_sum
                    })
        
        return 'worktime_report.html', data, None
    
    def _get_target_workers(self, req):
        if 'WORKTIME_REPORT_VIEW' in req.perm:
            #空配列を返すと全workerを対象とする
            return []
        
        target_workers = get_accessable_users(self.group_providers, self.env,
                                              req, 'WORKTIME_REPORT_VIEW_')
        return target_workers
        
    def _calc_sum(self, req, table, workers):
        results = {}
        for worker in workers:
            hours = [x['__worker__' + worker] for x in table if x.get('__worker__' + worker) != None]
            results[worker] = reduce(lambda x,y: x+y, hours, 0)
        return results, reduce(lambda x,y: x+y, results.values(), 0)
    
    def _calc_unit(self, table, workers, sum_by_workers, selected_unit):
        if selected_unit != 'ratio':
            return table
        
        for t in table:
            row_sum = D(0)
            for worker in workers:
                sum = sum_by_workers.get(worker)
                hours = t.get('__worker__' + worker)
                if hours:
                    ratio = (hours/sum)
                    row_sum += ratio
                    t.update({'__worker__' + worker: ratio.quantize(TWOPLACES)})
            t.update({'sum': row_sum.quantize(TWOPLACES)})
                              
        return table
    
    def _to_table(self, req, row_table):
        table = {}
        for t in row_table:
            row = table.get(t['value'])
            if not row:
                t.update({
                          '__worker__' + t['worker']: t['hours'],
                          'sum': t['hours']
                          })
                table[t['value']] = t
            else:
                #他の人の作業時間をこの行に追加する
                row['__worker__' + t['worker']] = t['hours']
                
                #合計時間に作業時間を足して算出する
                row['sum'] += t['hours']
                
        values = table.values()
        values.sort(lambda x,y: cmp(x['value'], y['value']))
        return values
    
    def _get_worktime_table_by_month(self, req, cday, group='id', target_workers=[]):
        u"""指定月の作業時間テーブルを取得して返す。
        引数groupに指定したフィールド名でグルーピングして、作業時間を集計する。
        """
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        
        month = format_date(parse_date(cday.isoformat()), '%Y-%m')
        
        sql = self._create_sql(req, group, target_workers)
        self.env.log.debug(sql, "'%s'" % month)
        cursor.execute(sql, (list(target_workers) + [month]))
        results = []
        
        if group == 'id':
            for date, worker, ticket_id, type, component, summary, hours in cursor:
                results.append({
                                'date': date,
                                'worker': worker,
                                'name': 'id',
                                'value': ticket_id,
                                'type': type,
                                'component': component,
                                'summary': summary,
                                'hours': D(str(float(hours)))
                                })
        else:
            for date, worker, name, value, hours in cursor:
                results.append({
                                'date': date,
                                'worker': worker,
                                'name': name,
                                'value': value,
                                'hours': D(str(float(hours)))
                                })
        return results
    
    def _create_sql(self, req, group, target_workers=[]):
        
        def column(for_group=False):
            if is_custom_field(group):
                return 'tc1.name, tc1.value,'
            else:
                if group == 'id':
                    return 'tw.ticket, t.type, t.component, t.summary,'
                elif group in REPORT_GROUP_FIELD:
                    if for_group:
                        return "t.%s," % (group)
                    else:
                        return "'%s', t.%s," % (group, group)
        
        def left_outer_join():
            join = ''
            if is_custom_field(group):
                join = """
                LEFT OUTER JOIN ticket_custom tc1
                ON tc1.ticket = t.id AND tc1.name = '%s'
                """ % group
            return join
        
        def group_by():
            group_by = ''
            if is_custom_field(group):
                group_by = """
                GROUP BY substr(tw.date, 1, 7), tc1.name, tw.worker, %s
                ORDER BY tc1.name ASC, tw.worker
                """ % column(True)[:-1]
            else:
                group_by = """
                GROUP BY substr(tw.date, 1, 7), t.%s, tw.worker, %s
                ORDER BY t.%s ASC, tw.worker
                """ % (group, column(True)[:-1], group)
            return group_by
        
        def where_workers():
            if len(target_workers) > 0:
                where_workers = """
                tw.worker IN (%s) AND
                """ % ','.join(['%s']*len(target_workers))
                return where_workers
            return ''
                
        sql = """
        SELECT 
            substr(tw.date, 1, 7) AS date,
            tw.worker, %s sum(tw.hours_worked)
        FROM ticket_worktime tw, ticket t
        %s
        WHERE
            %s
            substr(tw.date, 1, 7) = %s AND t.id = tw.ticket
        %s
        """ % (column(), left_outer_join(),
               where_workers(), '%s', group_by())
        
        return sql
        
    def _add_ctxnavs(self, req):
        add_ctxtnav(req, u'作業時間入力', href=req.href.worktime())
        add_ctxtnav(req, u'レポート')
        from burndown import TracWorkTimeBurndownChartModule
        if self.env.is_component_enabled(TracWorkTimeBurndownChartModule):
            add_ctxtnav(req, u'Burndown Chart', href=req.href.worktime('burndown/daily'))
    
    def _add_alternate_links(self, req, args={}):
        params = args.copy()
        if 'year' in req.args:
            params['year'] = req.args['year']
        if 'month' in req.args:
            params['month'] = req.args['month']
        if 'selected_unit' in req.args:
            params['selected_unit'] = req.args['selected_unit']
        href = ''
        if params:
            href = '&' + unicode_urlencode(params)
        add_link(req, 'alternate', '?format=csv' + href,
                 _('Comma-delimited Text'), 'text/plain')
        add_link(req, 'alternate', '?format=tab' + href,
                 _('Tab-delimited Text'), 'text/plain')
        
def is_custom_field(field):
    return field not in TICKET_COLUMNS
