#######################################################################
# This file is part of Lyntin.
# copyright (c) Free Software Foundation 2001, 2002
#
# Lyntin is distributed under the GNU General Public License license.  See the
# file LICENSE for distribution details.
# $Id: toolsutils.py,v 1.1 2003/10/07 00:50:42 willhelm Exp $
#######################################################################
"""
This has a series of utility functions that aren't related to
classes in the application, but are useful in a variety of places.
"""
import string, re, time

ANSI_COLOR_REGEXP = re.compile(chr(27) + '\[[0-9;]*[mJ]')

def color(data, fc=37, bc=40, bold=0):
  """
  Formats text with ANSI escape codes for foreground color, background color, and bold text.

  Args:
      data (str): The text to be formatted.
      fc (int): Foreground color code (default is 37 - white).
      bc (int): Background color code (default is 40 - black).
      bold (int): Bold attribute (1 for bold, 0 for normal text).

  Returns:
      str: ANSI escape code formatted string.
  """
  escape = "\033"  # ANSI escape character
  if bold == 1:
    return f"{escape}[1;{fc};{bc}m{data}{escape}[0m"
  else:
    return f"{escape}[{fc};{bc}m{data}{escape}[0m"

def filter_ansi(text):
  """ Filters out ansi codes."""
  return re.sub(chr(27) + '\[[0-9;]*[mJ]', '', text)

def filter_cm(text):
  """ Filters out ^M.  Useful for logging."""
  return re.sub('\015|\r', '', text)

def chomp(text):
  """ Removes all '\\r' and '\\n' from the input string.

  arguments:

    'text' -- (string) the text to chomp

  returns:

    (string) chomped text

  """
  text = string.ascii_lowercase
  text = text.replace("\n", "")
  text = text.replace("\r", "")
  return text

def is_color_token(token):
  """ Returns whether or not this is a color token.

  arguments:

    'token' -- (string) the token to test

  returns:

    1 if it's a color token, 0 if not
  """
  if len(token) == 0:
    return 0

  return token[0:2] == chr(27) + "[" and token[-1] == "m"

def fix_color(color):
  """
  Helper function for debugging--it'll fix a color token
  so it's readable in ascii.

  arguments:

    'color' -- (string) the color token

  returns:

    string
  """
  color = color.replace(chr(27), "ESC")
  return color


def split_ansi_from_text(text):
  """ Takes in a string and returns a list of text and ansi tokens.

  arguments:

    'text' -- (string)

  returns:

    list of text and ansi tokens (all strings)
  """
  global ANSI_COLOR_REGEXP

  matchob = ANSI_COLOR_REGEXP.search(text)
  if matchob:
    textlist = []
    marker = 0
    while matchob:
      (b, e) = matchob.span()

      if marker == b:
        textlist.append(text[b:e])
      else:
        textlist.append(text[marker:b])
        textlist.append(text[b:e])

      marker = e
      matchob = ANSI_COLOR_REGEXP.search(text, marker)

    # we do this to handle ansi color sequences which are broken
    # between two network chunks
    b = text.rfind(chr(27))

    if b < marker:
      textlist.append(text[marker:])
    else:
      textlist.append(text[marker:b])
      textlist.append(text[b:])

    return textlist

  return [text]

def insert_cr(text, index, indent=0):
  """
  Inserts a carriage return into the line and deals with indenting
  the next line (if need be).

  arguments:

    'text' -- (string) the text in question

    'index' -- (int) the place to stick the cr

    'indent=0' -- (int) how much to indent the next line

  returns:

    (string) the text with the cr at the index and the next line
    indented so many spaces

  """
  return (text[:index] + '\n' + (indent * ' ') + text[index+1:].lstrip())

