import re
import os
import urllib

try:
    from cStringIO import StringIO
except ImportError:
    from StringIO import StringIO

from trac import util
from trac.mimeview import *
from trac.wiki.api import WikiSystem
from trac.wiki.formatter import Formatter
from trac.util import shorten_line, to_unicode

__all__ = ['wiki_to_latex']

def pretexfilter(s, boolparam=True):
    """
    Escape LaTeX seqeuences in \a s that do not affect Wiki formatting
    """
    s = s.replace('\\', '\textbackslash ') #<lbrace /><rbrace /> ??
    s = s.replace('{', '\\{{<rbrace />').replace('}', '\\<rbrace />{<rbrace />').replace('<rbrace />', '}')
    s = s.replace('$', '\\${}')
    s = s.replace('&gt;', '\textgreater{}').replace('&lt;', '\textless{}').replace('&amp;', '\myamp{}')
    s = s.replace('&', '\\&{}').replace('%', '\\mypercent{}') #etc
    return s

def gtotex(s, boolparam=True):
    """
    Escape LaTeX seqeuences in \a s that may still be present after processing.
    Favour pretexfilter() because the LaTeX formatting added _during_ processing need to be kept
    """
    if not s:
        return s
    s = re.sub(r'"([^,. ])', r"``\1", s).replace('"', "''") #tex-ify quote character
    s = s.replace('<', '\\textless{}').replace('>', '\\textgreater{}').replace('%', '\\mypercent{}') #should NEVER be any of these
    s = s.replace('&gt;', '\textgreater{}').replace('&lt;', '\textless{}')
    s = re.sub(r'^#', r'\\#{}', s)
    s = re.sub(r'([^\\$])#', r'\1\\#{}', s) #replace hashes, if not already preceded by a backslash or $
    s = re.sub(r'^_', r'\\_{}', s)
    s = re.sub(r'([^\\$])_', r'\1\\_{}', s) #replace underscores, if not already preceded by a backslash or $
    s = re.sub(r'^\^', '\\\\textasciicircum{}', s)
    s = re.sub(r'([^$])^', r'\1\\\\textasciicircum{}', s)
    return to_unicode(s)

def gfilter_to_url(s, boolparam=True):
    """
    Filter out things escaped by pretexfilter that \url is able to cope with
    """
    if not s:
        return s
    s = s.replace('\\_{}', '_').replace('\\%{}', '%').replace('\\&{}amp;', '&')
    return s

def LatexURL(formatter, ns, target, text, boolparam=True):
    """
    Create a LaTeX \url with a given namespace, target and link text
    """
    if text == target:
        return '\\url{' + (ns and ns + ':' or "") + gfilter_to_url(target) + '}';
    return '\\anchortext{' + (gtotex(text) or gtotex(target) or "broken") + '}\\footnote{\\url{' + (ns and ns + ':' or "") + gfilter_to_url(target) + '}}';

def WikiLatexURL(formatter, ns, target, text, boolparam=True):
    """
    Create a LaTeX \url with a given namespace, target and link text
    """
    if text == target:
        return '\\anchortext{' + gtotex(target) + '} (\\S\\ref{sub:' + totexlabel(target) + '})'
    return '\\anchortext{' + (gtotex(text) or gtotex(target) or "broken") + '} (\\S\\ref{sub:' + totexlabel(target) + '})\\footnote{\\url{' + (ns and ns + ':' or "") + gfilter_to_url(target) + '}}';

def totexlabel(s, boolparam=True):
    """
    Create a valid LaTeX for \ref cross references
    """
    if not s:
        return s
    return s.replace('&', '').replace('$', '').replace(' ', '-').replace('"', '').replace("'", '').replace('#', ':').replace('{', '').replace('}', '').replace('~', '-')

