"""
This project lets you practice NESTED LOOPS (i.e., loops within loops)
in the context of SEQUENCES OF SUB-SEQUENCES.

Authors: David Mutchler, Vibha Alangar, Dave Fisher, Matt Boutell, Mark Hays,
         Mohammed Noureddine, Sana Ebrahimi, Sriram Mohan, their colleagues and
         PUT_YOUR_NAME_HERE.
"""  # TODO: 1. PUT YOUR NAME IN THE ABOVE LINE.

import time
import testing_helper


def main():
    """ Calls the other functions to test them. """
    print("-----------------------------------------------")
    print("Un-comment each of the following TEST functions")
    print("as you implement the functions that they test.")
    print("-----------------------------------------------")

    # run_test_multiply_numbers()
    # run_test_print_characters()
    # run_test_print_characters_slanted()


def run_test_multiply_numbers():
    """ Tests the   multiply_numbers   function. """
    # -------------------------------------------------------------------------
    # We have supplied tests for you. No additional tests are required,
    # although you are welcome to supply more tests if you choose.
    # -------------------------------------------------------------------------
    print()
    print("------------------------------------------------------")
    print("Testing the   multiply_numbers   function:")
    print("------------------------------------------------------")

    format_string = "    multiply_numbers( {} )"
    test_results = [0, 0]  # Number of tests passed, failed.

    # -------------------------------------------------------------------------
    # Test 1: Tests whether the function MUTATES the sub-lists correctly.
    seq_of_lists = ([4, 2, 1],
                    [8, 0],
                    [1, 2, 3, 4, 5],
                    [],
                    [101])
    # After the function call, seq_of_lists should be as follows:
    seq_of_lists_after_multiply = ([4, 2, 1],
                                   [16, 0],
                                   [3, 6, 9, 12, 15],
                                   [],
                                   [505])
    print_expected_result_of_test([seq_of_lists],
                                  seq_of_lists_after_multiply,
                                  test_results, format_string)
    actual = multiply_numbers(seq_of_lists)
    print_actual_result_of_test(seq_of_lists_after_multiply, seq_of_lists,
                                test_results)
    print("The above is for  seq_of_lists  (whose lists should be MUTATED.")

    # Test 2: (a continuation of Test 1)
    #   Tests whether the function does not RETURN a value (i.e., returns None)
    print_expected_result_of_test([seq_of_lists], None,
                                  test_results, format_string)
    print_actual_result_of_test(None, actual, test_results)
    print("The above is for the RETURNED VALUE, which should be")
    print("the constant None, NOT the STRING \"None\".")
    # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    # Test 3: Tests whether the function MUTATES the sub-lists correctly.
    seq_of_lists = ([4, 0, 100],
                    [1, 2, 3],
                    [100, 100, 20, 30])
    # After the function call, seq_of_lists should be as follows:
    seq_of_lists_after_multiply = ([4, 0, 100],
                                   [2, 4, 6],
                                   [300, 300, 60, 90])
    print_expected_result_of_test([seq_of_lists],
                                  seq_of_lists_after_multiply,
                                  test_results, format_string)
    actual = multiply_numbers(seq_of_lists)
    print_actual_result_of_test(seq_of_lists_after_multiply, seq_of_lists,
                                test_results)
    print("The above is for  seq_of_lists  (whose lists should be MUTATED.")

    # Test 4: (a continuation of Test 3)
    #   Tests whether the function does not RETURN a value (i.e., returns None)
    print_expected_result_of_test([seq_of_lists], None,
                                  test_results, format_string)
    print_actual_result_of_test(None, actual, test_results)
    print("The above is for the RETURNED VALUE, which should be")
    print("the constant None, NOT the STRING \"None\".")
    # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    # Test 5: Tests whether the function MUTATES the sub-lists correctly.
    seq_of_lists = (["the ", "rain "],
                    ["in spain ", "falls ", "mainly on the "],
                    ["plain."])
    # After the function call, seq_of_lists should be as follows:
    seq_of_lists_after_multiply = (["the ", "rain "],
                                   ["in spain in spain ",
                                    "falls falls ",
                                    "mainly on the mainly on the "],
                                   ["plain.plain.plain."])
    print_expected_result_of_test([seq_of_lists],
                                  seq_of_lists_after_multiply,
                                  test_results, format_string)
    actual = multiply_numbers(seq_of_lists)
    print_actual_result_of_test(seq_of_lists_after_multiply, seq_of_lists,
                                test_results)
    print("The above is for  seq_of_lists  (whose lists should be MUTATED.")

    # Test 6: (a continuation of Test 5)
    #   Tests whether the function does not RETURN a value (i.e., returns None)
    print_expected_result_of_test([seq_of_lists], None,
                                  test_results, format_string)
    print_actual_result_of_test(None, actual, test_results)
    print("The above is for the RETURNED VALUE, which should be")
    print("the constant None, NOT the STRING \"None\".")
    # -------------------------------------------------------------------------

    # -------------------------------------------------------------------------
    # Test 7: Tests whether the function MUTATES the sub-lists correctly.
    seq_of_lists = [[10, 5],
                    [10, 5, 5, 8, 20],
                    ["a", "b", "c"],
                    ["d"],
                    ["e", "f"],
                    [100, 0, "z"]]
    # After the function call, seq_of_lists should be as follows:
    seq_of_lists_after_multiply = [[10, 5],
                                   [20, 10, 10, 16, 40],
                                   ["aaa", "bbb", "ccc"],
                                   ["dddd"],
                                   ["eeeee", "fffff"],
                                   [600, 0, "zzzzzz"]]
    print_expected_result_of_test([seq_of_lists],
                                  seq_of_lists_after_multiply,
                                  test_results, format_string)
    actual = multiply_numbers(seq_of_lists)
    print_actual_result_of_test(seq_of_lists_after_multiply, seq_of_lists,
                                test_results)
    print("The above is for  seq_of_lists  (whose lists should be MUTATED.")

    # Test 8: (a continuation of Test 7)
    #   Tests whether the function does not RETURN a value (i.e., returns None)
    print_expected_result_of_test([seq_of_lists], None,
                                  test_results, format_string)
    print_actual_result_of_test(None, actual, test_results)
    print("The above is for the RETURNED VALUE, which should be")
    print("the constant None, NOT the STRING \"None\".")
    # -------------------------------------------------------------------------

    # SUMMARY of test results:
    print_summary_of_test_results(test_results)