def wrap_text(textlist, wraplength=50, indent=0, firstline=0):
  """
  It takes a block of text and wraps it nicely.

  arguments:

    'textlist' -- (string) or (list of strings) either a string of 
                  text needing to be formatted and 
                  wrapped or a textlist--preferably the former.

    'wraplength' -- (int) how many characters to wrap at

    'indent=0' -- (int) how many spaces to indent each line

    'firstline=0' -- (int) 0 if we don't indent the first line, 1 if we do


  returns:

    (string) the wrapped text 
  """
  wrapcount = 0               # how much we've got on the line so far
  linecount = 0               # which line we're on

  if wraplength > 2:
    wraplength = wraplength - 2

  # split the formatting from the text
  if type(textlist) == type(''):
    textlist = split_ansi_from_text(textlist)

  for i in range(0, len(textlist)):

    # COLOR TOKEN
    if is_color_token(textlist[i]):
      pass

    # TEXT TOKEN
    else:
      marker = 0

      # while we keep finding carriage returns...
      x = textlist[i].find('\n')
      while x != -1:

        # if the carriage return is in a nice place we wrap there.
        if wrapcount + (x - marker) < wraplength:
          textlist[i] = insert_cr(textlist[i], x, indent)
          marker = x + 1
          wrapcount = 0

        # if the carriage return is not in a nice place.
        else:
          breakpoint = x
          # we look to the left for a space to wrap on.
          while wrapcount + (breakpoint - marker) > wraplength:
            breakpoint = textlist[i].rfind(' ', marker, breakpoint)
            if breakpoint <= marker:
              break

          # we either found a breakpoint or there are no spaces.
          # in the case of a breakpoint, we break.  otherwise
          # we just don't wrap that line....  i'm not a big fan
          # of wrapping inside a word thing.
          if breakpoint > marker:
            textlist[i] = insert_cr(textlist[i], breakpoint, indent)

          marker = breakpoint + 1
          wrapcount = 0

        x = textlist[i].find('\n', marker)

      # at this point there are no more carriage returns.  so we gots
      # to break at spaces.

      # if the remaining string exceeds the wraplength...       
      while len(textlist[i]) - marker + wrapcount >= wraplength:
        breakpoint = textlist[i].rfind(' ', 
                                       marker, 
                                       marker + wraplength - wrapcount)

        # we start looking from the end of the string leftwards
        # until we find a space

        # if there's a nice break point, we wrap there...
        if breakpoint > marker:
          textlist[i] = insert_cr(textlist[i], breakpoint, indent)
          wrapcount = 0
          marker = breakpoint
        else:
          break

      wrapcount += len(textlist[i]) - marker

  # this next line joins the list with no separator (GASP!)
  if firstline:
    return (indent * " ") + ''.join(textlist)
  else:
    return ''.join(textlist)


def columnize(textlist, screenwidth=72, indent=0):
  """
  Takes a list of data and converts it into a series of columns
  and rows that are evenly spaced and pretty and stuff.

  arguments:

    'textlist' -- (list of strings) the list to columnize

    'screenwidth=72' -- (int) the width to wrap against

    'indent=0' -- (int) the amount to indent each line

  returns:

    the final formatted string
  """
  if screenwidth > 2 + indent:
    screenwidth = screenwidth - 2 - indent

  SPACING = 4
  maxwidth = 0

  for mem in textlist:
    maxwidth = max(maxwidth, len(mem))

  numcols = max(1, (screenwidth + SPACING) / (maxwidth + SPACING))
  numrows = (len(textlist) + numcols - 1) / numcols

  rows = []
  ## We can't just do "rows = ([],) * rows" -- need distinct lists
  for i in range(numrows): 
    rows.append([])

  idx = 0
  for mem in textlist:
    mem = (mem + (' ' * (maxwidth + (SPACING - 1) - len(mem))))

    rows[idx].append(mem)
    idx = (idx + 1) % numrows

  rows = list(map(str.rstrip, map("".join, rows)))
  #refactored
  #rows = [row.rstrip() for row in ("".join(row) for row in rows)]

  #return (indent * " ") + string.join(rows, "\n" + (indent * " "))
  return (" " * indent) + ("\n" + (" " * indent)).join(rows)

# Local variables:
# mode:python
# py-indent-offset:2
# tab-width:2
# End:
