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

import calendar
import os
import pkg_resources
import re
import time

from datetime import datetime

from trac.core import *
from trac.notification import NotifyEmail
from trac.perm import IPermissionRequestor
from trac.resource import IResourceManager, Resource, ResourceNotFound
from trac.search import ISearchSource, search_to_sql, shorten_result
from trac.ticket import Ticket
from trac.timeline import ITimelineEventProvider
from trac.util.datefmt import to_timestamp, utc
from trac.util.presentation import Paginator
from trac.util.translation import _
from trac.web.api import IRequestHandler, IRequestFilter, ITemplateStreamFilter
from trac.web.chrome import add_link, add_stylesheet, add_script, add_ctxtnav, prevnext_nav, \
                        INavigationContributor, ITemplateProvider
from trac.wiki import wiki_to_oneliner

from genshi.filters.transform import Transformer
from genshi.template import MarkupTemplate

from attachment import MailArchiveAttachment
from model import Mail, MailFinder
from util import *

class MailArchiveModule(Component):
    
    implements(INavigationContributor, IPermissionRequestor, IResourceManager,
               IRequestHandler)
    
    permission_actions = [('MAILARCHIVE_ADMIN', ['MAILARCHIVE_VIEW', 'MAILARCHIVE_REPLYMAIL']), 'MAILARCHIVE_REPLYMAIL']
    
    # ITemplateProvider methods
    def get_htdocs_dirs(self):
        return [('mailarchive',pkg_resources.resource_filename(__name__, 'htdocs'))]

    def get_templates_dirs(self):
        return [pkg_resources.resource_filename(__name__, 'templates')]
    
    # INavigationContributor methods
    def get_active_navigation_item(self, req):
        return 'mailarchive'

    def get_navigation_items(self, req):
        if has_mailarchive_view_permission(self.env, req):
            yield ('mainnav', 'mailarchive',
                   tag.a(_('MailArchive'), href=req.href.mailarchive()))

    # IResourceManager methods
    def get_resource_realms(self):
        yield 'mailarchive'

    def get_resource_url(self, resource, href, **kwargs):
        return href.mailarchive(resource.id)
        
    def get_resource_description(self, resource, format=None, context=None,
                                 **kwargs):
        if context:
            return tag.a('mail:'+resource.id, href=context.href.mailarchive(resource.id))
        else:
            return 'mail:'+resource.id

    # IPermissionRequestor method
    def get_permission_actions(self):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute('SELECT DISTINCT mlid FROM mailarc_category ORDER BY mlid')
        ml_actions = []
        for row in cursor:
            ml_action = get_mlperm_action(row[0])
            ml_actions.append(ml_action)
        
        permission_actions = self.permission_actions + ml_actions
        if len(ml_actions) > 0:
            return permission_actions + [('MAILARCHIVE_VIEW', ml_actions)]
        else:
            return permission_actions + ['MAILARCHIVE_VIEW']
        
    def _get_ml_permission_map(self):
        db = self.env.get_db_cnx()
        cursor = db.cursor()
        cursor.execute('SELECT DISTINCT mlid FROM mailarc_category ORDER BY mlid')
        ml_permission_map = {}
        for row in cursor:
            ml_action = get_mlperm_action(row[0])
            ml_permission_map.update({ml_action: row[0]})
        return ml_permission_map

    # IRequestHandler methods
    def match_request(self, req):
        match = re.match(r'^/mailarchive(?:/(.*))?', req.path_info)
        if match:
            if match.group(1):
                req.args['messageid'] = match.group(1)
            return 1

    def process_request(self, req):
        db = self.env.get_db_cnx()

        messageid = req.args.get('messageid', '')
        action = req.args.get('action', 'list')
        flatmode = (req.args.get('flatmode', 'off') == 'on')
        
        if messageid != '':
            return self._render_view(req, db, messageid)
        else:
            return self._render_list(req, db, flatmode, False)

    def _render_view(self, req, db, id):
        data = {}
        data['action'] = 'view'

        mail = Mail(self.env, id, db=db)
            
        mail.assert_permission(req)

        target_threadroot = mail.thread_root

        if 'mailarc_mails' in req.session:
            self.log.debug(req.session['mailarc_mails'])
            mails = req.session['mailarc_mails'].split()
            if str(id) in mails:
                idx = mails.index(str(id))
                if idx > 0:
                    #add_ctxtnav(req, _('first'), req.href.mailarchive(mails[0]))
                    add_link(req, _('prev'), req.href.mailarchive(mails[idx - 1]))
                if idx < len(mails) - 1:
                    add_link(req, _('next'), req.href.mailarchive(mails[idx + 1]))
                    #add_ctxtnav(req, _('last'), req.href.mailarchive(mails[-1]))
                add_link(req, _('up'), req.session['mailarc_category_href'])
                prevnext_nav(req, u'メール', u'リストに戻る')

        #if target_threadroot == '':
        #    target_threadroot = messageid
        
        data['id'] = atomic_id()
        data['mail'] = mail
        data['related_tickets'] = get_related_tickets(self.env, req, db, mail.id)
        data['attachment'] = MailArchiveAttachment(self.env, mail.id) 
        data['get_author'] = get_author
        
        return 'maildetail.html', data, None

   # Internal methods
    def _render_list(self, req, db, flatmode, month):
        target_category = req.args.get('category', '')

        data = {}

        # ログインユーザがアクセス可能なMLのカテゴリのみを取得する
        data['mls'], data['name'], data['year'], data['month'], target_category \
             = MailFinder.get_categories(self.env, req, db, target_category,
                                         ml_permission_map=self._get_ml_permission_map())
        
        start = time.time()
        
        #results = MailFinder.find_by_category(self.env, target_category, db=db)
        mail_ids = MailFinder.find_ids_by_category(self.env, target_category, db=db)
        
        self.env.log.debug('Find by category time: %f' % (time.time() - start))
        
        #pagelize
        start = time.time()
        
        pagelized = self._pagelize_list(req, mail_ids, data)
        
        self.env.log.debug('pagelized time: %f' % (time.time() - start))

        #Thanks http://d.hatena.ne.jp/ohgui/20090806/1249579406
        data['reversemode'] = reversemode = (req.args.get('reversemode', 'off') == 'on')
        data['flatmode'] = flatmode

        mails_per_page, cached_mails = self._get_mails_per_page(pagelized, reversemode, flatmode)

        idstext = self._collect_ids(pagelized.items, flatmode)
        
        self.log.debug("Idtext: %s" % idstext)
        
        req.session['mailarc_mails'] = idstext
        req.session['mailarc_category_href'] = get_category_href(req, target_category)

        data['id'] = atomic_id()
        data['mails'] = mails_per_page
        data['highlight_ids'] = pagelized.items
        data['cached_mails'] = cached_mails

        return 'mailarchive.html', data, None
    
    def _collect_ids(self, mails, flatmode):
        if True:
            return ''.join(['%s ' % mail_id for mail_id in mails])
        #TODO: ツリー表示の場合に別処理を行うか？
    
    def _get_mails_per_page(self, pagelized, reversemode, flatmode):
        mail_ids_per_page = pagelized.items
        
        if flatmode:
            mails_per_page = self._get_mails_by_page(pagelized)
            if reversemode:
                mails_per_page.reverse()
            return mails_per_page, []
            
        else:
            from time import time
            start = time()
            
            #ツリー表示のため、rootだけを集める
            room_mails = MailFinder.find_roots_by_id(self.env, mail_ids_per_page)
            root_msgids = [x.messageid for x in room_mails]
                    
            #ルートから関連メールを全てキャッシュする
            cached_mails = MailFinder.find_thread_mails(self.env, root_msgids)
    
            if reversemode:
                room_mails.reverse()
                
            end = time()
            self.env.log.debug('Making Threads time: %f' % (end - start))
                
            return room_mails, cached_mails
        
    def _get_mails_by_page(self, pagelized):
        mail_ids = pagelized.items
        mails = MailFinder.find_mails(self.env, mail_ids)
        return mails
            
    def _pagelize_list(self, req, results, data):
        # get page from req(default page = max_page)
        page = int(req.args.get('page', '-1'))
        num_item_per_page = int(self.env.config.get('mailarchive', 'items_page','50'))
        num_shown_pages = int(self.env.config.get('mailarchive', 'shown_pages','30'))
        if page == -1:
            results_temp = Paginator(results, 0, num_item_per_page)
            page = results_temp.num_pages 

        results = Paginator(results, page - 1, num_item_per_page)
        
        pagedata = []    
        data['page_results'] = results
        shown_pages = results.get_shown_pages(num_shown_pages)
        for shown_page in shown_pages:
            page_href = req.href.mailarchive(category=req.args.get('category',None),
                                        page=shown_page, noquickjump=1)
            pagedata.append([page_href, None, str(shown_page),
                             'page ' + str(shown_page)])

        fields = ['href', 'class', 'string', 'title']
        results.shown_pages = [dict(zip(fields, p)) for p in pagedata]
        
        results.current_page = {'href': None, 'class': 'current',
                                'string': str(results.page + 1),
                                'title':None}

        if results.has_next_page:
            next_href = req.href.mailarchive(category=req.args.get('category',None),
                                        page=page + 1)
            add_link(req, 'next', next_href, _('Next Page'))

        if results.has_previous_page:
            prev_href = req.href.mailarchive(category=req.args.get('category',None),
                                        page=page - 1)
            add_link(req, 'prev', prev_href, _('Previous Page'))

        data['page_href'] = req.href.mailarchive(category=req.args.get('category',None))
        return results 
    