class LatexFormatter(Formatter):
    flavor = 'latex'

    def __init__(self, env, page, req=None, absurls=0, db=None):
        Formatter.__init__(self, env, req, absurls, db)
        self.pagename = page.name

    def _get_link_resolvers(self):
        return {'link':LatexURL, 'source':LatexURL, 'wiki':WikiLatexURL, 'ticket':LatexURL, 'changeset':LatexURL} #WikiSystem(self.env).link_resolvers
    link_resolvers = property(_get_link_resolvers)

    def _get_db(self):
        if not self._db:
            self._db = self.env.get_db_cnx()
        return self._db
    db = property(fget=_get_db)

    def _get_rules(self):
        return self.wiki.rules
    rules = property(_get_rules)

    def replace(self, fullmatch):
        for itype, match in fullmatch.groupdict().items():
            if match and not itype in self.wiki.helper_patterns:
                # Check for preceding escape character '!'
                if match[0] == '!':
                    return gtotex(pretexfilter(match[1:]))
                if match[0:4] == "http":
                    return LatexURL(self, 'http', match, match)
                elif match[0].isalpha():
                    return WikiLatexURL(self, 'wiki', match, match)
                if itype in self.wiki.external_handlers:
                    return self.wiki.external_handlers[itype](self, match, fullmatch)
                else:
                    return getattr(self, '_' + itype + '_formatter')(match, fullmatch)

    def _tickethref_formatter(self, match, fullmatch):
        """
        This is from a patch for Trac 0.8 \todo how to call for 0.9+ (via link resolvers?)
        """
        number = int(match[1:])
        cursor = self.db.cursor ()
        cursor.execute('SELECT summary,status FROM ticket WHERE id=%s', number)
        row = cursor.fetchone ()
        if not row:
            return '\\#%d (missing)' % (number)
        else:
            summary =  gtotex(self.prefilter(util.shorten_line(row[0])))
            if row[1] == 'new':
                return '\\#%d*\\footnote{%s \\emph{(new)}}' % (number, summary)
            elif row[1] == 'closed':
                return '\\sout{\\#%d}\\footnote{%s \\emph{(closed)}}'% (number, summary)
            else:
                return '\\#%d\\footnote{%s}' % (number, summary)

    def _bolditalic_formatter(self, match, fullmatch):
        italic = ('\\emph{', '}')
        italic_open = self.tag_open_p(italic)
        tmp = ''
        if italic_open:
            tmp += italic[1]
            self.close_tag(italic[1])
        tmp += self._bold_formatter(match, fullmatch)
        if not italic_open:
            tmp += italic[0]
            self.open_tag(*italic)
        return tmp

    def _unquote(self, text):
        if text and text[0] in "'\"" and text[0] == text[-1]:
            return text[1:-1]
        else:
            return text

    def _shref_formatter(self, match, fullmatch):
        ns = fullmatch.group('sns')
        target = self._unquote(fullmatch.group('stgt'))
        return self._make_link(ns, target, match, match)

    def _make_link(self, ns, target, match, label):
        if ns in self.link_resolvers:
            return self.link_resolvers[ns](self, ns, target, label, False)
        elif target.startswith('//') or ns == "mailto":
            return self._make_ext_link(ns+':'+target, label)
        else:
            return gtotex(match)

    def _make_ext_link(self, url, text, title=''):
        same = text == url
        url = gfilter_to_url(url)
        ref = totexlabel(url)
        text, title = gtotex(text), gtotex(title)
        if Formatter.img_re.search(url) and self.flavor != 'oneliner':
            return '\\url{%s} (extimage - todo: make figure float with caption %s)' % (
                   url, title or text)
        if not url.startswith(self._local):
            if same:
                return '\\url{' + url + '}'
            return '\\anchortext{%s}\\footnote{\\url{%s}}' % (text, url)
        else:
            if same:
                return '\\url{' + url + '}'
            return '\\anchortext{%s}\\footnote{ext: \\url{%s}}' % (text, url)

    def _make_relative_link(self, url, text):
        same = text == url
        url = gfilter_to_url(url)
        ref = totexlabel(url)
        text = gtotex(text)
        if Formatter.img_re.search(url) and self.flavor != 'oneliner':
            return '\\url{%s} (relimage - todo: make figure float with caption %s)' % (
                   url, text)
        if not url.startswith(self._local):
            if same:
                return '\\url{' + url + '}'
            return '\\anchortext{%s}\\footnote{\\url{%s}}' % (text, url)
        else:
            if same:
                return '\\url{' + url + '}'
            return '\\anchortext{%s}\\footnote{\\url{%s}} or Subsection \\ref{sub:%s}' % (text, url, ref)

    def _bold_formatter(self, match, fullmatch):
        return self.simple_tag_handler('\\textbf{', '}')

    def _italic_formatter(self, match, fullmatch):
        return self.simple_tag_handler('\\textit{', '}')

    def _underline_formatter(self, match, fullmatch):
        if match[0] == '!':
            return gtotex(pretexfilter(match[1:]))
        else:
            return self.simple_tag_handler('\\underbar{',
                                           '}')

    def _strike_formatter(self, match, fullmatch):
        if match[0] == '!':
            return gtotex(pretexfilter(match[1:]))
        else:
            return self.simple_tag_handler('\\sout{', '}')

    def _subscript_formatter(self, match, fullmatch):
        if match[0] == '!':
            return gtotex(pretexfilter(match[1:]))
        else:
            return self.simple_tag_handler('$_{', '}$')

    def _superscript_formatter(self, match, fullmatch):
        if match[0] == '!':
            return gtotex(pretexfilter(match[1:]))
        else:
            return self.simple_tag_handler('$^{', '}$')

    def _inlinecode_formatter(self, match, fullmatch):
        return '\\texttt{%s}' % gtotex(fullmatch.group('inline'))

    def _inlinecode2_formatter(self, match, fullmatch):
        return '\\texttt{%s}' % gtotex(fullmatch.group('inline2'))

    def _htmlescape_formatter(self, match, fullmatch):
        return match == "&" and "\\&{}" or \
               match == "<" and "\\textless{}" or "\\textgreater{}"

    def _macro_formatter(self, match, fullmatch):
        name = fullmatch.group('macroname')
        if name in ['br', 'BR']:
            if len(self.current_line) < 7:
                return '\\vspace{1ex}'
            return '\\\\'
        return '\\begin{verbatim}\n' + Formatter._macro_formatter(self, match, fullmatch) + '\\end{verbatim}\n'
