#!/opt/local/bin/python

import base
from base                   import rightdown
from base.rightdown.enums   import *


class Printer(base.Thing):
  ''' default options cause us to emit naked text with neither formatting nor markup '''

  # Copy()-ing a Printer means copying all our settings
  ATTRIBUTES        = [
      'markup', 'formatting',
      #'unicode',
      'metadata', 'empties', 'pass_comments', 'pass_html', 'pass_templates', 'force_fence', 'double_space',
      'block_indent', 'unfenced_indent',
      #'wrap_width', 'wrap_indent',
      'stop_after_stage',
  ]

  # should we emit markup?
  markup                      = False

  # should we emit formatting text?
  formatting                  = False

  # Should we emit metadata?
  metadata                    = False

  # Should we emit empty blocks?
  empties                     = False

  # Should we pass through raw html, comments, and template syntax?
  pass_html                   = False
  pass_comments               = False
  pass_templates              = False

  # should we force indented code blocks to turn into fenced code blocks?
  force_fence                 = False

  # should we emit blank lines between blocks?
  double_space                = True

  # How many spaces to indent nested blocks?
  block_indent                = 0

  # How many spaces to indent un-fenced code blocks?
  unfenced_indent             = 4

  # How many characters to a line before we wrap?  0 to disable wrapping
  #wrap_width                  = 90

  # How many spaces to indent a wrapped line's continuations?
  #wrap_indent                 = 2

  # this is only meaningful if it's set to STAGE_IMPRINTS
  stop_after_stage            = STAGE_DONE

  # this affects whether this printer emits rich characters or text equivalents
  _test_substitution_column   = 1

  def Print(self, rd):
    ''' distills a RightDown instance to a single string '''
    if isinstance(rd, rightdown.tokens.Snip):
      imprints      = self._NormalizeImprints(rd.PrintOpen(self, None, None)) + self._NormalizeImprints(rd.PrintClose(self, None, None))
    elif isinstance(rd, rightdown.tokens.Block):
      imprints      = rd.Walk(self._WalkOpen, self._WalkClose) or []
    else:
      raise rightdown.errors.UnPrintable(rd)

    if self.stop_after_stage == rightdown.enums.STAGE_IMPRINTS:
      return ''.join(x.debug + '\n' for x in imprints).rstrip()

    return self._MergeImprints(imprints).rstrip()

  ###
  ## callbacks
  #

  def HandleToken(self, token, depth, first, last):
    ''' allows subclasses to override the action on any token rendering '''

  def MuteUntilClose(self, token):
    ''' causes us to suppress all output until the given token is in the rear view mirror '''
    self._muted     = token

  def PrefixUntilClose(self, token, imprint):
    ''' the given imprint should precede any line of output until the given token is closing '''
    if self._stack[-1][0] != token:
      raise rightdown.errors.TreeWalkBroken(list(repr(x[0]) for x in self._stack), repr(token))
    if self._stack[-1][1]:
      raise rightdown.errors.OnePrefixPerToken(token)
    self._stack[-1] = (token, imprint)

  ###
  ## mechanical
  #

  def __init__(self, **kwargs):
    base.utils.SetAttrs(self, **kwargs)
    self._muted     = None    # token
    self._stack     = []      # [ (token, prefix) ]
    self._broken    = True    # was the last emitted imprint a break?
    self._inlining  = False   # are we in the midst of emitting inline imprints?
    self._maybe     = None    # for double-spacing, a list of imprints we *might* want to apply

  def _NormalizeImprints(self, imprints):
    ''' convert a lone imprint to a tuple '''
    if not imprints:
      return []
    if isinstance(imprints, rightdown.tokens.Imprint):
      imprints      = [imprints]
    return imprints

  def _CollectPrefixes(self, depth):
    ''' accumulates the imprints that should begin a line, and augment each with its stack depth '''
    prefixes          = []
    for i in range(len(self._stack)):
      token, imprint  = self._stack[i]
      if imprint:
        imprint.depth = i
        prefixes.append(imprint)
    if self.block_indent and (not hasattr(self, 'minimize') or not self.minimize):
      prefixes.append(rightdown.tokens.Imprint(IMPRINT_FORMATTING, text=' '*depth*self.block_indent, depth=depth))
    return prefixes

  def _FixBreaks(self, imprints, breaker, prefixes, depth):
    ''' any non-inline imprints must be surrounded both sides by a break, and breaks should be followed by prefixes '''
    # self._broken is assumed to be accurate to the point *before* our first imprint
    results           = []
    doublespace       = self.double_space and (prefixes + [breaker])
    maybies           = self._maybe and [x for x in self._maybe if x.depth <= depth]
    for imprint in imprints:

      # imprint is a break; pass it through, maybe double it
      if imprint.imprinttype == rightdown.enums.IMPRINT_BREAK:
        self._broken  = True
        results.append(breaker)
        if doublespace:
          if imprint is imprints[-1] and prefixes:
            self._maybe = prefixes
          else:
            results.extend(doublespace)

      # imprint is inline; we are now *not* broken
      elif imprint.inline:
        if self._broken:
          if self._maybe:
            results.extend(maybies)
            results.append(breaker)
            self._maybe = None
          results.extend(prefixes)
        results.append(imprint)
        self._broken  = False

      # imprint is not inline; make sure it's broken on both sides
      else:
        if self._maybe:
          results.extend(maybies)
          results.append(breaker)
          self._maybe = None
        if not self._broken:
          results.append(breaker)
          if doublespace:
            results.extend(doublespace)
        results.extend(prefixes)
        results.append(imprint)
        results.append(breaker)
        if doublespace:
          if imprint is imprints[-1] and prefixes:
            self._maybe = prefixes
          else:
            results.extend(doublespace)
        self._broken  = True

    return results

  def _WalkOpen(self, token, depth, first, last):
    ''' called on each token in the tree, before descending into that token's children '''
    # track who's called whom
    self._stack.append((token, None))

    # be silent if asked
    if self._muted:
      return

    # become silent if needed
    if not self.empties and token.empty:
      self.MuteUntilClose(token)
      return

    # gather prefixes before calling PrintOpen
    prefixes        = self._CollectPrefixes(depth)

    # get the token's imprints
    imprints        = self.HandleToken(token, depth, first, last)
    if imprints is None:
      imprints      = token.PrintOpen(self, first, last)
    imprints        = self._NormalizeImprints(imprints)

    # line-breaking
    breaker         = rightdown.tokens.Imprint(IMPRINT_BREAK)
    if imprints and imprints[0].inline and not self._inlining:
      self._inlining  = True
      if not self._broken:
        imprints    = [breaker] + imprints
    imprints        = self._FixBreaks(imprints, breaker, prefixes, depth)

    return imprints or None

  def _WalkClose(self, token, depth, first, last):
    ''' called on each token in the tree, after that token's children are finished '''
    # track who's called whom
    if self._stack[-1][0] != token:
      raise rightdown.errors.TreeWalkBroken(list(repr(x[0]) for x in self._stack), repr(token))
    self._stack     = self._stack[:-1]

    # might be finished with silence
    if self._muted:
      if self._muted == token:
        self._muted = None
      return

    # ask the token for its imprints
    prefixes        = self._CollectPrefixes(depth)
    imprints        = self._NormalizeImprints(token.PrintClose(self, first, last))
    breaker         = rightdown.tokens.Imprint(IMPRINT_BREAK)

    # break when exiting an inline context
    if not token.inline and self._inlining:
      self._inlining  = False
      if not self._broken:
        imprints.append(breaker)

    # fix up breaks
    imprints        = self._FixBreaks(imprints, breaker, prefixes, depth)

    return imprints or None

  def _MergeImprints(self, imprints):
    ''' merge the final list of imprints into a single string '''
    NEWLINE         = '\n'
    substrings      = []
    for imprint in imprints:
      if imprint.imprinttype == rightdown.enums.IMPRINT_BREAK:
        substrings.append(NEWLINE)
      if imprint.text:
        substrings.append(imprint.text)
    return ''.join(substrings)



