"""
Practice Test 2, problem 1.

Authors: David Mutchler, Valerie Galluzzi, Mark Hays, Amanda Stouder,
         their colleagues and PUT_YOUR_NAME_HERE.  October 2015.
"""  # TODO: 1. PUT YOUR NAME IN THE ABOVE LINE.

import rosegraphics as rg
import juggler_tests as jt


def main():
    """Calls the TEST functions in this module"""
    test_init()
#     test_move_circles()
#     test_draw_circles()
#     test_hand_off()
#     test_juggle()
#     test_line_up()
#     test_performance()
#     test_rainbow()
    # Uncomment after you finished all of your TODOs for a fun surprise :)
    # just_for_fun()


class Juggler(object):

    def __init__(self, c_tuple):
        """
        INPUTS: A tuple of rg.Circle objects.
        OUTPUT: None.
        SIDE EFFECTS: The instance variable
                         self.circles
                      is set to the given tuple of rg.Circle objects.
        DESCRIPTION: Initializes instance variables as needed
                     in a new instance of the Juggler class.
        PRECONDITIONS:
            :type c_tuple: tuple(rg.Circle)
        """

    def move_circles(self, dx, dy):
        """
        INPUTS: integers dx and dy.
        OUTPUT: None.
        SIDE EFFECTS:
            For each circle in this Juggler's tuple of circles,
            moves the circle by dx and dy.
        """

    def draw_circles(self, window):
        """
        INPUTS: An rg.RoseWindow.
        OUTPUT: A number.  See DESCRIPTION.
        SIDE EFFECTS: Draws, onto the given rg.RoseWindow,
                      the circles in this Juggler's tuple of circles.
                      See DESCRIPTION for details.
        DESCRIPTION:
            The first time that this method is called, it:
              1. attaches each circle in this Juggler's tuple of circles
                 to the given rg.RoseWindow, and then
              2. does a render with a 0.5 second delay, as in:
                    window.render(0.5)

            Subsequent calls to this method will do the same, except
            step 2 above (the rendering) is:
              -- Render with a 0.25 second delay if the previous
                   rendering by this method was with a 0.5 second delay.
              -- Render with a 0.5 second delay otherwise.

            RETURNS the amount of the delay in the render that this call
            draw_circles did.  That is, the number BLAH in this call's
               window.render(BLAH)

        PRECONDITIONS:
            :type window: rg.RoseWindow
        """

    def hand_off(self, index, jester2):
        """
        INPUTS: A nonnegative integer and a Juggler.
        OUTPUT: None.
        SIDE EFFECTS: The radius and color of a circle in the
                      circles tuple of both Juggler objects is changed.
                      See DESCRIPTION for details.
        DESCRIPTION: The  hand_off  method swaps the radius and color
            of the circle in the specified index of this Juggler's
            tuple of circles with the radius and color of the circle
            in the specified index of the given Juggler (jester2).
            This makes it look like the Jugglers swapped a ball.
        EXAMPLE:
            Suppose jester1 has a:
              -- YELLOW ball (i.e. circle) with radius 3 at position (0,0)
                 at index 4 of jester1's tuple of circles.
            And suppose that jester2 has a:
              -- GREEN ball (i.e., circle) with radius 10 at position (5,5)
                 at index 4 of jester2's tuple of circles.
            Then after calling
                jester1.hand_off(4, jester2)
            jester1's ball (circle) in index 4 of its tuple of circles
            should be GREEN with radius 10 but still at position (0,0),
            while jester2's ball (circle) in index 4 of its tuple of circles
            should be YELLOW with radius 3 but still at position (5,5).
        PRECONDITIONS:
          :type index: int
          :type jester2: Juggler
        and
          -- The given index is valid for this Juggler, that is,
               is at least 0 and less than the length of this Juggler's
               tuple of circles.
          -- The given index is valid for the given Juggler, that is,
               is at least 0 and less than the length of that Juggler's
               tuple of circles.
        """

    def juggle(self):
        """
        INPUTS: None
        OUTPUT: None
        SIDE EFFECTS: Each circle in this Juggler's tuple of circles
                      changes its position.  See DESCRIPTION.
        DESCRIPTION:
            The  juggle  method moves each circle in this Juggler's
            tuple of circles to the position of the next circle
            in the tuple. The last circle in the tuple is moved to the
            position the first circle in the tuple was in before it
            was moved by this method.
        EXAMPLE:
            Consider a Juggler instance   jester1   and suppose that
            jester1's circle of tuples has the following 3 circles:
              -- The first circle is at position (1,1).
              -- The second circle is at position (2,2).
              -- The third circle is at position (3,3).
            After the execution of:
               jester1.juggle()
            jester1's tuple of circles should contain the same circles
            except now:
              -- The first circle is at position (2,2).
              -- The second circle is at position (3,3).
              -- The third circle is at position (1,1).
        """

    def line_up(self, p1, p2):
        """
        INPUTS: Two rg.Point instances that are far enough apart
                that all circles in this Juggler's tuple of circles
                can fall between them without overlap.
        OUTPUT: None
        SIDE EFFECTS: The circles in this Juggler's tuple of circles
                change position.  See DESCRIPTION.
        DESCRIPTION:
            The  line_up   method takes two points and lines up the
            the circles in this Juggler's tuple of circles between
            those points.  The first input point specifies where
            the center of the first circle in the tuple should be placed,
            and the second input point specifies where the center of the
            last circle in the tuple should be placed.  The centers of
            all other circles in the tuple should be evenly distributed
            between the first and last circles in the order that they
            appear in the tuple.
        PRECONDITIONS:
            :type p1: rg.Point
            :type p2: rg.Point
        HINT: Test using a horizontal or vertical line first,
            then extend to any arbitrary line.
        """

    def performance(self, seconds, window):
        """
        INPUTS: A positive number and an rg.RoseWindow.
        OUTPUT: None.
        SIDE EFFECTS: An amazing juggling show.
        DESCRIPTION:
            The   performance   method juggles for the number of seconds
            specified by the input. Each time the circles are juggled
            they should be displayed, resulting in an amazing show of juggling
            prowess on the computer screen.
        PRECONDITIONS:
            :type seconds: (float, int)  that is positive
            :type window: rg.RoseWindow
        """

    def rainbow(self):
        """
        INPUTS: None
        OUTPUT: None
        SIDE EFFECTS: Circles change color.  See DESCRIPTION.
        DESCRIPTION:
            The  rainbow   method changes the color of all of the
            circles in this Juggler's tuple of circles to the next
            color in the rainbow, where the colors of the rainbow are
            ['red', 'orange', 'yellow', 'green', 'blue', 'indigo', 'violet'].
            If the colors of the circles have reached the end of the
            rainbow ('violet') by calls to this method, this method
            returns them to the first color ('red').

            The first time that this method is called, it should change
            all the circles in this Juggler's circle of tuples to have
            the first color, 'red'.
        """