def multiply_numbers(sequence_of_lists):
    """
    What comes in:
       -- a sequence of lists, with each item in the lists being something
          that can be multiplied by an integer
     What goes out: Nothing (i.e. None).
     Side effects: MUTATES the given lists by:
      -- multiplies each item of the first list by 1,
      -- multiplies each item of the second list by 2,
      -- multiplies each item of the third list by 3,
      -- and so forth.
      For example, consider the following code:
          seq_of_lists = ([4, 2, 1],
                          [8, 0],
                          [1, 2, 3, 4, 5],
                          [],
                          [101])
          v = multiply_numbers(seq_of_lists)
      After the above code runs,
          v (the returned value) should be None
          and  seq_of_lists  should be:
                       ([4, 2, 1],
                        [16, 0],
                        [3, 6, 9, 12, 15],
                        [],
                        [505])
    Type hints:
       :type sequence_of_lists:  sequence of lists, with each item
       in the lists being something that can be multiplied by an integer
       [FYI: The "can be multiplied ..." is an example of DUCK TYPING.]
    """
    # -------------------------------------------------------------------------
    # TODO: 2. Implement and test this function.
    #  ** READ THE TESTS that have been written for you (ABOVE).
    #  ** ASK QUESTIONS if you do not understand the TESTS (ABOVE).
    # -------------------------------------------------------------------------


def run_test_print_characters():
    """ Tests the    print_characters    function. """
    # -------------------------------------------------------------------------
    # We have supplied tests for you. No additional tests are required,
    # although you are welcome to supply more tests if you choose.
    # -------------------------------------------------------------------------
    print()
    print("------------------------------------------")
    print("Testing the   PRINT_CHARACTERS   function:")
    print("------------------------------------------")

    # Test 1:
    print()
    print("The following should be:")
    print("  h")
    print("  i")
    print("  b")
    print("  y")
    print("  e")
    print("  a")
    print("   ")
    print("  t")
    print("  i")
    print("  e")
    print("  !")
    print("but without the leading spaces:")
    print_characters(["hi", "bye", "a tie!"])

    # Test 2:
    print()
    print("The following should be:")
    print("  9876abc67 89z")
    print("but printed in a COLUMN, one character per line:")
    print_characters(["9876", "abc", "", "67 89", "z"])