class TextPrinter(Printer):
  ''' this printer emits normalized rightdown text '''

  formatting                  = True
  metadata                    = True
  pass_html                   = True
  pass_comments               = True
  pass_templates              = True
  _test_substitution_column   = 2



class HtmlPrinter(Printer):
  ''' this printer emits HTML similar to any other markdown engine '''

  markup                      = True
  pass_html                   = True
  pass_templates              = True
  double_space                = False
  block_indent                = 2
  _test_substitution_column   = 3

#   ATTRIBUTES        = Printer.ATTRIBUTES + ['dress', 'minimize']
#
#   # Normally we emit naked HTML, but let us know if you want <HTML>, <HEAD>, and <BODY> tags with that
#   dress                       = False
#
#   # Should the HTML be compacted down to one line?
#   minimize                    = False
#
#   def _MergeImprints(self, imprints):
#     if self.minimize:
#       return ''.join(x.text or '' for x in imprints)
#     return super()._MergeImprints(imprints)



class DebugPrinter(Printer):
  ''' emits a plaintext summary of the token tree '''

  ATTRIBUTES        = ('truncate_width', 'token_width')

  # How many characters when printing lines to the console?
  truncate_width              = 60

  # How many characters to allocate for the name of a token?
  token_width                 = 24

  def Print(self, rd):
    ''' distills a RightDown instance to a single string '''
    return '\n'.join(rd.Walk(self._WalkOpen, self._WalkClose) or [])

  def _WalkOpen(self, token, depth, first, last):
    tokensymbol     = token.tokentype.enum == rightdown.enums.LineTypes and '-' or ''
    tokentag        = '  ' * depth + tokensymbol + token.tokentype.name
    tokentext       = token.debug
    if tokentext:
      content       = base.utils.PadString(tokentag, self.token_width) + token.debug_sep + ' ' + tokentext
    else:
      content       = tokentag
    if self.truncate_width and len(content) > self.truncate_width:
      content       = content[:self.truncate_width-1] + '…'
    if content:
      return content

  def _WalkClose(self, token, depth, first, last):
    ''' this method intentionally left blank '''
