# --------------------------------------------------
# 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, default_options

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
    ]

    in_list = False

    def __init__(self, xmlrfc, width=72, quiet=None, options=default_options, date=None):
        if not quiet is None:
            options.quiet = quiet
        PaginatedTextRfcWriter.__init__(self, xmlrfc, width=width, options=options, 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 = True
        
    def _length(self, text):
        """ Get rid of the zero width items before getting the length
        """
        return len(re.sub(u'[\u200B|\u2060|\uE060]', '', text))

    def _indent(self, amount, buf=None):
        # Writes an indent command if it differs from the last
        # If it is not the global buffer - ways write and never remember
        if not (buf is None or buf == self.buf):
            self.write_nroff('.in ' + str(amount), buf=buf)
        elif 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, buf=None):
        # Used by nroff to write a line with nroff commands.  No escaping
        if buf is None:
            buf = self.buf
        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._set_break_hint(end - begin - nr, 'raw', begin)

    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._set_break_hint(end - begin - nr, 'raw', begin)

    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
        first = len(self.buf)
        self._indent(0)
        self.write_nroff('.nf')
        begin = len(self.buf)
        PaginatedTextRfcWriter.write_raw(self, text, indent=indent, 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] in nroff_linestart_meta:
               self.buf[i] = nroff_escape_linestart(self.buf[i])
        self.write_nroff('.fi')
        self._indent(indent)
        # Move the paging hint to the first command
        self.break_hints[first] = self.break_hints[begin]
        del self.break_hints[begin]
        

    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

        if 'buf' in kwargs:
            buffer = kwargs["buf"]
        else:
            buffer = self.buf

        par = []
        kwargs["buf"] = par

        begin = len(buffer)
        
        leading_blankline = kwargs.get("leading_blankline", False)
        if self.in_list and not leading_blankline and len(buffer) and not buffer[-1].startswith('.sp'):
            self.write_nroff('.br')
        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([x for x in par if x != ""]), buf=buffer)
        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, buf=buffer)

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

        mark = len(buffer)

        # Write to buffer
        buffer.extend(par)

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

    def write_list(self, *args, **kwargs):
        save_in_list = self.in_list
        self.in_list = True
        PaginatedTextRfcWriter.write_list(self, *args, **kwargs)
        self.in_list = save_in_list

    def write_ref_element(self, *args, **kwargs):
        begin = len(self.buf)
        PaginatedTextRfcWriter.write_ref_element(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._set_break_hint(end - begin - nr, 'raw', begin)

    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 pre_write_iref_index(self):
        return ['', '.ti 0','Index']

    def IsFormatting(self, line):
        return len(line) > 0 and line[0] in nroff_linestart_meta
    
    # ---------------------------------------------------------
    # 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 and not self.rfcnumber:
            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

        RawTextRfcWriter.write_heading(self, text, bullet=bullet, \
                                       autoAnchor=autoAnchor, anchor=anchor, level=level)
        # Reserve room for a blankline and some lines of section content
        # text, in order to prevent orphan headings
        #
        orphanlines = self.get_numeric_pi('sectionorphan', default=self.orphan_limit+2)
        end = len(self.buf)
        nr = len([ l for l in self.buf[begin:end] if l and l[0] in nroff_linestart_meta])
        needed = end - begin - nr + orphanlines
        self._set_break_hint(needed, "raw", begin)

    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 post_process_lines(self, lines):
        output = []
        flow_text = True
        for line in lines:
            if line == ".fi":
                flow_text = True
            elif line == ".nf":
                flow_text = False
            else:
                line = self.post_process_nroff(flow_text, line)
            output.append(line)
        return output
        
    def post_process_nroff(self, flow_text, text):
        if text and text[0] == '.':
            return text
        # normal quoting
        text = text.replace('\\', '\\\\')       # slashes 
        text = re.sub("^\\\\\\\\&\\.", "\\&.", text)  # repair start of line markers
        text = re.sub("^\\\\\\\\&'", "\\&'", text)  # repair start of line markers

        # character replacement
        text = re.sub(u"\u2011", "-", text)
        text = re.sub(u'\u00A0', r'\\0', text)
        text = re.sub(u'\u200B', r'\\&', text)
        text = re.sub(u'\u200B', r'\\&', text)
        assert u'\uE060' not in text

        # wrapping quoting
        if flow_text:
            text = re.sub("([^ \t\n]*-)", r"\%\1", text)
        return text

    def emit(self, text):
        "Used by post_rendering() to emit and count lines."
        if isinstance(text, type('')) or isinstance(text, type(u'')):
            if self.page_length == 1 and text.strip() == '':
                return 
            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.emit(line)
        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.post_process_lines(self.output):
            file.write(line.rstrip(" \t"))
            file.write("\n")
            
