import turtle
import random

GRID_HEIGHT = 40
GRID_WIDTH = 10
CELL_SIZE = 15


EMPTY_COLOR = "gray"
FULL = [[False for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
PREV_COLOR = [[None for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]
COLOR = [[EMPTY_COLOR for _ in range(GRID_WIDTH)] for _ in range(GRID_HEIGHT)]


score = 0
snap_to_end = False
speed = 20


def spawn_piece():
    global piece_x, piece_y, next_piece_x, piece, piece_color, next_piece
    piece_x = 5
    piece_y = GRID_HEIGHT + 1
    next_piece_x = 5
    rand = random.randrange(0, 7)
    print("spawned", rand)
    if rand == 0:
        piece = [(-1, 0), (0, 0), (1, 0), (2, 0)]
        piece_color = "teal"
    elif rand == 1:
        piece = [(0, 0), (1, 0), (-1, 0), (0, -1)]
        piece_color = "purple"
    elif rand == 2:
        piece = [(1, 0), (0, 0), (0, -1), (-1, -1)]
        piece_color = "green"
    elif rand == 3:
        piece = [(-1, 0), (0, 0), (0, -1), (1, -1)]
        piece_color = "red"
    elif rand == 4:
        piece = [(-1, 0), (0, 0), (1, 0), (1, -1)]
        piece_color = "blue"
    elif rand == 5:
        piece = [(1, 0), (0, 0), (-1, 0), (-1, -1)]
        piece_color = "orange"
    elif rand == 6:
        piece = [(0, 0), (-1, 0), (-1, -1), (0, -1)]
        piece_color = "yellow"
    next_piece = piece


spawn_piece()


turtle.setup(350, 650)

turtle.tracer(30, 0)
t = turtle.Turtle()
t.penup()
t.hideturtle()
t.speed(0)


def set_cell(x, y, color):
    if x not in range(GRID_WIDTH) or y not in range(GRID_HEIGHT):
        return
    COLOR[y][x] = color


def draw_board():
    def draw_cell(x, y, color):
        t.goto(x * CELL_SIZE - (GRID_WIDTH * CELL_SIZE / 2),
               y * CELL_SIZE - (GRID_HEIGHT * CELL_SIZE / 2) + 20)
        t.setheading(0)
        t.color(color)
        t.begin_fill()
        for _ in range(4):
            t.forward(CELL_SIZE)
            t.right(90)
        t.end_fill()

    for y in range(GRID_HEIGHT):
        for x in range(GRID_WIDTH):
            if COLOR[y][x] != PREV_COLOR[y][x]:
                print("changed", x, y, COLOR[y][x])
                draw_cell(x, y, COLOR[y][x])
                PREV_COLOR[y][x] = COLOR[y][x]


def piece_fits(x, y, candidate_piece):
    for (x_offset, y_offset) in candidate_piece:
        if y + y_offset >= GRID_HEIGHT:
            # falling pieces are allowed to go above the screen
            continue
        if x + x_offset not in range(GRID_WIDTH) \
                or y + y_offset not in range(GRID_HEIGHT) \
                or FULL[y + y_offset][x + x_offset]:
            return False
    return True


def try_clear(row):
    if all(FULL[row]):
        FULL.pop(row)
        FULL.append([False for _ in range(GRID_WIDTH)])
        COLOR.pop(row)
        COLOR.append([EMPTY_COLOR for _ in range(GRID_WIDTH)])
        return True
    return False


def score_from_lines(num):
    if num == 0:
        return 0
    elif num == 1:
        return 100
    elif num == 2:
        return 300
    elif num == 3:
        return 500
    elif num == 4:
        return 800
    else:
        raise "uh oh"


def place_piece():
    global piece_y, score
    sorted_segments = sorted(
        piece, key=lambda offsets: offsets[1])
    print("place piece", sorted_segments)
    cleared_total = 0
    for (x_offset, y_offset) in sorted_segments:
        if piece_y + y_offset >= GRID_HEIGHT:
            return False
        FULL[piece_y + y_offset][piece_x + x_offset] = True
        cleared = try_clear(piece_y + y_offset)
        if cleared:
            cleared_total += 1
            # move piece down a row now that the bottom of the piece has caused a clear
            piece_y -= 1
    score += score_from_lines(cleared_total)
    return True


def on_left():
    global next_piece_x
    if piece_fits(next_piece_x - 1, piece_y, next_piece):
        next_piece_x -= 1


def on_right():
    global next_piece_x
    if piece_fits(next_piece_x + 1, piece_y, next_piece):
        next_piece_x += 1


def on_space():
    global snap_to_end
    snap_to_end = True


def on_down():
    global speed
    speed *= 3


def on_down_released():
    global speed
    speed //= 3


def on_z():
    global next_piece
    candidate_piece = [rotate_left(offsets) for offsets in next_piece]
    if piece_fits(next_piece_x, piece_y, candidate_piece):
        next_piece = candidate_piece


def on_x():
    global next_piece
    candidate_piece = [rotate_right(offsets) for offsets in next_piece]
    if piece_fits(next_piece_x, piece_y, candidate_piece):
        next_piece = candidate_piece


def rotate_right(offsets):
    (x_offset, y_offset) = offsets
    return (y_offset, -x_offset)


def rotate_left(offsets):
    (x_offset, y_offset) = offsets
    return (-y_offset, x_offset)


def move_piece():
    global piece_x, piece_y, piece
    if not piece_fits(next_piece_x, piece_y - 1, next_piece):
        return False

    for (x_offset, y_offset) in piece:
        set_cell(piece_x + x_offset, piece_y + y_offset, EMPTY_COLOR)
    piece_x = next_piece_x
    piece_y -= 1
    piece = next_piece
    for (x_offset, y_offset) in piece:
        set_cell(piece_x + x_offset, piece_y + y_offset, piece_color)
    return True


turtle.onkeypress(on_left, "Left")
turtle.onkeypress(on_right, "Right")
turtle.onkeypress(on_space, " ")
turtle.onkeypress(on_down, "Down")
turtle.onkeyrelease(on_down_released, "Down")
turtle.onkeypress(on_z, "z")
turtle.onkeypress(on_x, "x")
turtle.listen()

draw_board()

step = 0
is_game_over = False


def game_over():
    global is_game_over
    is_game_over = True
    t.goto(0, 0)
    t.color("black")
    t.write("Game over! Score: " + str(score),
            align="center", font=("arial", 20, "normal"))


def update():
    global step, snap_to_end
    if is_game_over:
        return
    step += 1
    print("update", step)
    turtle.ontimer(update, 1000//speed)
    draw_board()
    fits = move_piece()
    if snap_to_end:
        while fits:
            fits = move_piece()
        snap_to_end = False
    if not fits:
        good = place_piece()
        print("placed good=", good)
        if not good:
            game_over()
            return
        spawn_piece()


update()