def make_test_circles(center_x=120, center_y=80, size=1, n=6):
    """
    INPUTS: Two positive integers  (for center_x and center_y),
            a positive float   (for size), and
            a nonnegative integer n <= 7.  All are optional.
    OUTPUT: A tuple of  n  rg.Circle objects.  The set of circles is
            centered at the given x and y.  The circles have various
            sizes, with size being a size multiplier.
    SIDE EFFECTS: None.
    PRECONDITIONS:
      :type center_x: int  and is positive
      :type center_y: int  and is positive
      :type size: (float, int)  and is positive.
      :type n: int   and is at least 1 and at most 7.
    """
    # ------------------------------------------------------------------
    # Students:  There is NO NEED TO EDIT THIS FUNCTION.
    #   -- If you wish, you can use this function to generate
    #        tuples of circles that you can use for your testing.
    #   -- See  test_init  for an example of how to use this function.
    # ------------------------------------------------------------------
    offset = 0 if n % 2 == 0 else 15

    c_A = rg.Circle(rg.Point(center_x - size * (offset + 60),
                             center_y + size * 10), 10 * size)
    c_A.fill_color = 'yellow'
    c_B = rg.Circle(rg.Point(center_x - size * (offset + 40),
                             center_y - size * 20), 9 * size)
    c_B.fill_color = 'red'
    c_C = rg.Circle(rg.Point(center_x - size * (offset + 15),
                             center_y - size * 35), 15 * size)
    c_C.fill_color = 'blue'
    c_D = rg.Circle(rg.Point(center_x,
                             center_y - size * 45), 11 * size)
    c_D.fill_color = 'deep pink'
    c_E = rg.Circle(rg.Point(center_x + size * (offset + 15),
                             center_y - size * 35), 5 * size)
    c_E.fill_color = 'green'
    c_F = rg.Circle(rg.Point(center_x + size * (offset + 40),
                             center_y - size * 20), 12 * size)
    c_F.fill_color = 'orange'
    c_G = rg.Circle(rg.Point(center_x + size * (offset + 60),
                             center_y + size * 10), 8 * size)
    c_G.fill_color = 'purple'

    all_circles = [c_A, c_B, c_C, c_D, c_E, c_F, c_G]

    start = (len(all_circles) - n) // 2
    middle = len(all_circles) // 2
    end = (len(all_circles) + n + 1) // 2

    if n % 2 == 1:
        return tuple(all_circles[start:end])
    else:
        return tuple(all_circles[start:middle] + all_circles[middle + 1:end])


