"""
This module contains a binary search function.  It also contains a utility for
debugging a binary search and a search function which uses that utility to
visualize the search process.

Author: Zachary Palmer
Date: 2015-10-28
"""

import math

# Commenting out the binary search function.  Students will need to implement
# this in class.
# def binary_search(lst, element):
#   """
#   Performs a binary search on the provided list.
#   Parameters:
#     lst - The list to search.  This list must be sorted.
#     element - The element to search for.
#   Returns:
#     The index of the element, or None if the element is not found.
#   """
#   # Left is the leftmost (inclusive) element we want to consider.
#   left = 0
#   # Right is the rightmost (inclusive) element we want to consider.
#   right = len(lst) - 1
#   # Keep searching until there's nothing left to search.
#   while left <= right:
#     middle = (left + right) / 2
#     if lst[middle] == element:
#       return middle
#     elif lst[middle] < element:
#       left = middle + 1
#     elif lst[middle] > element:
#       right = middle - 1
#   return None

def binary_search_debug_printout(lst, element, left, right, middle=None):
  """
  A tool to help when developing a binary search.  This function visualizes the
  state of a binary search by showing the list, highlighting the search element,
  and indicating the positions of the left, right, and middle values.  The
  middle value is optional; if it is not provided, no middle index will be
  shown.
  """
  # First, we determine the printed width of each list element.
  width = 1
  for x in lst:
    width = max(width,len(str(x)))
  # Leave room for the list indices
  width = max(width,math.ceil(math.log(len(lst),10)))
  # Leave room for the left, middle, and right markers
  width = max(width,3)
  # Show a status line telling what we want
  print "Searching for %s..." % element
  # Build ASCII-art for the top and bottom lines of the list.
  cap_line = "+" + ("-" * width + "+") * len(lst)
  print cap_line
  # Print the list contents
  list_line = "|"
  for x in lst:
    if x == element:
      # Add terminal code for yellow, bold
      list_line += '\033[93m\033[1m'
    list_line += (str(x).center(width))
    if x == element:
      # Add terminal code for text reset
      list_line += '\033[0m'
    list_line += "|"
  print list_line
  print cap_line
  # Print the indices of the list
  indices_line = ""
  for i in range(len(lst)):
    indices_line += " "
    indices_line += str(i).center(width)
  print indices_line
  # Show the markers under the indices.
  marker_line = ""
  for i in range(len(lst)):
    marker_line += " "
    marker_group = ""
    for (n,c) in [(left,'L'),(middle,'M'),(right,'R')]:
      if n == i:
        marker_group += c
    marker_line += marker_group.center(width)
  print marker_line

def binary_search_with_debug(lst, element):
  """
  Illustrates a binary search using debugging output.  To show how a binary
  search works, this function will print the intermediate steps of the search
  as well as wait for keystrokes as the search proceeds.  (Normally, this sort
  of function should not print or input anything; it would just do the search!)
  Parameters:
    lst - The list to search.  This list must be sorted.
    element - The element to search for.
  Returns:
    The index of the element, or None if the element is not found.
  """
  # Left is the leftmost (inclusive) element we want to consider.
  left = 0
  # Right is the rightmost (inclusive) element we want to consider.
  right = len(lst) - 1
  # Keep searching until there's nothing left to search.
  while left <= right:
    # Show debug
    binary_search_debug_printout(lst, element, left, right)
    # Wait for input
    raw_input()
    # Show middle
    middle = (left + right) / 2
    binary_search_debug_printout(lst, element, left, right, middle)
    # Wait for input
    raw_input()
    # Move on
    if lst[middle] == element:
      print "Found result at index %d" % middle
      return middle
    elif lst[middle] < element:
      left = middle + 1
    elif lst[middle] > element:
      right = middle - 1
  print "Element not found in list!"
  return None


