# --------------------------------------------------
# Copyright The IETF Trust 2011, All Rights Reserved
# --------------------------------------------------

from __future__ import print_function

# Python libs
import time
import datetime
import re
try:
    import debug
    assert debug
except ImportError:
    pass

# Local libs
import xml2rfc
from xml2rfc.writers.paginated_txt import PaginatedTextRfcWriter
from xml2rfc.writers.raw_txt import RawTextRfcWriter
from xml2rfc.writers.base import BaseRfcWriter

nroff_linestart_meta = ["'", ".", ]

def nroff_escape_linestart(line):
    if line.startswith("'"):
        return "\\" + line
    if line.startswith("."):
        return "\\&" + line

class NroffRfcWriter(PaginatedTextRfcWriter):
    """ Writes to an nroff formatted file

        The page width is controlled by the *width* parameter.
    """

    comment_header = '.\\" automatically generated by xml2rfc v%s on %s'

    settings_header = [
        '.pl 10.0i',      # Page length
        '.po 0',          # Page offset
        '.ll 7.2i',       # Line length
        '.lt 7.2i',       # Title length
        '.nr LL 7.2i',    # Printer line length
        '.nr LT 7.2i',    # Printer title length
        '.hy 0',          # Disable hyphenation
        '.ad l',          # Left margin adjustment only
    ]

    def __init__(self, xmlrfc, width=72, quiet=False, verbose=False, date=datetime.date.today()):
        PaginatedTextRfcWriter.__init__(self, xmlrfc, width=width, \
                                        quiet=quiet, verbose=verbose, date=date)
        self.curr_indent = 0    # Used like a state machine to control
                                # whether or not we print a .in command
        self.wrapper.width = self.width
        self.wrapper.break_on_hyphens = False
        self.wrapper.post_break_replacements = {
            '&#160;' : r'\\0',  # nbsp
            '&#8209;': r'\-',  # nbhy
            '&#8288;': r'',     # wj
        }


    def _indent(self, amount):
        # Writes an indent command if it differs from the last
        if amount != self.curr_indent:
            self.write_nroff('.in ' + str(amount))
            self.curr_indent = amount

    # Override
    def _vspace(self, num=0):
        """ nroff uses a .sp command in addition to a literal blank line """
        self.write_nroff('.sp %s' % num)

    def write_nroff(self, string):
        # Used by nroff to write a line with nroff commands.  No escaping
        self.buf.append(string)

    def write_line(self, string):
        # Used by nroff to write a line with no nroff commands
        if string[0] in nroff_linestart_meta:
            string = nroff_escape_linestart(string)
        self.buf.append(string)

    # Override
    def write_figure(self, *args, **kwargs):
        """ Override base writer to add a marking """
        begin = len(self.buf)
        BaseRfcWriter.write_figure(self, *args, **kwargs)
        end = len(self.buf)
        nr = len([ l for l in self.buf[begin:end] if l and l[0] in nroff_linestart_meta])
        self.break_hints[begin] = (end - begin - nr, "txt")

    def write_table(self, *args, **kwargs):
        """ Override base writer to add a marking """
        begin = len(self.buf)
        BaseRfcWriter.write_table(self, *args, **kwargs)
        end = len(self.buf)
        nr = len([ l for l in self.buf[begin:end] if l and l[0] in nroff_linestart_meta])
        self.break_hints[begin] = (end - begin - nr, "txt")

    def write_raw(self, text, indent=3, align='left', blanklines=0, \
                  delimiter=None, leading_blankline=True, source_line=None):
        # Wrap in a no fill block
        self._indent(indent)
        self.write_nroff('.nf')
        begin = len(self.buf)
        PaginatedTextRfcWriter.write_raw(self, text, indent=0, align=align,
                                         blanklines=blanklines,
                                         delimiter=delimiter,
                                         leading_blankline=leading_blankline,
                                         source_line=source_line)
        for i in range(begin, len(self.buf)):
            if self.buf[i] and self.buf[i][0] == '.':
               self.buf[i] = '\&' + self.buf[i]
        self.write_nroff('.fi')

    def write_text(self, *args, **kwargs):
        #-------------------------------------------------------------
        # RawTextRfcWriter override
        #
        # We should be able to handle mostly all of the nroff commands by
        # intercepting the alignment and indentation arguments
        #-------------------------------------------------------------
        # Store buffer position for paging information
        begin = len(self.buf)
        
        par = []
        kwargs["buf"] = par
        RawTextRfcWriter.write_text(self, *args, **kwargs)

        # Escape as needed
        for i in range(len(par)):
            par[i] = par[i].strip()
            if par[i] and par[i][0] in nroff_linestart_meta:
                par[i] = nroff_escape_linestart(par[i])

        # Handle alignment/indentation
        align = kwargs.get('align', 'left')
        indent = kwargs.get('indent', 0)
        sub_indent = kwargs.get('sub_indent', 0)
        bullet = kwargs.get('bullet', '')
        
        if align == 'center':
            self.write_nroff('.ce %s' % len(par))
        else:
            # Use bullet for indentation if sub not specified
            full_indent = sub_indent and indent + sub_indent or indent + len(bullet)
            self._indent(full_indent)

        if bullet and len(bullet.strip()) > 0:
            # Bullet line: title just uses base indent
            self.write_nroff('.ti %s' % indent)


        mark = len(self.buf)

        # Write to buffer
        self.buf.extend(par)

        # Page break information
        end = len(self.buf)
        self.break_hints[begin] = (end - mark, "txt")
        """
        elif bullet:
            # If the string is empty but a bullet was declared, just
            # print the bullet
            buf.append(initial)
        """

    def pre_write_toc(self):
        # Wrap a nofill/fill block around TOC
        return ['','.ti 0','Table of Contents','.in 0', '.nf','']

    def post_write_toc(self):
        return ['.fi','.in 3']

    def _expand_xref(self, xref):
        """ Returns the proper text representation of an xref element """
        target = xref.attrib.get('target', '')
        format = xref.attrib.get('format', self.defaults['xref_format'])
        item = self._getItemByAnchor(target)
        if not item or format == 'none':
            target_text = '[' + target + ']'
        elif format == 'counter':
            target_text = item.counter
        elif format == 'title':
            target_text = item.title
        else: #Default
            target_text = item.autoName
        if xref.text and not target_text.startswith('['):
            target_text = '(' + target_text + ')'
        target_text_list = target_text.split(" ")
        target_text = "\\0".join(map(lambda x : ("\%" + x)
                                    if "." in x or "-" in x else x,
                                    target_text_list))
        if xref.text:        
            return xref.text.rstrip() + ' ' + target_text
        else:
            return target_text

    # ---------------------------------------------------------
    # PaginatedTextRfcWriter overrides
    # ---------------------------------------------------------

    def write_title(self, text, docName=None, source_line=None):
        self._lb()
        self.write_text(text, align='center', source_line=source_line)
        if docName:
            self.write_text(docName, align='center')

    def write_heading(self, text, bullet='', autoAnchor=None, anchor=None, level=1):
        # Store the line number of this heading with its unique anchor, 
        # to later create paging info
        begin = len(self.buf)
        self.heading_marks[begin] = autoAnchor
        # Override to use a .ti command
        self._lb()
        if bullet:
            bullet += '  '
        if len(bullet+text) > (self.width - 3):
            self._indent(len(bullet))
        self.write_nroff('.ti 0')
        self.write_line(bullet + text)
        # Reserve room for a blankline and some lines of section content
        # text, in order to prevent orphan headings
        end = len(self.buf) + self.pis["sectionorphan"]
        self.break_hints[begin] = (end - begin - 1, "txt")

    def urlkeep(self, text):
        text = re.sub(r'(\(?)http(s?)://', r'\%\1http\2://', text)
        return text

    def write_paragraph(self, text, align='left', autoAnchor=None):
        """ Write a generic paragraph of text.  Used for boilerplate. """
        text = self.urlkeep(text)
        self.write_text(text, indent=3, align=align, leading_blankline=True)

    def pre_indexing(self):
        # escape backslashes in the text
        for element in self.r.iter():
            if element.text:
                element.text = element.text.replace('\\', '\\\\')
            if element.tail:
                element.tail = element.tail.replace('\\', '\\\\')

    def pre_rendering(self):
        """ Inserts an nroff header into the buffer """

        # Construct the RFC header and footer
        PaginatedTextRfcWriter.pre_rendering(self)

        # Insert a timestamp+version comment
        self.write_nroff(self.comment_header %
            (
                xml2rfc.__version__,
                time.strftime('%Y-%m-%dT%H:%M:%SZ', datetime.datetime.utcnow().utctimetuple())
            )
        )
        self._lb()

        # Insert the standard nroff settings
        self.buf.extend(NroffRfcWriter.settings_header)

        # Insert the RFC header and footer information
        self.write_nroff('.ds LH ' + self.left_header)
        self.write_nroff('.ds CH ' + self.center_header)
        self.write_nroff('.ds RH ' + self.right_header)
        self.write_nroff('.ds LF ' + self.left_footer)
        self.write_nroff('.ds CF ' + self.center_footer)
        self.write_nroff('.ds RF FORMFEED[Page %]')


    def page_break(self, final=False):
        "Used by post_rendering() to insert page breaks."
        while True:
            x = self.output.pop()
            if x:
                self.output.append(x)
                break;

        if not final:
            self.output.append('.bp')
        self.page_length = 1
        self.page_num += 1

    def emit(self, text):
        "Used by post_rendering() to emit and count lines."
        if self.page_length == 1 and text.strip() == '':
            return 
        if isinstance(text, str):
            self.output.append(text)
            if not text or text[0] not in nroff_linestart_meta:
                self.page_length += 1
            elif text.startswith('.sp'):
                parts = text.split()
                if len(parts) >= 2:
                    self.page_length += int(parts[1])
        elif isinstance(text, list):
            for line in text:
                self.output.append(line)
                if not line or line[0] not in nroff_linestart_meta:
                    self.page_length += 1
        else:
            raise TypeError("a string or a list of strings is required")

    def write_to_file(self, file):
        """ Writes the buffer to the specified file """
        for line in self.output:
            file.write(line.rstrip(" \t"))
            file.write("\n")
            