def more_tests(s):
    """ Runs more tests on the method whose name is the argument s. """
    jt.more_tests(s)


def test_init():
    """ Tests the   __init__   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 2.
    #   a. Read the specification of the   __init__   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for __init__.
    #   c. Implement and test the  __init__   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   __init__   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # An example of a test:
    # ------------------------------------------------------------------

    # Make a tuple with   make_test_circles.  It has optional arguments.
    # Make a clone (copy) of it too, by calling make_test_circles again.
    circle_tuple = make_test_circles()
    tuple_clone = make_test_circles()

    # Make a Juggler.
    jester = Juggler(circle_tuple)

    # See if the Juggler has the right circle at index 2:
    index = 2
    expected = tuple_clone[index]
    actual = jester.circles[index]
    print('Expected is: ', expected)
    print('Actual is:   ', actual)

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('init')


def test_move_circles():
    """ Tests the   move_circles   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 3.
    #   a. Read the specification of the   move_circles   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for move_circles.
    #   c. Implement and test the  move_circles   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   move_circles   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('move_circles')


def test_draw_circles():
    """ Tests the   draw_circles   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 4.
    #   a. Read the specification of the   draw_circles   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for draw_circles.
    #   c. Implement and test the  draw_circles   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   draw_circles   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('draw_circles')


def test_hand_off():
    """ Tests the   hand_off   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 5.
    #   a. Read the specification of the   hand_off   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for hand_off.
    #   c. Implement and test the  hand_off   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   hand_off   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('hand_off')


def test_juggle():
    """ Tests the   juggle   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 6.
    #   a. Read the specification of the   juggle   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for hand_off.
    #   c. Implement and test the  juggle   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   juggle   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('juggle')


def test_line_up():
    """ Tests the   line_up   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 7.
    #   a. Read the specification of the   line_up   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for line_up.
    #   c. Implement and test the  line_up   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    #
    # NOTE: When you read the specification for the  line_up  method,
    #       you will see that the only side effect listed is that the
    #       Juggler's circles change position. That means that the
    #       points given as input should not be changed.
    #       Does your method fulfill that test?
    #       Un-comment the commented-out test below to find out.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   line_up   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # Tests whether the  line_up  method leaves the input points unmodified:
    # p1 = rg.Point(30, 30)
    # p2 = rg.Point(300, 30)
    # expected1 = p1.clone()
    # expected2 = p2.clone()
    # jester = Juggler(make_test_circles())
    # jester.line_up(p1, p2)
    # print ("Expected and actual are:", expected1, p1)
    # print ("Expected and actual are:", expected2, p2)

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('line_up')