class MailArchiveAjaxModule(Component):
    
    implements(IRequestHandler)
    
    # IRequestHandler methods
    def match_request(self, req):
        match = re.match(r'^/ajaxmailarchive(?:/(.*))?', req.path_info)
        if match:
            if match.group(1):
                req.args['mail_id'] = match.group(1)
            return 1

    def process_request(self, req):
        db = self.env.get_db_cnx()

        mail_id = req.args.get('mail_id')
        mail = Mail(self.env, id=mail_id, db=db)
        
        mail.assert_permission(req)
        
        data = {'mail': mail}
        data['get_author'] = get_author
        data['attachment'] = MailArchiveAttachment(self.env, mail.id) 
        return 'mail_response.html', data, None
    
class MailThreadAjaxModule(Component):
    
    implements(IRequestHandler,
               IRequestFilter, ITemplateStreamFilter)

    FIELD_XPATH = 'div[@id="ticket"]/table[@class="properties"]/td[@headers="h_%s"]/text()'

    # IRequestFilter methods
    def pre_process_request(self, req, handler):
        return handler
        
    def post_process_request(self, req, template, data, content_type):
        #チケット表示画面時はfilter_streamで加工できるように
        #dataに追加しておく
        if data and req.path_info.startswith('/ticket/'):
            ticket = data.get('ticket')
            if ticket is None:
                return template, data, content_type
                
            if not isinstance(ticket, Ticket):
                ticket = Ticket(self.env, ticket)
            mail_id_plain = ticket['mail_id']
            mail_ids = to_mail_ids(mail_id_plain)
                    
            if len(mail_ids) > 0:
                data['mailarchive'] = {
                    'mail_ids': mail_ids,
                    'link': linkify_ids(self.env, req, mail_ids),
                }
        return template, data, content_type

    # ITemplateStreamFilter methods
    def filter_stream(self, req, method, filename, stream, data):
        if 'mailarchive' in data:
            #cssを追加
            add_stylesheet(req, 'common/css/report.css')
            add_stylesheet(req, 'mailarchive/css/mailarchive.css')
            add_script(req, 'mailarchive/js/mailarchive.js')
            
            #mail_idをハイパーリンクに置き換える
            link =  data['mailarchive']['link']
            stream |= Transformer(self.FIELD_XPATH % 'mail_id').replace(link)
            
            #関連メールのスレッド表示を追加する
            mail_ids =  data['mailarchive']['mail_ids']
            template = MarkupTemplate(u"""
<div xmlns:py="http://genshi.edgewall.org/"
     xmlns:xi="http://www.w3.org/2001/XInclude">
  <script type="text/javascript">
    jQuery(function(){
      showRelatedMailThread($('#relatedmailthreads'));
    });
  </script>
  <h2>関連メール</h2>
  <div class="mail_id" style="display:none;">${','.join(mail_ids)}</div>
  <div class="mail_loading" style="display:none;">&nbsp;</div>
  <div id="relatedmailthreads"></div>
</div>
            """)
            thread_stream = template.generate(mail_ids=mail_ids, req=req)
            
            THREAD_PATH = '//div[@id="ticket"]'
            stream |= Transformer(THREAD_PATH).after(thread_stream)
            
        return stream

    # IRequestHandler methods
    def match_request(self, req):
        match = re.match(r'^/ajaxmailthread(?:/(.*))?', req.path_info)
        if match:
            if match.group(1):
                req.args['mail_id'] = match.group(1)
            return 1

    def process_request(self, req):
        db = self.env.get_db_cnx()

        mail_id_plain = req.args.get('mail_id')
        mail_ids = to_mail_ids(mail_id_plain)
        
        mails = MailFinder.find_mails(self.env, mail_ids)
        data = {'id': atomic_id(),
                'mails': mails}
        
        return 'mail_ticketrelated.html', data, None
    
