# textGridworldDisplay.py
# -----------------------
# Licensing Information: Please do not distribute or publish solutions to this
# project. You are free to use and extend these projects for educational
# purposes. The Pacman AI projects were developed at UC Berkeley, primarily by
# John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu).
# For more info, see http://inst.eecs.berkeley.edu/~cs188/sp09/pacman.html

import util

class TextGridworldDisplay:
  
  def __init__(self, gridworld):
    self.gridworld = gridworld
  
  def start(self):
    pass
  
  def pause(self):
    pass
  
  def displayValues(self, agent, currentState = None, message = None):
    if message != None:
      print message
    values = util.Counter()
    policy = {}
    states = self.gridworld.getStates()
    for state in states:
      values[state] = agent.getValue(state)
      policy[state] = agent.getPolicy(state)
    prettyPrintValues(self.gridworld, values, policy, currentState)
  
  def displayNullValues(self, agent, currentState = None, message = None):
    if message != None: print message
    prettyPrintNullValues(self.gridworld, currentState)

  def displayQValues(self, agent, currentState = None, message = None):
    if message != None: print message
    qValues = util.Counter()
    states = self.gridworld.getStates()
    for state in states:
      for action in self.gridworld.getPossibleActions(state):
        qValues[(state, action)] = agent.getQValue(state, action)
    prettyPrintQValues(self.gridworld, qValues, currentState)


def prettyPrintValues(gridWorld, values, policy=None, currentState = None):
  grid = gridWorld.grid
  maxLen = 11
  newRows = []
  for y in range(grid.height):
    newRow = []
    for x in range(grid.width):
      state = (x, y)
      value = values[state]
      action = None
      if policy != None and state in policy:
        action = policy[state]          
      actions = gridWorld.getPossibleActions(state)        
      if action not in actions and 'exit' in actions:
        action = 'exit'
      valString = None
      if action == 'exit':
        valString = border('%.2f' % value)
      else:
        valString = '\n\n%.2f\n\n' % value
        valString += ' '*maxLen
      if grid[x][y] == 'S':
        valString = '\n\nS: %.2f\n\n'  % value
        valString += ' '*maxLen        
      if grid[x][y] == '#':
        valString = '\n#####\n#####\n#####\n'
        valString += ' '*maxLen
      pieces = [valString]                
      text = ("\n".join(pieces)).split('\n')        
      if currentState == state:
        l = len(text[1])
        if l == 0:
          text[1] = '*'
        else:
          text[1] = "|" + ' ' * int((l-1)/2-1) + '*' + ' ' * int((l)/2-1) + "|"       
      if action == 'east':
        text[2] = '  ' + text[2]  + ' >'
      elif action == 'west':
        text[2] = '< ' + text[2]  + '  '
      elif action == 'north':
        text[0] = ' ' * int(maxLen/2) + '^' +' ' * int(maxLen/2)
      elif action == 'south':
        text[4] = ' ' * int(maxLen/2) + 'v' +' ' * int(maxLen/2)
      newCell = "\n".join(text)
      newRow.append(newCell)
    newRows.append(newRow)
  numCols = grid.width
  for rowNum, row in enumerate(newRows):
    row.insert(0,"\n\n"+str(rowNum))
  newRows.reverse()
  colLabels = [str(colNum) for colNum in range(numCols)]
  colLabels.insert(0,' ')
  finalRows = [colLabels] + newRows
  print indent(finalRows,separateRows=True,delim='|', prefix='|',postfix='|', justify='center',hasHeader=True)


