#  Copyright 2015 Google Inc. All Rights Reserved.
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

"""The block language system for the R language formatter."""

from . import base
from . import support
import re

_options = base.Options()  # Shorthand for convenient access


class LayoutBlock(object):
  """The abstract class at base of the block hierarchy."""

  def __init__(self, is_breaking=False):
    # If a newline is mandated after this block.
    self.is_breaking = is_breaking
    # See OptLayout method below for use of layout_cache.
    self.layout_cache = {}

  def Parms(self):
    """A dictionary containing the parameters of this block."""
    return {}

  def ReprParms(self):
    """The printed representation of this block's parameters."""
    if not self.Parms(): return ''
    return ('<%s>' % (', '.join('%s=%s' % (key, val.__repr__())
                                for key, val in self.Parms().iteritems())))

  def __repr__(self):
    return re.sub('[a-z]', '', self.__class__.__name__ +
                  '*' * self.is_breaking) + self.ReprParms()

  def OptLayout(self, rest_of_line):
    """Retrieve or compute the least-cost (optimum) layout for this block.

    Args:
      rest_of_line: a Solution object representing the text to the right of
        this block.
    Returns:
      A Solution object representing the optimal layout for this block and
      the rest of the line.
    """
    # Deeply-nested choice block may result in the same continuation supplied
    # repeatedly to the same block. Without memoisation, this may result in an
    # exponential blow-up in the layout algorithm.
    if rest_of_line not in self.layout_cache:
      self.layout_cache[rest_of_line] = self.DoOptLayout(rest_of_line)
    return self.layout_cache[rest_of_line]

  def DoOptLayout(self, rest_of_line):
    """Compute the least-cost (optimum) layout for this block.

    Args:
      rest_of_line: a Solution object representing the text to the right of
        this block.
    Returns:
      A Solution object representing the optimal layout for this block and
      the rest of the line.
    """
    # Abstract method.
    pass

  def PrintOn(self, outp):
    """Print the contents of this block with the optimal layout.

    Args:
      outp: a stream on which output is to be printed.
    """
    soln = self.OptLayout(None)
    support.Console(outp).PrintLayout(soln.layouts[0])


class TextBlock(LayoutBlock):
  """A block containing a single unbroken string."""

  def __init__(self, text, is_breaking=False):
    super(TextBlock, self).__init__(is_breaking)
    self.text = text

  def __repr__(self):
    return '*' * self.is_breaking + self.text

  def DoOptLayout(self, rest_of_line):
    span = len(self.text)
    layout = support.Layout([support.LayoutElement.String(self.text)])
    # The costs associated with the layout of this block may require 1, 2 or 3
    # knots, depending on how the length of the text compares with the two
    # margins (m0 and m1) in _options. Note that we assume
    # _options.m1 >= _options.m0 >= 0, as asserted in base.Options.Check().
    if span >= _options.m1:
      s = support.Solution([0], [span],
                           [(span - _options.m0) * _options.c0 +
                            (span - _options.m1) * _options.m1],
                           [_options.c0 + _options.c1], [layout])
    elif span >= _options.m0:
      s = support.Solution([0, _options.m1 - span], [span] * 2,
                           [(span - _options.m0) * _options.c0,