''' @file apg/lib/utilities.py
@brief A few APG utility functions'''
import sys
import os
from pprint import pprint


def string_to_tuple(string):
    '''Converts a string to a tuple of the
    Unicode values of the string characters.
    The APG parser requires a tuple of integers as input.
    However, many problems are defined in terms of ASCII
    or Unicode strings. This utility is a handy assist to get
    a string into the proper format for parser input.
    @param The string to convert.
    @return A tuple of the Unicode code points
    for each of the strings's characters.
    '''
    lst = []
    for c in string:
        lst.append(ord(c))
    return tuple(lst)


def tuple_to_string(input):
    '''Converts a tuple of Unicode values to the equivalent string.
    @param The tuple of Unicode values to convert.
    @return The converted string.
    '''
    string = ''
    for u in input:
        string += chr(u)
    return string


def tuple_to_ascii(input, map=None):
    '''Converts a tuple of Unicode values to an ASCII string.
    Non-ASCII characters are displayed by hexadecimal value.
    Used mainly for error reporting in the syntax phase.
    @param input The tuple of Unicode values to convert.
    @param map If map = [] a list of character display mappings is generated.
    map[index] = [offset, length] where<br>
      - index is the zero-based index of the character to map
      - offset is the offset from the beginning of the ASCII display
        to the first display gliph for the indexed character
      - length is the number of ASCII characters used to display
        the indexed character
    @return The converted string.
    '''
    def hex(char):
        if(char <= 0xff):
            s = '\\x%02X' % char
        elif(char <= 0xffff):
            s = '\\x%04X' % char
        elif(char <= 0xffffffff):
            s = '\\x%08X' % char
        else:
            s = '\\x%X' % char
        return s

    ascii = ''
    has_map = False
    if(map is not None):
        has_map = True
        offset = 0
        length = 0
    for u in input:
        if(u == 9):
            ascii += '\\t'
        elif(u == 10):
            ascii += '\\n'
        elif(u == 13):
            ascii += '\\r'
        elif(u < 32 or u > 127):
            ascii += hex(u)
        else:
            ascii += chr(u)
        if(has_map):
            length = len(ascii) - offset
            map.append(length)
            offset += length
    return ascii


def tuple_to_ascii_underline(map, index):
    '''Uses the (optional) map generated by @ref tuple_to_ascii()
    to generate a mapping of the display characters to the actual
    tuple (integer) characters.
    Used mainly for error reporting in the syntax phase.
    @param map A list of [offset, length] lists, one for each tuple
    character that indicates the offset into the ASCII display.
    and the length of the display of that specific character.
    @param index The index of the underlying tuple character to accent.
    @returns A string to underline the original with caret(s) to mark
    the accented character.
    '''
    underline = ''
    count = 0
    has_caret = False
    for length in map:
        if(count is index):
            underline += '^' * length
            has_caret = True
        else:
            underline += '-' * length
        count += 1
    if(has_caret is False):
        underline += '^'
    return underline


def pprint_to_string(obj, temp):
    '''Pretty prints an object to a temporary file,
    then reads the string back and returns it.
    @param obj The object to pretty print.
    @param temp The file name to use to save the printed string.
    It is deleted after use.
    @returns The pretty printed string or the exception on file error.
    - result['string'] is the displayed object on success
    - result['error'] is the exception raised on failure.
    '''
    stdout_save = sys.stdout
    result = {'string': '', 'error': None}
    try:
        fn = open(temp, 'w')
        sys.stdout = fn
        pprint(obj, sort_dicts=False)
        sys.stdout = stdout_save
        fn.close()
        fn = open(temp, 'r')
        result['string'] = fn.read()
        fn.close()
        os.remove(temp)
    except Exception as e:
        result['error'] = e
    if(sys.stdout is not stdout_save):
        # should not happen
        # but just in case make sure we restore sys.sdtout
        sys.stdout = stdout_save
    return result