def prettyPrintNullValues(gridWorld, currentState = None):
    grid = gridWorld.grid
    maxLen = 11
    newRows = []
    for y in range(grid.height):
      newRow = []
      for x in range(grid.width):
        state = (x, y)

        # value = values[state]

        action = None
        # if policy != None and state in policy:
        #   action = policy[state]
        # 
        actions = gridWorld.getPossibleActions(state)

        if action not in actions and 'exit' in actions:
          action = 'exit'

        valString = None
        # if action == 'exit':
        #   valString = border('%.2f' % value)
        # else:
        #   valString = '\n\n%.2f\n\n' % value
        #   valString += ' '*maxLen

        if grid[x][y] == 'S':
          valString = '\n\nS\n\n'
          valString += ' '*maxLen
        elif grid[x][y] == '#':
          valString = '\n#####\n#####\n#####\n'
          valString += ' '*maxLen
        elif type(grid[x][y]) == float or type(grid[x][y]) == int:
          valString = border('%.2f' % float(grid[x][y]))
        else: valString = border('  ')
        pieces = [valString]

        text = ("\n".join(pieces)).split('\n')

        if currentState == state:
          l = len(text[1])
          if l == 0:
            text[1] = '*'
          else:
            text[1] = "|" + ' ' * int((l-1)/2-1) + '*' + ' ' * int((l)/2-1) + "|"

        if action == 'east':
          text[2] = '  ' + text[2]  + ' >'
        elif action == 'west':
          text[2] = '< ' + text[2]  + '  '
        elif action == 'north':
          text[0] = ' ' * int(maxLen/2) + '^' +' ' * int(maxLen/2)
        elif action == 'south':
          text[4] = ' ' * int(maxLen/2) + 'v' +' ' * int(maxLen/2)
        newCell = "\n".join(text)
        newRow.append(newCell)
      newRows.append(newRow)
    numCols = grid.width
    for rowNum, row in enumerate(newRows):
      row.insert(0,"\n\n"+str(rowNum))
    newRows.reverse()
    colLabels = [str(colNum) for colNum in range(numCols)]
    colLabels.insert(0,' ')
    finalRows = [colLabels] + newRows
    print indent(finalRows,separateRows=True,delim='|', prefix='|',postfix='|', justify='center',hasHeader=True)
  
def prettyPrintQValues(gridWorld, qValues, currentState=None):
    grid = gridWorld.grid
    maxLen = 11
    newRows = []
    for y in range(grid.height):
      newRow = []
      for x in range(grid.width):
        state = (x, y)
        actions = gridWorld.getPossibleActions(state)
        if actions == None or len(actions) == 0:
          actions = [None]
        bestQ = max([qValues[(state, action)] for action in actions])
        bestActions = [action for action in actions if qValues[(state, action)] == bestQ]
    
        # display cell
        qStrings = dict([(action, "%.2f" % qValues[(state, action)]) for action in actions])
        northString = ('north' in qStrings and qStrings['north']) or ' '
        southString = ('south' in qStrings and qStrings['south']) or ' '
        eastString = ('east' in qStrings and qStrings['east']) or ' '
        westString = ('west' in qStrings and qStrings['west']) or ' '
        exitString = ('exit' in qStrings and qStrings['exit']) or ' '

        eastLen = len(eastString)
        westLen = len(westString)
        if eastLen < westLen:
          eastString = ' '*(westLen-eastLen)+eastString
        if westLen < eastLen:
          westString = westString+' '*(eastLen-westLen)
    
        if 'north' in bestActions:
          northString = '/'+northString+'\\'
        if 'south' in bestActions:
          southString = '\\'+southString+'/'
        if 'east' in bestActions:
          eastString = ''+eastString+'>'
        else:
          eastString = ''+eastString+' '
        if 'west' in bestActions:
          westString = '<'+westString+''
        else:
          westString = ' '+westString+''
        if 'exit' in bestActions:
          exitString = '[ '+exitString+' ]'

    
        ewString = westString + "     " + eastString
        if state == currentState:
          ewString = westString + "  *  " + eastString
        if state == gridWorld.getStartState():
          ewString = westString + "  S  " + eastString
        if state == currentState and state == gridWorld.getStartState():
          ewString = westString + " S:* " + eastString
    
        text = [northString, "\n"+exitString, ewString, ' '*maxLen+"\n", southString]
    
        if grid[x][y] == '#':
          text = ['', '\n#####\n#####\n#####', '']
    
        newCell = "\n".join(text)
        newRow.append(newCell)
      newRows.append(newRow)
    numCols = grid.width
    for rowNum, row in enumerate(newRows):
      row.insert(0,"\n\n\n"+str(rowNum))
    newRows.reverse()
    colLabels = [str(colNum) for colNum in range(numCols)]
    colLabels.insert(0,' ')
    finalRows = [colLabels] + newRows

    print indent(finalRows,separateRows=True,delim='|',prefix='|',postfix='|', justify='center',hasHeader=True)