class MailEditorAjaxModule(Component):
    
    implements(IRequestHandler)

    _LB = '\\n'
    
    # IRequestHandler methods
    def match_request(self, req):
        match = re.match(r'^/ajaxmaileditor(?:/(.*))?', req.path_info)
        if match:
            if match.group(1):
                req.args['mail_id'] = match.group(1)
            return 1

    def process_request(self, req):
        if req.args.get('action') == 'send':
            return self._send_mail(req)
        else:
            return self._open_mail_editor(req)
        
    def _send_mail(self, req):
        db = self.env.get_db_cnx()
        mail_id = req.args.get('mail_id')
        mail = Mail(self.env, id=mail_id, db=db)
        if not mail.has_permission(req):
            return 'mail_send_response.html', {'body':u"送信権限がありません"}, None
        
        from_mail = req.args.get('from')
        subject = req.args.get('subject')
        to = req.args.get('to')
        cc = req.args.get('cc')
        body = req.args.get('body')
        related_tickets = req.args.get('related_tickets')
        addchange = req.args.get('addchange')
        comment = req.args.get('comment')
        
        self.env.log.debug('To:' + str(to))
        self.env.log.debug('Cc:' + str(cc))
        self.env.log.debug('related tickets:' + str(related_tickets))
        self.env.log.debug('addchange:' + str(addchange))
        #self.env.log.debug('comment:' + comment)
        
        if u'\ufeff' in body:
            p = re.compile(u'\ufeff')
            body = p.sub('', body)

        tos = [x.strip() for x in to.split(',') if x.strip() != '']
        ccs = [x.strip() for x in cc.split(',') if x.strip() != '']

        
        #add ticket change check
        tickets = []
        current_related_ticket_ids = get_all_related_tickets(self.env, req, db, mail)
        
        if addchange == 'true':
            ticket_ids = [x.strip() for x in related_tickets.split(',') if x.strip() != '']
            
            for ticket_id in set(ticket_ids):
                try:
                    ticket = Ticket(self.env, ticket_id, db=db)
                    tickets.append(ticket)
                except ResourceNotFound:
                    return 'mail_send_response.html', {'body':u"送信エラー: #%sは存在しないチケットIDです。" % ticket_id}, None
        
        #send email
        email = ReplyEmail(self.env, from_mail.strip(), from_mail.strip(), tos, ccs, subject, body, mail.messageid)
        email.notify()
        
        if addchange == 'true':
            ticket['status'] = 'accepted'
            for ticket in tickets:
                current = ticket['mail_id']
                if not ticket.id in current_related_ticket_ids:
                    current_mail_ids = [x.strip() for x in current.split(',') if x.strip() != '']
                    if len(current_mail_ids) == 0:
                        ticket['mail_id'] = mail_id
                    else:
                        ticket['mail_id'] += ', ' + mail_id
                    
            message = \