#        args = fullmatch.group('macroargs')
#        try:
#            macro = WikiProcessor(self.env, name)
#            return macro.process(self.req, args, 1)
#        except Exception, e:
#            self.env.log.error('Macro %s(%s) failed' % (name, args),
#                               exc_info=True)
#            return system_message('Error: Macro %s(%s) failed' % (name, args), e)

    def _heading_formatter(self, match, fullmatch):
        match = match.strip()
        self.close_table()
        self.close_paragraph()
        self.close_indentation()
        self.close_list()
        self.close_def_list()

        depth = min(len(fullmatch.group('hdepth')), 5)
        heading = match[depth + 1:len(match) - depth - 1]

        anchor = text = heading
        sans_markup = re.sub(r'</?\w+(?: .*?)?>', '', text)

        #check if valid LaTeX label
        i = 1
        anchor = anchor_base = anchor.encode('utf-8')
        while anchor in self._anchors:
            anchor = anchor_base + str(i)
            i += 1
        self._anchors.append(anchor)
        try:
                self.out.write('\\subsubsection{\\label{anchor:%s}%s}' % (totexlabel(anchor), gtotex(text)))
        except:
                self.out.write('\\subsubsection{Bad Unicode}')

    def _indent_formatter(self, match, fullmatch):
        depth = int((len(fullmatch.group('idepth')) + 1) / 2)
        list_depth = len(self._list_stack)
        if list_depth > 0 and depth == list_depth + 1:
            self.in_list_item = 1
        else:
            self.open_indentation(depth)
        return ''

    def _last_table_cell_formatter(self, match, fullmatch):
        return ''

    def _table_cell_formatter(self, match, fullmatch):
        self.open_table()
        self.open_table_row()
        if self.in_table_cell:
            return ' & '
        else:
            self.in_table_cell = 1
            return ''

    def close_indentation(self):
        self.out.write(('\\end{quote}' + os.linesep) * self.indent_level)
        self.indent_level = 0

    def open_indentation(self, depth):
        if self.in_def_list:
            return
        diff = depth - self.indent_level
        if diff != 0:
            self.close_paragraph()
            self.close_indentation()
            self.close_list()
            self.indent_level = depth
            self.out.write(('\\begin{quote}' + os.linesep) * depth)

    def _list_formatter(self, match, fullmatch):
        ldepth = len(fullmatch.group('ldepth'))
        depth = int((len(fullmatch.group('ldepth')) + 1) / 2)
        self.in_list_item = depth > 0
        type_ = ['ol', 'ul'][match[ldepth] == '*']
        self._set_list_depth(depth, type_)
        return ''

    def _definition_formatter(self, match, fullmatch):
        tmp = self.in_def_list and '' or '\\begin{description}\n'
        tmp += '\\item %s ' % gtotex(match[:-2]) #wiki_to_oneliner(match[:-2], self.env, self.db)
        self.in_def_list = True
        return tmp

    def close_def_list(self):
        if self.in_def_list:
            self.out.write('\\end{description}\n')
        self.in_def_list = False

    def _hl_to_ll(self, l):
        if l == 'ul':
            return 'itemize'
        else:
            return 'enumerate'

    def _set_list_depth(self, depth, type_):
        current_depth = len(self._list_stack)
        diff = depth - current_depth
        self.close_table()
        self.close_paragraph()
        self.close_indentation()
        if diff > 0:
            for i in range(diff):
                self._list_stack.append(type_)
                self.out.write('\\begin{' + self._hl_to_ll(type_) + '}\n')
                self.out.write('\n\\item ')
        elif diff < 0:
            for i in range(-diff):
                tmp = self._list_stack.pop()
                self.out.write('\\end{' + self._hl_to_ll(tmp) + '}\n')
            if self._list_stack != [] and type_ != self._list_stack[-1]:
                tmp = self._list_stack.pop()
                self._list_stack.append(type_)
                self.out.write('\n\\end{%s}\n\\begin{%s}\n\\item ' % (self._hl_to_ll(tmp), self._hl_to_ll(type_)))
            if depth > 0:
                self.out.write('\n\\item ')
        # diff == 0
        elif self._list_stack != [] and type_ != self._list_stack[-1]:
            tmp = self._list_stack.pop()
            self._list_stack.append(type_)
            self.out.write('\n\\end{%s}\n\\begin{%s}\n\\item ' % (self._hl_to_ll(tmp), self._hl_to_ll(type_)))
        elif depth > 0:
            self.out.write('\n\\item ')

    def close_list(self):
        if self._list_stack != []:
            self._set_list_depth(0, None)

    def open_paragraph(self):
        if not self.paragraph_open:
            self.out.write(os.linesep)
            self.paragraph_open = 1

    def close_paragraph(self):
        if self.paragraph_open:
            while self._open_tags != []:
                self.out.write(self._open_tags.pop()[1])
            self.out.write(os.linesep)
            self.paragraph_open = 0

    def open_table(self):
        if not self.in_table:
            self.close_paragraph()
            self.close_indentation()
            self.close_list()
            self.close_def_list()
            self.in_table = 1
            self.out.write('\\begin{tabular}{|l|l|l|l|l|l|l|l|l|} \\hline' + os.linesep)

    def open_table_row(self):
        if not self.in_table_row:
            self.open_table()
            self.in_table_row = 1
            self.out.write(os.linesep)

    def close_table_row(self):
        if self.in_table_row:
            self.in_table_row = 0
            if self.in_table_cell:
                self.in_table_cell = 0
                self.out.write('~\\\\ \\hline' + os.linesep)

    def close_table(self):
        if self.in_table:
            self.close_table_row()
            self.out.write('\\end{tabular}' + os.linesep)
            self.in_table = 0

    def handle_code_block(self, line):
        if line.strip() == '{{{':
            self.in_code_block += 1
            if self.in_code_block == 1:
                self.out.write('\\begin{verbatim}' + os.linesep)
            else:
                self.out.write(line + os.linesep)
        elif line.strip() == '}}}':
            self.in_code_block -= 1
            if self.in_code_block == 0:
                self.out.write('\\end{verbatim}' + os.linesep)
            else:
                self.out.write(line + os.linesep)
        else:
            self.out.write(line + os.linesep)

    def preamble(self):
        self.secname = self.pagename
        self.out.write('\\documentclass{article}' + os.linesep)
        self.out.write('\\usepackage{url}' + os.linesep)
        self.out.write('\\usepackage{ulem}' + os.linesep)