def print_characters(sequence_of_strings):
    """
    Prints all the characters in the sequence of strings,
    but each character on ITS OWN LINE.  For example,
    if the given argument is ["hi", "bye", "a tie!"],
    then this function prints:
       h
       i
       b
       y
       e
       a

       t
       i
       e
       !
    Precondition:  the given argument is a sequence of strings.
    """
    # -------------------------------------------------------------------------
    # TODO: 3. Implement and test this function.
    #  ** READ THE TESTS that have been written for you (ABOVE).
    #  ** ASK QUESTIONS if you do not understand the TESTS (ABOVE).
    # -------------------------------------------------------------------------


def run_test_print_characters_slanted():
    """ Tests the    print_characters_slanted    function. """
    # -------------------------------------------------------------------------
    # We have supplied tests for you. No additional tests are required,
    # although you are welcome to supply more tests if you choose.
    # -------------------------------------------------------------------------
    print()
    print("--------------------------------------------------")
    print("Testing the   PRINT_CHARACTERS_SLANTED   function:")
    print("--------------------------------------------------")

    # Test 1:
    print()
    print("The following should be:")
    print("h")
    print(" i")
    print("b")
    print(" y")
    print("  e")
    print("a")
    print(" _")
    print("  t")
    print("   i")
    print("    e")
    print("     !")
    print("Actual result:")
    print_characters_slanted(["hi", "bye", "a_tie!"])

    # Test 2:
    print()
    print("The following should be:")
    print("a")
    print(" b")
    print("  c")
    print("   d")
    print("    e")
    print("x")
    print("y")
    print("z")
    print(" z")
    print("  z")
    print("1")
    print(" 2")
    print("  3")
    print("   4")
    print("Actual result:")
    print_characters_slanted(["abcde", "x", "y", "zzz", "1234"])


def print_characters_slanted(sequence_of_strings):
    """
    Same as the previous problem, but each string "slants".
    For example, if the given argument is ["hi", "bye", "a_tie!"],
    then this function prints:
       h
        i
       b
        y
         e
       a
        _
         t
          i
           e
            !
    Precondition:  the given argument is a sequence of strings.
    """
    # -------------------------------------------------------------------------
    # TODO: 4. Implement and test this function.
    #  ** READ THE TESTS that have been written for you (ABOVE).
    #  ** ASK QUESTIONS if you do not understand the TESTS (ABOVE).
    #  __
    #  ** HINT: ** Consider using string multiplication for the spaces
    #              and string addition to stitch the spaces to the character.
    # -------------------------------------------------------------------------


###############################################################################
# Our tests use the following to print error messages in red.
# Do NOT change it.  You do NOT have to do anything with it.
###############################################################################

def print_expected_result_of_test(arguments, expected,
                                  test_results, format_string, suffix=""):
    testing_helper.print_expected_result_of_test(arguments, expected,
                                                 test_results, format_string,
                                                 suffix)


def print_actual_result_of_test(expected, actual, test_results,
                                precision=None):
    testing_helper.print_actual_result_of_test(expected, actual,
                                               test_results, precision)


def print_summary_of_test_results(test_results):
    testing_helper.print_summary_of_test_results(test_results)


# To allow color-coding the output to the console:
USE_COLORING = True  # Change to False to revert to OLD style coloring

testing_helper.USE_COLORING = USE_COLORING
if USE_COLORING:
    # noinspection PyShadowingBuiltins
    print = testing_helper.print_colored
else:
    # noinspection PyShadowingBuiltins
    print = testing_helper.print_uncolored

# -----------------------------------------------------------------------------
# Calls  main  to start the ball rolling.
# The   try .. except   prevents error messages on the console from being
# intermingled with ordinary output to the console.
# -----------------------------------------------------------------------------
try:
    main()
except Exception:
    print("ERROR - While running this test,", color="red")
    print("your code raised the following exception:", color="red")
    print()
    time.sleep(1)
    raise