u"""mail:%s への返信として、%sからメールを送信しました。

 * 件名: %s
 * 宛先: %s
 * Cc: %s
""" % (mail_id, req.authname, subject, to, cc)
            if comment:
                message += u"""
コメント:
 * %s
""" % comment           

            # save changes
            for ticket in tickets:
                ticket.save_changes(req.authname, message, db=db)
                
            # commit!
            db.commit()
        
        return 'mail_send_response.html', {'body':u"送信完了しました"}, None
        
    def _open_mail_editor(self, req):
        
        db = self.env.get_db_cnx()

        mail_id = req.args.get('mail_id')
        mail = Mail(self.env, id=mail_id, db=db)
        
        mail.assert_permission(req)
        
        thread_mails = mail.get_thread_mails()
        thread_root = mail.get_thread_root()
        thread_mails.append(thread_root)
        
        related_tickets = set()
        for thread_mail in thread_mails:
            ticket_ids = get_related_ticket_ids(self.env, req, db, thread_mail.id)
            related_tickets.update(ticket_ids)
        
        quote_mail = ''
        for line in mail.body.split('\n'):
            quote_mail += '> ' + line + '\n'
        
        template_key = self.env.config.get('mailarchive', 'reply_email_template.key', None)
        template_keys = []
        if template_key:
            template_keys = template_key.split(',')
            
        template_mails = []
        comments = []
        for key in template_keys:
            template = self.env.config.get('mailarchive', 'reply_email_template.%s' % key.strip().encode('utf-8'), '')
            comment = self.env.config.get('mailarchive', 'reply_email_comment.%s' % key.strip().encode('utf-8'), '')
            template_mails.append((key, template.replace('\n', '\\n'), comment.replace('\n', '\\n')))
        
        data = {}
        data['id'] = atomic_id()
        data['quote_mail'] = quote_mail
        data['template_mails'] = template_mails
        data['mail'] = mail
        data['related_tickets'] = related_tickets
        data['from_email'] = get_login_email(self.env, req)
        data['get_author'] = get_author
        return 'mail_editor.html', data, None
        
