import turtle

# NOTE: Many statements in this notebook are commented out as turtle graphics can have
#       in a Jupyter Notebook.  You can use uncommented versions in your Python IDE.

#bob = turtle.Turtle()
#print( bob )

#bob.fd( 100 ) # Move forward 100 pixels
#bob.lt( 90 )  # Turn 90 degrees to the left
#bob.fd( 100 ) # Move forward 100 pixels

"""
Explanation: Chapter 4: Interface Design

Contents
- TurtleWorld
- Simple repetition
- Encapsulation
- Generalization
- Interface design
- Refactoring
- A development plan
- docstring
- Debugging
- Exercises

This notebook is based on "Think Python, 2Ed" by Allen B. Downey <br>
https://greenteapress.com/wp/think-python-2e/

TurtleWorld

Turtle graphics are a simple way of drawing on the screen
Imagine a turtle walking around carrying a pen
He can:
Pull the pen up
Push the pen down
Move forward
Turn to the left or right
That's it!
End of explanation
"""


for i in range(4):
    print( 'Hello!' )

"""
Explanation: Simple repetition

When designing algorithms, we often need to repeat some action over and over
One way to implement this in Python is the for loop
End of explanation
"""


def square( t ):
    for i in range( 4 ):
        t.fd( 100 )
        t.lt( 90 )

#square( bob )


"""
Explanation: To practice loops and turtle graphics, complete the following exercises from the textbook:
1. Write a function called square that takes a parameter named t, which is a turtle. It should use the turtle to draw a square.  Write a function call that passes bob as an argument to square, and then run the program again.
1. Add another parameter, named length, to square. Modify the body so length of the sides is length, and then modify the function call to provide a second argument. Run the program again. Test your program with a range of values for length.
1. The functions lt and rt make 90-degree turns by default, but you can provide a second argument that specifies the number of degrees. For example, lt(bob, 45) turns bob 45 degrees to the left.  Make a copy of square and change the name to polygon. Add another parameter named n and modify the body so it draws an n-sided regular polygon. Hint: The exterior angles of an n-sided regular polygon are 360.0/n degrees.
1. Write a function called circle that takes a turtle, t, and radius, r, as parameters and that draws an approximate circle by invoking polygon with an appropriate length and number of sides. Test your function with a range of values of r.  Hint: figure out the circumference of the circle and make sure that length * n = circumference.  Another hint: if bob is too slow for you, you can speed him up by changing bob.delay, which is the time between moves, in seconds. bob.delay = 0.01 ought to get him moving.
1. Make a more general version of circle called arc that takes an additional parameter angle, which determines what fraction of a circle to draw. angle is in units of degrees, so when angle=360, arc should draw a complete circle.
If you would like more of a challenge, try these additional exercises:
1. Write a function called drawSpiral that takes a parameter named degrees that specifies the amount in degrees to draw a spiral.
2. Write a function called drawStar that takes a parameter named length that draws a five-sided star with the specified side length.
3. Write a function called drawCircles that takes a parameter named count that draws the specified number of circles side-by-side.
Encapsulation

Encapsulation is hiding how you implement your algorithm in a function
Why do you think this would be beneficial?
When we created a square() function, we encapsulated the process for drawing a square
End of explanation
"""


def square( t, length ):
    for i in range( 4 ):
        t.fd( 100, length )
        t.lt( 90 )

#square( bob, 100 )

"""
Explanation: This allows us to reuse functionality in the future
It also allows us to improve the code in the future (maybe even fix bugs) and all the code that uses the function automatically gets the new and improved code
Encapsulation doesn't work if we aren't able to abstract away the details
In this case, the details are the specific turtle doing the drawing
We could use the same function with a different turtle and it would still work
These are two of the big time terms in programming, and this course course specifically

Generalization

Another way to view abstraction, is to think of it as generalization
When we generalize, we get away from the details and think about the bigger concepts
For example, we can generalize the square() function to use any distance
End of explanation
"""


def polygon( t, n, length ):
    # Calculate the angle to turn for each side
    angle = 360.0 / n
    # Draw each side
    for i in range( n ):
        t.fd( length )
        t.lt( angle )

#polygon( bob, 7, 70 )

