#
# Copyright (c) 2018 Red Hat, Inc.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
# 
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
# 
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA. 
#

"""
  DisplaySpec

  $Id: //eng/vdo-releases/magnesium/src/python/vdo/statistics/StatFormatter.py#1 $
"""

import sys

class StatFormatter(object):
  """
  An object which formats labeled values. Formatters are able to
  represent all of the verbose formatting for vdoStats and vdoMonitor
  (the df-style formatting in vdoStats is done by hand).

  A formatter is specified by supplying a description of the format for each
  level of the hierarchy of labeled values. If the values have more levels than
  the formatter has specified, the final level specification will be applied
  to all remaining value levels.

  A level specification is specified as a hash with any of the following keys:

    displayFilter: A function which takes a LabeledValue and return True if
                   that value (and its children) should be displayed. Defaults
                   to a tautology.
    indent:        The additional indentation to apply from this level down.
                   Defaults to no additional indentation.
    joiner:        The string to use for joining label-value pairs at the
                   current level. In YAML mode, defaults to a newline,
                   otherwise defaults to a space.
    namer:         The way of naming the current level. Can be any of these:
                   None:   Don't name this level
                   list:   A list of indexes into the value list at this
                           level, the name of the level will be the
                           concatenation of the values at those indexes.
                           If the first element of the list is a string, it
                           will be used as a prefix for the name.
                   '=':    Use the label of the value at this level as the
                           name, use an equals sign to connect the name to the
                           values
                   '+':    Use the label of the value at this level as the
                           name, regardless of the formatter's mode
                   string: Any other string will be used as the name
                   True:   Any other true value will use the label as the name
                           if the mode indicates that the level should be named
                           and otherwise won't name the level
                           (non-hierarchical, multivalued levels don't get
                           named).
  """
  def __init__(self, displayLevels, hierarchical=True, yaml=True):
    """
    Create a new formatter.

    :param displayLevels: An array of hashes describing how to format each
                          level of the labled values to be formatted.
    :param hierarchical:  If True, indentation will be increased at each level
                          otherwise, indentation will only be modified at
                          levels which specify the 'indent' parameter. Defaults
                          to True.
    :param yaml:          Whether to output as YAML or not. Defaults to True.
    """
    self.hierarchical  = hierarchical
    self.yaml          = yaml
    self.spec          = None

    # build the DisplaySpecs from the bottom up to set child pointers.
    displayLevels.reverse()
    for level in displayLevels:
      self.spec = DisplaySpec(self, self.spec, level)

    # walk back down setting the indentation
    s = self.spec
    indent = ''
    while (True):
      indent = s.setIndentation(indent)
      if s.child is None:
        # Set the child of the bottom level to be itself
        s.child = s
        break
      s = s.child

  def format(self, lv):
    """
    Format labeled values.

    :param lv: The values to format

    :return: The formatted value string
    """
    return self.spec.format(lv)

  def output(self, lv):
    """
    Format labeled values and print the result.

    :param lv: The values to format
    """
    try:
      print self.format(lv)
      sys.stdout.flush()
    except IOError:
      # Someone must have closed stdout. Don't die.
      pass

class DisplaySpec(object):
  """
  An object which formats a single level of labeled values.
  """
  def __init__(self, formatter, child, parameters):
    """
    Create a new display specification.

    :param formatter  : The formatter which owns this specification.
    :param child      : The formatter for the next level down.
    :param parameters : A dict which defines the format at this level. Valid
                        keys are documented in the header of this file.
    """
    self.formatter       = formatter
    self.parent          = None
    self.child           = child
    self.displayFilter   = parameters.get('displayFilter', lambda(lv): True)
    self.indent          = parameters.get('indent', '')
    self.joiner          = parameters.get('joiner',
                                          "\n" if formatter.yaml else ' ')
    self.nameJoiner      = parameters.get('nameJoiner',
                                          ': ' if formatter.yaml else ' ')
    self.namer           = self._setNamer(parameters.get('namer'))
    self.width           = None
    self.subWidth        = None
    if child:
      child.parent = self

  def _setNamer(self, nameType):
    """
    Set up the namer for this level from the specification.

    :param nameType: The type of namer to use

    :return: The specified namer function
    """
    if not nameType:
      return lambda lv: None

    if isinstance(nameType, list):
      if isinstance(nameType[0], str):
        name = [nameType[0], nameType[1:]]
      else:
        name = ['', nameType]
      return lambda lv: (name[0]
                         + ' '.join(str(lv.value[n].value) for n in name[1]))

    if (isinstance(nameType, str)):
      if (nameType == '='):
        self.nameJoiner = '='
        return lambda lv: None if lv.isMultiValued() else lv.label

      if (nameType == '+'):
        return lambda lv: lv.label

      return lambda lv: nameType

    if self.formatter.hierarchical:
      return lambda lv: lv.label

    return lambda lv: None if lv.isMultiValued() else lv.label

  def getName(self, lv):
    """
    Get the name for a labeled value.

    :param lv: The value to name

    :return: The name for the value or None if the value should not be named
    """
    name = self.namer(lv)
    if not name:
      return None

    if self.formatter.yaml:
      nameWidth = max(self.width, len(name)) + 1
      return "{0}{1:<{2}}{3}{4}".format(self.indent, name, nameWidth,
                                        self.nameJoiner,
                                        "\n" if lv.isMultiValued() else '')

    return self.indent + name + self.nameJoiner

  def setIndentation(self, parentIndent):
    """
    Set the indentation for this level.

    :param parentIndent: The indentation of the level above us
    """
    self.indent = parentIndent + self.indent
    return self.indent

  def setWidth(self, lv):
    """
    Set the width of the labels at this level.

    :param lv: The value being formatted
    """
    if self.width:
      return

    if self.formatter.hierarchical:
      self.width    = self.parent.subWidth if self.parent else lv.width()
      self.subWidth = lv.subWidth(True)
      return

    if self.parent:
      self.width = self.subWidth = self.parent.subWidth
      return

    self.width    = lv.width()
    self.subWidth = lv.subWidth(False)

  def format(self, lv):
    """
    Recursively format a labeled value.

    :param lv: The value to format

    :return: The formatted value string
    """
    if not self.displayFilter(lv):
      return None

    self.setWidth(lv)
    name  = self.getName(lv)
    value = lv.format(self.child, self.joiner)
    return name + value if name else value