#        self.out.write("""\def\dotuline{\\bgroup
#                \\ifdim\\ULdepth=\\maxdimen  % Set depth based on font, if not set already
#                \\settodepth\\ULdepth{(j}\\advance\\ULdepth.4pt\\fi
#                \\markoverwith{\\begingroup
#                \\advance\\ULdepth0.08ex
#                \\lower\\ULdepth\\hbox{\\kern.15em .\\kern.1em}%
#                \\endgroup}\\ULon}""")
        self.out.write('\\newcommand{\\anchortext}[1]{\def\ULthickness{.2pt}\\underbar{#1}\def\ULthickness{.4pt}} %this does not appear to work properly' + os.linesep)
        self.out.write('\\newcommand{\\mypercent}{\\%{}}')
        self.out.write('\\newcommand{\\myamp}{\\&{}}')
        self.out.write('\\begin{document}' + os.linesep)
        self.out.write('\\subsection{\\label{sub:' + self.secname + '}' + self.secname + '}' + os.linesep)

    def format(self, text, out, escape_newlines=False):
        self.out = out
        self._open_tags = []
        self._list_stack = []

        self.in_code_block = 0
        self.in_table = 0
        self.in_def_list = 0
        self.in_table_row = 0
        self.in_table_cell = 0
        self.indent_level = 0
        self.paragraph_open = 0

        self.preamble()

        for line in text.splitlines():
            self.current_line = line
            # Handle code block
            if self.in_code_block or line.strip() == '{{{':
                self.handle_code_block(line)
                continue
            # Handle Horizontal ruler
            elif line[0:4] == '----':
                self.close_paragraph()
                self.close_indentation()
                self.close_list()
                self.close_def_list()
                self.close_table()
                self.out.write('{\\normalsize \\vspace{1ex} \\hrule width \\columnwidth \\vspace{1ex}}' + os.linesep)
                continue
            # Handle new paragraph
            elif line == '':
                self.close_paragraph()
                self.close_indentation()
                self.close_list()
                self.close_def_list()
                continue

            if escape_newlines:
                line += ' [[BR]]'
            self.in_list_item = False
            # Throw a bunch of regexps on the problem
            result = re.sub(self.rules, self.replace, line)

            if not self.in_list_item:
                self.close_list()

            if self.in_def_list and not line.startswith(' '):
                self.close_def_list()

            if self.in_table and line[0:2] != '||':
                self.close_table()

            if len(result) and not self.in_list_item and not self.in_def_list \
                    and not self.in_table:
                self.open_paragraph()
            try:
                out.write(gtotex(result) + os.linesep)
            except:
                out.write("\\textless Bad Unicode \textgreater" + os.linesep)
            self.close_table_row()

        self.close_table()
        self.close_paragraph()
        self.close_indentation()
        self.close_list()
        self.close_def_list()
        self.out.write('\\end{document}')

def wiki_to_latex(page, env, req, db=None, absurls=0, escape_newlines=False):
    out = StringIO()
    LatexFormatter(env, page, req, absurls, db).format(page.text, out, escape_newlines)
    return util.Markup(out.getvalue())