"""
Explanation: We can now draw a square of any size
We can even generalize it further to draw a polygon with any number of sides
End of explanation
"""


def polyline( t, length, n, angle ):
    """Draw n line segments with the given length and
    angle (in degreees) between them.  t is a turtle.
    """
    for i in range( n ):
        t.fd( length )
        t.lt( angle )

"""
Explanation: Interface design

When we generalize a function, we need to think hard about how a user will call the function
We want to make it "as simple as possible, but not simpler"
This part of the function is referred to as its interface
It is:
The name of the function
What the function is supposed to do
The function arguments
The function's return value
We interact with interfaces everyday
Some examples of interfaces are:
TV remote control
ATM
Steering wheel and dashboard in a car
What are some other real-world examples of interfaces?

Refactoring

Sometimes you will discover that you can generalize a function even further after you have already written it
When you rearrange a program (or function) to improve functionality, sometimes by improving function interfaces, it is called refactoring
This happens very often in software development
Programmers rarely, if ever, get functions and programs right the first time
Often, you learn something about the problem only after you start implementing your solution
What sort of things can you learn?  Why do you learn them only after you start on the solution?
Software development is an iterative process
What does this mean?  Have you experienced this in your programs?

A development plan

There are a variety of processes used to develop software
One of the simplest process for novice programmers is encapsulation and generalization
It assumes that you have already decomposed the problem into subproblems
The following are the steps in the process:
Design an algorithm for a small subproblem with no abstraction
Encapsulate and generalize the algorithm
Repeat the previous two steps for additional subproblems
Combine subproblem solutions where appropriate into more general algorithms
This is referred to as a bottom-up approach
Another process goes the other way:
Design a high-level algorithm for the overall problem
Identify subproblems using abstract portions of the algorithm
Expand the abstract portions of the algorithm into more specific algorithms
Repeat the previous two steps until an algorithm is as specific and simple as possible
This is referred to as a top-down approach
Neither are perfect and both have problems, but
They are useful approaches for now

docstring

A docstring is a special type of comment at the beginning of a function that explains the interface
End of explanation
"""


help( polyline )

"""
Explanation: A user can access it in the cosole using the help function
End of explanation
"""


def draw_square( t ):
    # INSERT YOUR CODE HERE
    print( 'Remove this line' ) # Jupyter needs a statement to compile

"""
Explanation: It explains the essential information needed by users in a concise manner

Debugging

An interface is like a contract between a function and code that calls the function
It does not specify how that contract is implemented, just that it is
The contract specifies what the function will do for the caller
The required state values (i.e., variables) of the system before a function starts executing are called the preconditions
The state values of the system after a function executes are called the postconditions
Meeting preconditions are the responsibility of the code calling the function
If the preconditions are met, it is the responsibility of the function to do what it says it will and meet the postconditions
If the preconditions aren't met, there is no guarantee that the postconditions will be true
Understanding preconditions and postconditions can help you write better code and find any errors you may encounter

Exercises

Write a function called draw_square that takes a parameter t, which is a turtle, and then uses the turtle to draw a square.
End of explanation
"""


def draw_variable_square( t, length ):
    # INSERT YOUR CODE HERE
    print( 'Remove this line' ) # Jupyter needs a statement to compile

"""
Explanation: Write a function called draw_variable_square that takes a parameter t, which is a turtle, and a parameter length, which is the length of a side.  The function should use the turtle to draw a square with sides of the specified length.
End of explanation
"""


def draw_polygon( t, n, length ):
    # INSERT YOUR CODE HERE
    print( 'Remove this line' ) # Jupyter needs a statement to compile

"""
Explanation: Write a function called draw_polygon that takes a parameter t, which is a turtle, a parameter n, which is the number of sides, and a parameter length, which is the length of a side.  The function should draw a polygon with the specified number of sides of the specified length.
End of explanation
"""


def draw_circle( t, r ):
    # INSERT YOUR CODE HERE
    print( 'Remove this line' ) # Jupyter needs a statement to compile

"""
Explanation: Write a function called draw_circle that takes a parameter t, which is a turtle, and a parameter r, which is a radius.  The function should draw a circle with the specified radius.
End of explanation
"""