def border(text):    
  length = len(text)
  pieces = ['-' * (length+2), '|'+' ' * (length+2)+'|', ' | '+text+' | ', '|'+' ' * (length+2)+'|','-' * (length+2)]
  return '\n'.join(pieces)
    
# INDENTING CODE

# Indenting code based on a post from George Sakkis
# (http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/267662)

import cStringIO,operator

def indent(rows, hasHeader=False, headerChar='-', delim=' | ', justify='left',
           separateRows=False, prefix='', postfix='', wrapfunc=lambda x:x):
    """Indents a table by column.
       - rows: A sequence of sequences of items, one sequence per row.
       - hasHeader: True if the first row consists of the columns' names.
       - headerChar: Character to be used for the row separator line
         (if hasHeader==True or separateRows==True).
       - delim: The column delimiter.
       - justify: Determines how are data justified in their column. 
         Valid values are 'left','right' and 'center'.
       - separateRows: True if rows are to be separated by a line
         of 'headerChar's.
       - prefix: A string prepended to each printed row.
       - postfix: A string appended to each printed row.
       - wrapfunc: A function f(text) for wrapping text; each element in
         the table is first wrapped by this function."""
    # closure for breaking logical rows to physical, using wrapfunc
    def rowWrapper(row):
        newRows = [wrapfunc(item).split('\n') for item in row]
        return [[substr or '' for substr in item] for item in map(None,*newRows)]
    # break each logical row into one or more physical ones
    logicalRows = [rowWrapper(row) for row in rows]
    # columns of physical rows
    columns = map(None,*reduce(operator.add,logicalRows))
    # get the maximum of each column by the string length of its items
    maxWidths = [max([len(str(item)) for item in column]) for column in columns]
    rowSeparator = headerChar * (len(prefix) + len(postfix) + sum(maxWidths) + \
                                 len(delim)*(len(maxWidths)-1))
    # select the appropriate justify method
    justify = {'center':str.center, 'right':str.rjust, 'left':str.ljust}[justify.lower()]
    output=cStringIO.StringIO()
    if separateRows: print >> output, rowSeparator
    for physicalRows in logicalRows:
        for row in physicalRows:
            print >> output, \
                prefix \
                + delim.join([justify(str(item),width) for (item,width) in zip(row,maxWidths)]) \
                + postfix
        if separateRows or hasHeader: print >> output, rowSeparator; hasHeader=False
    return output.getvalue()
    
import math
def wrap_always(text, width):
    """A simple word-wrap function that wraps text on exactly width characters.
       It doesn't split the text in words."""
    return '\n'.join([ text[width*i:width*(i+1)] \
                       for i in xrange(int(math.ceil(1.*len(text)/width))) ])
    
    
# TEST OF DISPLAY CODE
                                
if __name__ == '__main__':
  import gridworld, util

  grid = gridworld.getCliffGrid3()
  print grid.getStates()
  
  policy = dict([(state,'east') for state in grid.getStates()])
  values = util.Counter(dict([(state,1000.23) for state in grid.getStates()]))
  prettyPrintValues(grid, values, policy, currentState = (0,0))

  stateCrossActions = [[(state, action) for action in grid.getPossibleActions(state)] for state in grid.getStates()]
  qStates = reduce(lambda x,y: x+y, stateCrossActions, [])
  qValues = util.Counter(dict([((state, action), 10.5) for state, action in qStates]))
  qValues = util.Counter(dict([((state, action), 10.5) for state, action in reduce(lambda x,y: x+y, stateCrossActions, [])]))
  prettyPrintQValues(grid, qValues, currentState = (0,0))
