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

from trac.core import *
from trac.wiki.api import IWikiMacroProvider
from trac.util import escape, Markup
from trac.wiki.api import parse_args
from trac.wiki.macros import WikiMacroBase
from trac.wiki.formatter import wiki_to_html
from trac.web.chrome import add_link,add_script, add_stylesheet, INavigationContributor, ITemplateProvider
from trac.util import format_datetime
from trac.versioncontrol.api import RepositoryManager, Changeset, NoSuchChangeset
from trac.versioncontrol.web_ui.changeset import ChangesetModule
from trac.versioncontrol.web_ui.util import *
from genshi.builder import tag
from datetime import date
import os
import pkg_resources
import time
from trac.util.datefmt import from_utimestamp

class CommitCountMacro(WikiMacroBase):
    implements(IWikiMacroProvider,ITemplateProvider)

    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        return [('commitcount',
                 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 'CommitCount'
    def get_macro_description(self, name):
        return """
    マルチリポジトリ対応、日毎のコミット数積算のグラフ出力
    {{{
       [[CommitCount(path)]]
       path省略 : 全リポジトリ
       path = '/' : defaultリポジトリ
       path = '/mmm' : リポジトリmmm
       path = '/trunk' : defaultリポジトリのtrunk
       path = '/mmm/trunk' : リポジトリmmmのtrunk
       path = '/trunk,/mmm/trunk' : defaultリポジトリのtrunkとリポジトリmmmのtrunk
              「,」で区切って続けると複数のパスを記述できます。
       「,」で区切って以下を追加するとmilestoneを参照して上部に表示することができます。
       (flotを使用している関係でmilestone文字列が重なることがあります。)
       但し、スペースなどもそのまま取り込みますので注意してください。
       mson=1 : 完了したmilestoneを表示します。
       en_str=xxx : mson=1のとき文字列xxxが含まれるmilestoneのみ表示します。
       en_str_cut=1 : mson=1かつen_str指定のとき
                      milestone文字列からen_str文字列を削除して表示します。
       legend=repos : 凡例にrepository名を表示(記述しなければpathを表示)
       width=xxx : xxxが数文字列である場合、グラフの横幅[pixel]を指定します。指定なし:500
       height=xxx : xxxが数文字列である場合、グラフの縦幅[pixel]を指定します。指定なし:300
       start=xxx : mson=1でxxxがmilestoneに当てはまる場合、そのmilestoneを開始日とします。
                   また、2010/3/1のように直接指定してもその日を開始日とします。指定なし:一番最初のcommit日
       end=xxx : mson=1でxxxがmilestoneに当てはまる場合、そのmilestoneを終了日とします。
                   また、2010/3/1のように直接指定してもその日を終了日とします。指定なし:一番最後のcommit日
       例-
       [[CommitCount(/mmm/trunk)]]
       [[CommitCount(/mmm/trunk,mson=1,en_str=xxx,en_str_cut=1,legend=repos)]]
       [[CommitCount(/mmm/trunk,width=400,height=250)]]
       [[CommitCount(/mmm/trunk,mson=1,start=milestone1,end=milestone2)]]
       [[CommitCount(/mmm/trunk,start=2010/3/1,end=2010/3/31)]]
    }}}
    """


    def expand_macro(self, formatter, name, content):
        req = formatter.req
        args, kwargs = parse_args(content) #キーなしはargsにlistで、キーありはkwargsにdictで入る
        
        if 'CHANGESET_VIEW' not in req.perm:
            return Markup('<i>CHANGESET_VIEW not available</i>')
        if kwargs.has_key('mson') and kwargs['mson']=='1': #milestone表示で
            if 'MILESTONE_VIEW' not in req.perm:
                return Markup('<i>MILESTONE_VIEW not available</i>')
        
        if not hasattr(req, '_commitcount'):
            add_script(req, 'commitcount/js/excanvas.min.js')
            add_script(req, 'commitcount/js/jquery.min.js')
            add_script(req, 'commitcount/js/jquery.flot.min.js')
            add_script(req, 'commitcount/js/commitcount.js')
            req._commitcount = 0
        else:
            req._commitcount = req._commitcount+1
        
        #multi repository
        rm = RepositoryManager(self.env)
        
        pathstr = []
        if len(args) == 0:
            all_repositories = rm.get_all_repositories().items()
            if len(all_repositories) == 0:
                msg = _("repository not found")
                raise ResourceNotFound(msg)
            for reponame, repoinfo in all_repositories:
                repo = rm.get_repository(reponame)
                if not repo:
                    continue #なければやめて次に
                if repo.youngest_rev == None:
                    continue #なければやめて次に
                pathstr.append('/'+reponame)
        else:
            for arg in args:
                if arg == '/':
                    arg = ''
                reponame, repo, path = rm.get_repository_by_path(arg)
                if not repo:
                    continue #なければやめて次に
                if repo.youngest_rev == None:
                    continue #なければやめて次に
                pathstr.append(arg)
        
        if len(pathstr) == 0:
            msg = _("path not found")
            raise ResourceNotFound(msg)
        
        #グラフの凡例位置(flotではne:右上、nw:左上、se:右下、sw:左下)
        legend_pos = True #True:右下,False:右上
        
        line_step = True #True:step状にする,False:直線でつなぐ
        line_point = False #True:点をつける,False:点をつけない
        
        start_day = None
        end_day = None
        
        #'%Y/%m/%d'フォーマットで引数が入っていれば取り込む
        if kwargs.has_key('start'):
            try:
                start_daystr = time.strptime(kwargs['start'],'%Y/%m/%d')
                start_day = time.mktime(start_daystr)
            except ValueError:
                start_day = None
        if kwargs.has_key('end'):
            try:
                end_daystr = time.strptime(kwargs['end'],'%Y/%m/%d')
                end_day = time.mktime(end_daystr)
            except ValueError:
                end_day = None
        
        #milestone data
        if len(kwargs)!=0 and kwargs.has_key('mson') and kwargs['mson']=='1':
            #milestoneをDBから読み出す
            milestondata = []
            cursor = self.env.get_db_cnx().cursor()
            cursor.execute("SELECT name, completed from milestone order by completed;")
            miles = [mile for mile in cursor]
            for name, completed in miles:
                if completed:#完了したものだけ
                    d = from_utimestamp(completed)
                    dc = d.strftime('%Y/%m/%d')
                    
                    if kwargs.has_key('en_str'):#許可文字列の指定があり
                        if name.find(kwargs['en_str'])==-1:#指定文字列が含まれてなければ
                            continue #次に
                        else:
                            #milestoneでstart/endを指定する場合
                            if kwargs.has_key('start') and kwargs['start']==name:
                                start_day = time.mktime(time.strptime(dc,'%Y/%m/%d'))
                            if kwargs.has_key('end') and kwargs['end']==name:
                                end_day = time.mktime(time.strptime(dc,'%Y/%m/%d'))
                            
                            if kwargs.has_key('en_str_cut') and kwargs['en_str_cut']=='1':#許可文字列カットなら
                                #self.env.log.debug('name:%s', name)
                                name = name.replace(kwargs['en_str'],'') #カットする
                                #self.env.log.debug('name2:%s', name)
                    milestondata.append([name,dc])
        if start_day != None and end_day != None:
            if start_day > end_day:#逆になっていたらNoneにする
                start_day = None
                end_day = None
        
        ymax = 0
        xmin_str = ''
        xmax_str = ''
        g_width='500' #glaphの幅
        g_height='300' #glaphの高さ
        if kwargs.has_key('width') and kwargs['width'].isdigit(): #'width'があって数字の文字列
            if int(kwargs['width']) != 0:
               g_width = int(kwargs['width'])
        if kwargs.has_key('height') and kwargs['height'].isdigit(): #'height'があって数字の文字列
            if int(kwargs['height']) != 0:
               g_height = int(kwargs['height'])
        wid = int(g_width)
        
        table = tag.table(class_="listing reports",
                          id="commitcounttable_%d"%(req._commitcount),style="display:none") #"display:visible")
        tr = tag.tr(tag.th('Field'),
                    tag.th('Date'),
                    tag.th('Count'))
        table.append(tag.thead(tr))
        
        for path in pathstr:
            reponame, repo, path2 = rm.get_repository_by_path(path)
            rev = repo.get_youngest_rev()
            if rev != None: #なければやめる
                node = repo.get_node(path2, rev)
                #path2の最後の/をとる
                if path2[-1:] == '//':
                    path2 = path2[:-2]
                if path2[len(path2)-1] == '/':
                    path2 = path2[:-1]
                #path2が空でなければ先頭に/をつける
                if path2 != '':
                    path2 = '/' + path2
                if kwargs.has_key('legend') and kwargs['legend']=='repos':
                    if reponame == '':
                        repopathstr = "(default)"
                    else:
                        repopathstr = reponame
                else:
                    repopathstr = path
                #self.env.log.debug('reponame %s', reponame)
                i=0
                datestr = []
                datecount = []
                for npath, nrev, nlog in node.get_history(): #get_history()は最新のrevから返す
                    #self.env.log.debug('npath:%s path2:%s', npath, path2)
                    if ('/' + npath) != path2:#/を足して同文字列でない
                        if not (npath == '/' and path2 == ''):#repoのルートでない
                            continue #別のパスなら次に
                    change = repo.get_changeset(nrev)
                    str = format_datetime(change.date,'%Y/%m/%d')
                    #self.env.log.debug('rev%d %s', nrev, str)
                    if len(datestr) == 0:
                        #i = 0
                        #日にちのmin/max保存
                        if xmin_str == '': #一番最初
                            xmin_str = str
                            xmax_str = str
                            xmin = time.mktime(time.strptime(str,'%Y/%m/%d'))
                            xmax = xmin
                        else:
                            x_cur = time.mktime(time.strptime(str,'%Y/%m/%d'))
                            if x_cur < xmin:
                                xmin_str = str
                                xmin = x_cur
                            if x_cur > xmax:
                                xmax_str = str
                                xmax = x_cur
                        
                        datestr.append(str)
                        datecount.append(1)
                    elif datestr[i] == str:
                        datecount[i] = datecount[i] + 1
                    else:
                        i += 1
                        #日にちのmin/max保存
                        x_cur = time.mktime(time.strptime(str,'%Y/%m/%d'))
                        if x_cur < xmin:
                            xmin_str = str
                            xmin = x_cur
                        if x_cur > xmax:
                            xmax_str = str
                            xmax = x_cur
                        
                        datestr.append(str)
                        datecount.append(1)
                linenum = i
                
                #一番古いものからにするため逆順に
                for i in range(linenum,-1,-1): 
                    if i != linenum:
                        datecount[i] += datecount[i+1]
                    curdate = time.mktime(time.strptime(datestr[i],'%Y/%m/%d'))
                    #start～endに入るものだけテーブルに追加
                    flag = False
                    if start_day == None:
                        if end_day == None: #どちらも指定なし
                            flag = True
                        elif curdate <= end_day: #start_day指定なし
                            flag = True
                    elif end_day == None:
                        if start_day <= curdate: #end_day指定なし
                            flag = True
                    elif start_day <= curdate and curdate <= end_day:#どちらも指定あり
                        flag = True
                    if flag:
                        tr = tag.tr(tag.td(repopathstr),
                                    tag.td(datestr[i]),
                                    tag.td(datecount[i])
                                    )
                        table.append(tr)
                        
                        if ymax < datecount[i]:
                            ymax = datecount[i]
        #ymaxstr = str(ymax) #これでunicodeがどうとかといわれる。ymaxをそのままoptsに入れても大丈夫。何故？
        if start_day != None:
            xmin = start_day
        if end_day != None:
            xmax = end_day
        
        #milestone list
        mstable = tag.table(class_="listing milestones",
                          id="commitcountmstable_%d"%(req._commitcount),style="display:none") #"display:visible")
        tr = tag.tr(tag.th('Milestone'),
                    tag.th('DateNum'))
        mstable.append(tag.thead(tr))
        if kwargs.has_key('mson') and kwargs['mson']=='1':
            for milestone_str,date_str in milestondata: #
                cur = time.mktime(time.strptime(date_str,'%Y/%m/%d'))
                #xmin～xmaxに入るものだけテーブルに追加
                if xmin <= cur and cur <= xmax:
                    cur2 = (cur - xmin) * wid / (xmax - xmin)
                    tr = tag.tr(tag.td(milestone_str),tag.td(cur2))
                    mstable.append(tr)
        
        #default
        opts = {
               'ymax':ymax,
               'gwidth':g_width,
               'linestep':line_step,
               'linepoint':line_point,
               'upper':legend_pos
               }
        opttag = tag.div(id="commitcountopt_%d"%(req._commitcount),style="display:none")#"display:visible")
        for opt in opts:
            opttag.append(tag.span(opts[opt],class_=opt))
        div = tag.div(
            tag.div(' ',
                    id="placeholder_%d"%(req._commitcount),
                    style="width:%spx;height:%spx;"%(g_width,g_height)),
            opttag,
            table,
            mstable,
            class_="commitcount",
            id="commitcount_%d"%(req._commitcount)
            )
        return div
