# -*- coding: utf-8 -*-

import pkg_resources
import re

from genshi.builder import tag

from trac.core import *
from trac.config import Option
from trac.perm import IPermissionGroupProvider
from trac.util import TracError
from trac.util.datefmt import format_datetime, format_date, format_time
from trac.util.html import Markup
from trac.util.text import to_unicode
from trac.util.translation import _
from trac.mimeview.api import Mimeview, get_mimetype, Context, WikiTextRenderer
from trac.resource import ResourceNotFound
from trac.ticket.report import ReportModule
from trac.web.api import IRequestHandler
from trac.web.chrome import add_link, add_script, add_stylesheet, ITemplateProvider
from trac.wiki.api import IWikiMacroProvider
from trac.wiki.model import WikiPage

from dateutils import *
from report import ReportRenderer
from utils import *

class WorkTimeReportMacro(Component):
    implements(IWikiMacroProvider, ITemplateProvider)
    
    group_providers = ExtensionPoint(IPermissionGroupProvider)

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

    def get_templates_dirs(self):
        return [pkg_resources.resource_filename(__name__, 'templates')]
    
    # IWikiMacroProvider methods
    def get_macros(self):
        yield 'WorkTimeReport'

    def get_macro_description(self, name):
        return u'''
以下のようにWikiにマクロを記載することで、作業時間のレポートを表示します。
また、オプションを設定することで、レポートの表からグラフを生成することもできます。
{{{
[[WorkTimeReport(args1,args2,...)]]
}}}

args:
 * workers: 表示するユーザIDを指定します。コロンに続けて、カンマ区切りで指定します。デフォルトでは全ユーザ(ただし、ログインユーザに権限によっては絞られます)となります。
 * showSummaryChart: 表示する全ユーザの作業時間を集計グループ単位に円グラフで出力するかどうか指定します。未指定の場合は、trueとなります。
   * true[[BR]]
     円グラフを表示します。
   * false[[BR]]
     円グラフを表示しません。
 * cutoffDay: 月単位の集計を行う場合の、締め日を指定します。指定可能な値は、1〜31です。月末としたい場合は、[last]と指定します。
 * monthlyReportCount: 月単位の集計を行う場合に、表示する月数を指定します。例えば、3を指定することで直近3ヶ月の集計結果を表示することができます。デフォルトでは1となります。
 * fromDay: 集計開始日を指定します。指定可能な値は、YYYY-mm-DD形式です。
 * toDay: 集計終了日を指定します。指定可能な値は、YYYY-mm-DD形式です。
 * title: タイトル文字列を指定することができます。コロンに続けてタイトル文字列を指定します。
 * groupField: 集計に利用するチケットのフィールドを指定します。未指定の場合は、id(チケットID)となります。
 * condition: 集計に利用する条件を指定します。コロンに続けて、条件を指定します。[[BR]]
  例えば、typeがtaskのみのチケットを条件としたい場合は、type=taskと指定します。[[BR]]
  複数の条件を指定したい場合は、&で区切ります。
 * unit: 作業時間の集計値の形式を指定します。コロンに続けて、以下の記載が可能です。
   * hours: 時間
   * ratio: 割合
 * graph: グラフも合わせて表示する場合に指定します。コロンに続けて、以下の記載が可能です。
   * lines[[BR]]
     折れ線グラフを表示します。
   * bars[[BR]]
     棒グラフを表示します。
 * stack: グラフ表示時に、スタック(積み上げ)グラフとするかどうか指定します。コロンに続けて、以下の記載が可能です。
   * true[[BR]]
     スタック(積み上げ)グラフとして表示します。
   * false[[BR]]
     スタック(積み上げ)グラフとして表示しません。
 * legendLoc: グラフの凡例の表示位置を指定します。コロンに続けて、方角を示す nw, n, ne, e, se, s, sw, w のいずれかを指定します。
 * legendXOffset: グラフの凡例位置の横方向のオフセットを指定します。未指定の場合は12となります。
 * legendYOffset: グラフの凡例位置の縦方向のオフセットを指定します。未指定の場合は12となります。
 * width: グラフの幅をpx単位で指定します。未指定の場合は536pxとなります。 
 * height: グラフの高さをpx単位で指定します。未指定の場合は300pxとなります。
 * table: テーブルを出力を制御できます。コロンに続けて、以下の記載が可能です。
   * hide[[BR]]
     テーブルを表示しません。グラフのみを表示する場合に使用します。
 * async: 非同期でレポート、グラフを描画するかどうか指定します。コロンに続けて、以下の記載が可能です。未指定の場合は、trueとなります。
   * true[[BR]]
     非同期で描画します。
   * false[[BR]]
     非同期で描画しません。

例:
 * 2009-11-01〜2009-11-30のデータを表示します。
{{{
[[WorkTimeReport(fromDay:2009-11-01,toDay:2009-11-30)]]
}}}
 * 2009-11-01〜2009-11-30のデータを表示します。グラフ化もします。
{{{
[[WorkTimeReport(fromDay:2009-11-01,toDay:2009-11-30,graph:bars,stack:true)]]
}}}
'''

    def expand_macro(self, formatter, name, content):
        req = formatter.req
        
        self._add_script(req)
        
        if self._is_async(content):
            s = self._render_async(req, content)
            return s
        else:
            return self._render(req, content)

    def _add_script(self, req):
        if not hasattr(req, '_worktimereportmacro'):
            add_script(req, 'worktime/js/dateformat.js')
            add_stylesheet(req, 'worktime/css/worktime.css')
            
            # add script and css for jqplot
            add_script(req, 'worktime/js/jqplot/excanvas.min.js')
            add_script(req, 'worktime/js/jqplot/jquery.jqplot.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.pointLabels.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.barRenderer.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.pieRenderer.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.categoryAxisRenderer.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.canvasAxisLabelRenderer.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.dateAxisRenderer.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.cursor.min.js')
            add_script(req, 'worktime/js/jqplot/jqplot.highlighter.min.js')
            add_script(req, 'worktime/js/jqplot/worktimegraph.js')
            add_stylesheet(req, 'worktime/css/jqplot/jquery.jqplot.min.css')
            
            req._worktimereportmacro = 0
        else:
            req._worktimereportmacro = req._worktimereportmacro + 1
            
    def _is_async(self, content):
        match = re.match(r'.*(async\s*:\s*false).*', content.lower())
        if match:
            return False
        # default Ajax Mode
        return True
            
    def _render(self, req, content):
        renderer = ReportRenderer(self.env, self.group_providers)
        return renderer.render(req, content)
        
    def _render_async(self, req, content):
        index = req._worktimereportmacro
        
        div = tag.div(id="worktimereport_async_%d" %(index))
        script = """
jQuery(function(){
  jQuery("#worktimereport_loading_%d").ajaxStart(function(){
    jQuery(this).show();
  });
  jQuery("#worktimereport_loading_%d").ajaxStop(function(){
    jQuery(this).hide();
  });

  jQuery.ajax({
    type: "POST",
    url: "%s",
    data: "__FORM_TOKEN=%s;format=ajaxtable;index=%s;params=%s",
    dataType: "html",
    success: function (data) {
      jQuery("#worktimereport_async_%d").html(data);
    }
  })
});
""" % (index, index, req.href.worktimeajax(), req.form_token, index,
       content.replace('&', '__AND__').replace('=', '__EQ__'), index)
        
        div.append(tag.script(script, type='text/javascript'))
        div.append(tag.div((tag.img(src=req.href.chrome('worktime/images/loading.gif')),
                            tag.div('loading...')),
                            id='worktimereport_loading_%d' %(index),
                            style='border: solid 1px black; top: 50%; left: 50%; width: 50%; height: 200px; padding-top: 100px; text-align: center; top: 50%;'))
        div.append(tag.div('', id='worktimereport_loading_%d' %(index)))
        return div

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

    # IRequestHandler methods
    def match_request(self, req):
        match = re.match(r'^/worktimeajax(?:/(.*))?', req.path_info)
        if match:
            return True
        else:
            return False

    def process_request(self, req):
        assert_report_view_permission(self.env, req)
        
        try:
            index, content = self._parse_args(req.args)
        except KeyError:
            return 'worktime_report_response.html', {'response':u'エラーが発生しました。'}, None
            
        req._worktimereportmacro = index
        
        html = self._render(req, content)
        
        data = {'response': html.generate()}
        
        return 'worktime_report_response.html', data, None
    
    def _parse_args(self, args):
        return (int(args['index']), args['params'].replace('__AND__', '&').replace('__EQ__', '='))
    
    def _render(self, req, params):
        u"""WorkTimeの結果をHTMLで返す。オプションによってはテーブル、グラフそれぞれを表示する。
        """
        index = req._worktimereportmacro
        
        opts = self._parse_params(params)
        opts.update({'index': index})
        workers = self._get_workers(opts)

        if opts['cutoffDay']:
            return self._render_multiple(req, opts, workers)
        else:
            return self._render_single(req, opts, workers)
            
    def _render_multiple(self, req, opts, workers):
        renderer = ReportRenderer(self.env, self.group_providers)
        days = cutoff_days(opts['cutoffDay'], int(opts['monthlyReportCount']))
        
        html = tag.div()
        
        base_index = opts['index']
        for index, (from_day, to_day) in enumerate(days):
            opts['index'] = base_index * 1000 + index
            opts['title'] = ''
            title = u"%sから%sの作業時間内訳" % (from_day.isoformat(), to_day.isoformat())
            data, count, table = renderer.get_table_by_date(req, opts['groupField'], opts['unit'],
                                    from_day, to_day,
                                    target_workers=workers,
                                    opts=opts)
            table = self._render_graph(req, opts, table, count, data)
            html += tag.h3(title)
            html += table
        #self.log.debug("ajax html=" + str(html));
        return html
    
    def _render_single(self, req, opts, workers):
        renderer = ReportRenderer(self.env, self.group_providers)
        opts['title'] = u"%sから%sの作業時間内訳" % (opts['fromDay'], opts['toDay'])
        data, count, table = renderer.get_table_by_date(req, opts['groupField'], opts['unit'],
                                    str_to_datetime(req, opts['fromDay']), str_to_datetime(req, opts['toDay']),
                                    target_workers=workers,
                                    opts=opts)
        
        table = self._render_graph(req, opts, table, count, data)
        return table

    def _render_graph(self, req, opts, table, count, data):

        if opts['graph'] != 'lines' and opts['graph'] != 'bars':  
            return table
        
        if count == 0:
            return table
        
        index = opts['index']

        opttag = tag.div(id="worktimegraphopt_%d" % (index),
                         style="display:none")
        
        def showSummaryChartScript():
            if opts['showSummaryChart'] == 'true':
                summary_data = ''
                for row in data:
                    #[['frogs',3], ['buzzards',7], ['deer',2.5], ['turkeys',6], ['moles',5], ['ground hogs',4]];
                    sum = str(row['sum'])
                    summary_data += "['%s(%s)',%s]," % (row['value'], sum, sum)
                return 'renderSummaryGraph("worktimesummarychartplaceholder_%d", [%s]);' % (index, summary_data)
            else:
                return ''

        for opt in opts:
            opttag.append(tag.span(opts[opt], class_=opt))

        script = """
jQuery(document).ready(function($) {
  renderGraph("#worktimegraph_%d");
  %s
});
        """ % (index, showSummaryChartScript())

        div = tag.div(
                      tag.div(' ', id="worktimesummarychartplaceholder_%d" % (index)),
                      tag.div(' ',
                              id="worktimeplaceholder_%d" % (index),
                              style="width:%spx;height:%spx;" %
                              (opts["width"],opts["height"])),
                      opttag,
                      tag.br(),
                      table,
                      tag.script(script, type="text/javascript"),
                      class_="worktimegraph",
                      id="worktimegraph_%d" % (index)
                      )
        return div
    
    def _parse_params(self, params):
        default_opts = {
               'workers':None,
               'showSummaryChart':'true',
               'cutoffDay':None,
               'monthlyReportCount':'1',
               'fromDay':None,
               'toDay':None,
               'condition':None,
               'groupField':'id',
               'unit':'hours',
               'width':'536',
               'height':'300',
               'graph':'bars',
               'title':None,
               'table':'inline',
               'dateFormat':'yyyy-MM-dd',
               'stack':'true',
               'legendLoc':'ne',
               'legendXOffset':'12',
               'legendYOffset':'12',
               'xaxisMin':'null',
               'xaxisMax':'null',
               'yaxisMin':'0',
               'yaxisMax':'null',
               'xaxisFormatString':'',
               'yaxisFormatString':'',
               'transpose':True
               }
        opts = default_opts
        
        for param in params.split(','):
            colon_split = param.split(':')
            key = colon_split[0].strip()
            value = ''
            if len(colon_split) > 1:
                value = ':'.join(colon_split[1:])
            else:
                value = True
            opts[key] = value
            
        if not opts['fromDay'] or not opts['toDay']:
            cday = today()
            opts['fromDay'] = monthtop(cday).isoformat()
            opts['toDay'] = monthlast(cday).isoformat()
        
        return opts
    
    def _parse_vars(self, id):
        vars = {}
        
        if id.find('?') == -1:
            return id, vars
        
        id_and_params = id.split('?')
        params = id_and_params[1]
        id = id_and_params[0]
        
        if params.find('&') != -1:
            for (index, param) in enumerate(params.split('&')):
                if param.find('=') == -1:
                    continue
                entry = param.split('=')
                vars[entry[0]] = entry[1]
        elif params.find('=') != -1:
            entry = params.split('=')
            vars[entry[0]] = entry[1]
        
        return id, vars
    
    def _get_workers(self, req):
        if not req.get('workers'):
            return []
        workers = [x.strip() for x in req.get('workers').split(',')]
        return workers