# a4.py
# ........   SOLUTIONS ..........
# By Prof. Lee and Will Xiao, Apr 2020

import a3_classes

"""Functions for processing nested todo lists made up of Tasks and lists of
    Tasks.

    Technical point (don't sweat the details too much here.)
    We assume all (possibly nested, possibly empty) Tasks lists are well-formed,
    meaning, informally, speaking, that they are created by repeatedly making
    up a higher-order Task lists by combining sublists that don't have any
    common Tasks between them, and there's no repeated Tasks within a single list.
    Any sublists (or subsublists, or ...) are allowed to be empty.

    This way, you don't have to worry about things like Tasks occurring more
    than once in the same list
        Illegal for A4, but allowable Python:
            t = Task('a', 1)
            badlisthelper = [t, Task('b'), 2]
            badlist = [t, badlisthelper]
    or lists that contain themselves
        Illegal for A4, but allowable Python
            looplist = [Task('weird', 1)]
            looplist.append(looplist)

"""



# Students, no need to modify this helper function.
def task_to_string(t):
    """
    Returns: string of the form
        <name>[:6]: <lh>
    where t's name is <name> and length is <lh>,
    with spaces padding out the name if necessary.

    Preconditions: t is a Task (and not None)

    Examples:
        task_to_string(a3_classes.Task("write masterpiece", 10))
        --->  "write : 10"
        task_to_string(a3_classes.Task("A5", 20))
        --->  "A5    : 20"
    """
    return (t.name+' '*6)[:6] +  ": " + str(t.length)



def sum_hours(tlist):
    """Returns the number of hours it would take to finish all the tasks contained
    in the (possibly empty, possibly nested) Task list tlist.
    If tlist is empty, returns 0.

    Precondition: tlist is a list, possibly empty and possibly nested, of Tasks,
    and the same is true of any of its sublists, or subsublists, or ...

    Examples:
        owe_s1 = [a3_classes.Task('camera-ready revisions', 8),
                    a3_classes.Task('revise slides', 1)]
        owe_s2 = [a3_classes.Task('review proof', 3)]
        owe_s3 = []
        owe_students = [owe_s1, owe_s2, owe_s3]
        research_todos = [a3_classes.Task("NSF proposal response", 2),
                  owe_students,
                  a3_classes.Task("Friday practice-talk feedback", 1)]

        Then,
            owe_students --> 12
            research_todos --> 15
    """
    # Experience note: the parameter is named `tlist` instead of the
    # shorter "tl" to avoid confusion between "tl" (tee-ell) vs "t1" (tee-one).
    # A recommendation is to avoid having lower-case l ("ell") be the last
    # character in your variable names; tracking down bugs where Python is
    # claiming that "tl"/"t1" is undefined when you KNOW you defined "t1"/"tl"
    # higher up in your code is infuriating, but sadly not uncommon.
    # (Alternately, choose a font where ones and lower-case-ells are easy to
    # distinguish.)


    # A TYPICAL SOLUTION
    if tlist == []:
        return 0
    if type(tlist[0]) == a3_classes.Task:   # later in course, we learned to use isinstance() instead
        return tlist[0].length + sum_hours(tlist[1:])
    else:
        return sum_hours(tlist[0]) + sum_hours(tlist[1:])

    # COMPACT (!= PREFERRED) VERSION OF THE ABOVE
    if tlist == []:
        return 0
    first = tlist[0]
    return (first.length if type(first) != list else sum_hours(first)) + sum_hours(tlist[1:])


    # ALTERNATE SOLUTION
    sum = 0 # number of hours seen so far
    if tlist == []:
        return sum

    # Handle the first item of tlist
    firstitem = tlist[0]
    if type(firstitem) == a3_classes.Task:  # again, isinstance() is preferred later in the course
        sum += firstitem.length
    else:
        sum += sum_hours(firstitem)

    # Add in the sum for the rest of the list
    sum += sum_hours(tlist[1:])
    return sum



    # RECURSIVE SOLUTION WITH A FOR-LOOP
    if tlist == []:
        return 0

    sum = 0
    for item in tlist:
        if isinstance(item, list):  
            sum += sum_hours(item)
        else:
            sum += item.length
    return sum




def todo_list_to_string(tlist):
    """Returns a string version of a (possibly nested, possibly empty) list of
    todo items tlist,
    each task on a separate line printed via task_to_string,
    each list, including the input `tlist` itself, delimited with a starting string
    'BEGIN\n' and ending string 'END'
    and each (sub)list's contents indented by one tab ('\t') per embedding level.
    When printed, this string shows the indent levels visually.

    Precondition: tlist is a list, possibly empty and possibly nested, of Tasks,
    and the same is true of any of its sublists, or subsublists, or ...


    Example:
    Suppose we set up `owe_students` as follows:
    owe_s1 = [a3_classes.Task('camera-ready revisions', 8),
                    a3_classes.Task('revise slides', 1)]
    owe_s2 = [a3_classes.Task('review proof', 3)]
    owe_s3 = []
    owe_students = [owe_s1, owe_s2, owe_s3]

    Then, the following very long string (broken up by "+" for readability) is
    the result of todo_list_to_string(owe_students):
    'BEGIN\n'
    +'\tBEGIN\n'
    +'\t\tcamera: 8\n'
    +'\t\trevise: 1\n'
    +'\tEND\n'
    +'\tBEGIN\n'
    +'\t\treview: 3\n'
    +'\tEND\n'
    +'\tBEGIN\n'
    +'\tEND\n'
    +'END'
    which, if printed via print(...), looks like this (yes, there's an empty list):
    BEGIN
        BEGIN
            camera: 8
            revise: 1
        END
        BEGIN
            review: 3
        END
        BEGIN
        END
    END
    """
 
    outstring = 'BEGIN\n'
    for item in tlist:
        if isinstance(item, list):
            # We are dealing with a sublist
            itemstr = todo_list_to_string(item)
        else:
            # Students do not need to have assert statements
            assert isinstance(item, a3_classes.Task), "todo list item is not Task for list"
            itemstr = task_to_string(item)
            
        # Everything is a subitem, and so should be indented
        # Also must end on a newline so the next item can start on a new line
        outstring += (indentlines(itemstr) + '\n')
    outstring += 'END'

    return outstring



def indentlines(line_str):
    """Returns a new string where each line in line_str has been indented by a tab
    character ('\t').

    Examples:
        'abc'   -->  '\tabc'
        'abc\ndef\nghi'  -->  '\tabc\n\tdef\n\tghi'
        '\tabc\n\tdef\n\tghi' -->    '\t\tabc\n\t\tdef\n\t\tghi'

    This function has the following nice effect, useful for other functions in A4.
    >>> from a4 import indentlines
    >>> s = 'abc\ndef\nghi'
    >>> print(s)
    abc
    def
    ghi
    >>> print(indentlines(s))
        abc
        def
        ghi
    >>> print(indentlines(indentlines(s)))
            abc
            def
            ghi

    Precondition: line_str is a non-empty string with no trailing whitespace
    (no tabs, newlines, or spaces at the end).
    Lines within line_str are delimited by the newline character '\n'.
    None of the lines in line_str are empty or contain only whitespace
    """

    # A for-loop solution
    out = '\t'
    for c in line_str:
        out += c
        if c == '\n':
            out += '\t'
    return out

    # An alternate one-liner
    return '\t' + '\n\t'.join(line_str.split('\n'))