def test_performance():
    """ Tests the   performance   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 8.
    #   a. Read the specification of the   performance   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for performance.
    #   c. Implement and test the  performance   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   performance   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('performance')


def test_rainbow():
    """ Tests the   rainbow   method of the Juggler class. """
    # ------------------------------------------------------------------
    # TODO: 9.
    #   a. Read the specification of the   rainbow   method.
    #        (The spec is in the Juggler class definition, above.)
    #   b. Add   ** 1 **   test to this TEST function for rainbow.
    #   c. Implement and test the  rainbow   method.
    #        We have supplied MORE tests for you.
    #   d. COMMIT your code and make  main  test only the next function.
    # ------------------------------------------------------------------
    print()
    print('-----------------------------------------------------------')
    print('Testing the   rainbow   method of the Juggler class.')
    print('-----------------------------------------------------------')

    # ------------------------------------------------------------------
    # Add your additional test below here:
    # ------------------------------------------------------------------

    # ------------------------------------------------------------------
    # The following line runs more tests.  Keep it here!
    # ------------------------------------------------------------------
    more_tests('rainbow')


def just_for_fun():
    # No need to understand this code or look through it--just run for fun
    # once you have implemented everything else :)
    window = rg.RoseWindow(750, 500)
    stands = rg.Rectangle(rg.Point(0, 0), rg.Point(750, 350))
    stands.fill_color = 'brown'
    stands.attach_to(window)
    stage = rg.Rectangle(rg.Point(0, 350), rg.Point(750, 500))
    stage.fill_color = 'Deepskyblue1'
    stage.attach_to(window)
    x = 0
    y = 0
    for _ in range(200):
        y = 10 + 30 * (x // 725)
        x = 10 + (x + 30)
        audience = rg.Circle(rg.Point(x % 725, y), 15)
        audience.fill_color = "gray"
        audience.attach_to(window)
    window.render(.1)
    j1 = rg.Circle(rg.Point(200, 400), 20)
    j1.fill_color = 'black'
    j1.attach_to(window)
    j2 = rg.Circle(rg.Point(550, 400), 20)
    j2.fill_color = 'black'
    j2.attach_to(window)
    window.render()
    j1_circles = make_test_circles(j1.center.x, j1.center.y, 1)
    j2_circles = make_test_circles(j2.center.x, j2.center.y, 1)
    for i in range(len(j1_circles)):
        j1_circles[i].radius = 8 + (2 * (i % 2))
        j2_circles[i].radius = 8 + (2 * (i % 2))
    jester1 = Juggler(j1_circles)
    jester2 = Juggler(j2_circles)
    jester1.rainbow()
    jester2.rainbow()
    jester1.draw_circles(window)
    jester2.draw_circles(window)
    for i in range(len(jester1.circles) - 1):
        jester2.rainbow()
        jester1.juggle()
        jester1.hand_off(i, jester2)
        jester1.draw_circles(window)
    jester2.rainbow()
    for _ in range(30):
        jester1.juggle()
        jester1.draw_circles(window)
    jester1.line_up(rg.Point(200, 350), rg.Point(200, 200))
    jester2.line_up(rg.Point(550, 350), rg.Point(550, 200))
    jester1.performance(2, window)
    jester2.performance(2, window)
    j1.radius = 10
    j2.radius = 10
    window.render(.5)
    j1.radius = 20
    j2.radius = 20
    window.render(.1)
    window.close_on_mouse_click()

# ----------------------------------------------------------------------
# If this module is running at the top level (as opposed to being
# imported by another module), then call the 'main' function.
# ----------------------------------------------------------------------
if __name__ == '__main__':
    main()