from math import pi, sqrt, sin, cos, atan
from turtle import penup, pendown, forward, left, radians, degrees

π = pi

def ellipse(rx, ry, num_points, points_to_draw=None):
    """
    Draws an ellipse with x-radius rx and y-radius ry, 
    with num_points control points. Optionally specify
    points_to_draw if you want only a partial ellipse.
    Assumes the turtle starts at the center, pointed in 
    the +x direction, and ends in the same position and heading.
    """
    radians()
    φstart = get_angle(rx, ry, num_points, 0) / 2
    fly(rx)
    left(π/2 - φstart) 
    for i in range(points_to_draw or num_points):
        φi = get_angle(rx, ry, num_points, i)
        di = get_distance(rx, ry, num_points, i)
        print(i, φi, di)
        left(φi)
        forward(di)
    left(-π/2 + φstart)
    fly(-rx)
    degrees()

def fly(distance):
    """Like forward, but with the pen up.
    """
    penup()
    forward(distance)
    pendown()

def get_angle(rx, ry, n, i):
    """Returns the angle φi, or how far left the turtle should
    turn at point i of n. 
    """
    coords_prev = get_coordinates(rx, ry, n, i - 1)
    coords      = get_coordinates(rx, ry, n, i)
    coords_next = get_coordinates(rx, ry, n, i + 1)
    vec_prev = get_vector(coords_prev, coords)
    vec_next = get_vector(coords, coords_next)
    vec_parallel, vec_perpendicular = project_onto_basis(vec_next, vec_prev)
    φi = atan(mag(vec_perpendicular) / mag(vec_parallel))
    return φi

def get_distance(rx, ry, n, i):
    """
    Returns the distance from point i to point i + 1.
    """
    coords      = get_coordinates(rx, ry, n, i)
    coords_next = get_coordinates(rx, ry, n, i + 1)
    vec_next = get_vector(coords, coords_next)
    return mag(vec_next)

def get_coordinates(rx, ry, n, i):
    """Returns the (x, y) coordinates of point i for the ellipse
    specified by rx, ry, and n.

    Normally, we can get the coordinates of a point on the circle
    at angle θ as (r * cos(θ), r * sin(θ)). An ellipse is a circle
    scaled differently along the x- and y- axes, so we need to
    apply different scaling factors rx and ry. 
    """
    θi = 2*π*i/n
    xi = rx * cos(θi)
    yi = ry * sin(θi)
    return xi, yi

def get_vector(start, end):
    """Returns a vector from the start point to the end point. 
    """
    x0, y0 = start
    x1, y1 = end
    return (x1 - x0, y1 - y0)

def project_onto_basis(vec, basis):
    """Projects a vector onto a basis vector, returning two vectors:
    the component which is parallel to basis (vec_a) and the component which 
    is perpendicular (vec_b). 
    """
    v0, v1 = vec
    unit_basis = normalize(basis)
    b0, b1 = unit_basis
    len_a = dot(vec, unit_basis)
    vec_a = (len_a * b0, len_a * b1)
    vec_b = get_vector(vec_a, vec)
    return vec_a, vec_b

def dot(vec0, vec1):
    """Computes the dot product of vec0 and vec1. 
    The result is the magnitude of vec0 projected onto vec1.
    """
    x0, y0 = vec0
    x1, y1 = vec1
    return x0 * x1 + y0 * y1

def normalize(vec):
    """Scales vec to magnitude 1.
    """
    x, y = vec
    len_vec = mag(vec)
    return x / len_vec, y / len_vec

def mag(vec):
    """Returns the magnitude, or length, of a vector.
    We can simplify the familiar formula sqrt(x*x + y*y)
    using the dot product. 
    """
    return sqrt(dot(vec, vec))