class ReplyEmail(NotifyEmail):
    template_name = "reply_email.txt"
    
    def __init__(self, env, from_name, from_mail, tos, ccs, subject, message, parent_id=None):
        NotifyEmail.__init__(self, env)
        self._from_name = from_name
        self._from_mail = from_mail
        self._tos = tos
        self._ccs = ccs
        self._subject = subject
        self._message = message
        self.parent_id = parent_id
    
    def notify(self):
        self.data.update({'message' : self._message })
        NotifyEmail.notify(self, None, self._subject)
        
    def get_recipients(self, resid):
        return self._tos, self._ccs
    
    def add_headers(self, msg, headers):
        for h in headers:
            if h in ('X-Trac-Version', 'X-Trac-Project', 'Reply-To'):
                continue
            msg[h] = self.encode_header(h, headers[h])
            
    def send(self, torcpts, ccrcpts, hdrs={}):
        # Override fields set in "NotifyEmail.notify"
        self.from_email = self._from_mail
        self.replyto_email = self._from_mail
        self.from_name = self._from_name
    
        if self.parent_id:
            hdrs['In-Reply-To'] = self.parent_id
            
        self.env.log.debug('send to:' + str(torcpts))
        self.env.log.debug('send cc:' + str(ccrcpts))
        
#        NotifyEmail.send(self, torcpts, ccrcpts, hdrs)
        
class Timeline(Component):
    
    implements(ITimelineEventProvider)

    # ITimelineEventProvider methods
    def get_timeline_filters(self, req):
        if has_mailarchive_view_permission(self.env, req):
            yield ('mailarchive', _(self.env.config.get('mailarchive', 'title', 'MailArchive')))

    def get_timeline_events(self, req, start, stop, filters):
        if 'mailarchive' in filters:
            add_stylesheet(req, 'mailarchive/css/mailarchive.css')

            mails = MailFinder.find_by_date(self.env, start, stop)

            mailarchive_realm = Resource('mailarchive')
                
            for mail in mails:
                resource = mailarchive_realm(id=mail.id, version=None)
                if not mail.has_permission(req):
                    continue
                yield ('mailarchive',
                       datetime.fromtimestamp(mail.utcdate, utc),
                       mail.get_fromtext(),
                       (resource, (mail.category, mail.get_fromtext(), mail.subject)))

    def render_timeline_event(self, context, field, event):
        mailarchive_page, (category,author,subject) = event[3]
        if field == 'url':
            return context.href.mailarchive(mailarchive_page.id, version=mailarchive_page.version)
        elif field == 'title':
            markup = tag(u'メールが ', category, u'に送信されました')
            return markup
        elif field == 'description':
            markup = tag(subject)
            return markup
    
class SearchProvider(Component):
    
    implements(ISearchSource)

    # ISearchProvider methods
    def get_search_filters(self, req):
        if has_mailarchive_view_permission(self.env, req):
            yield ('mailarchive', self.env.config.get('mailarchive', 'title', 'MailArchive'))

    def get_search_results(self, req, terms, filters):
        if 'mailarchive' in filters:
            if not has_mailarchive_view_permission(self.env, req):
                return
            
            db = self.env.get_db_cnx()
            sql_query, args = search_to_sql(db,
                                             ['m1.messageid', 'm1.subject', 'm1.fromname', 'm1.fromaddr', 'm1.text'],
                                             terms)
            sql = "SELECT m1.id, m1.category, m1.subject, m1.fromname, m1.fromaddr, m1.text, m1.utcdate as localdate " \
                  "FROM mailarc m1 " \
                  "WHERE "
            cursor = db.cursor()
            cursor.execute(sql + sql_query, args)
                
            mailarchive_realm = Resource('mailarchive')

            for id, category, subject, fromname, fromaddr, text, localdate in cursor:
                resource = mailarchive_realm(id=id, version=None)
                mlid = category[:-6]
                action = get_mlperm_action(mlid)
                if not action in req.perm(mailarchive_realm):
                    continue

                yield (req.href.mailarchive(id),
                       subject,
                       datetime.fromtimestamp(localdate, utc),
                       get_author(fromname, fromaddr),
                       shorten_result(text, terms))