
ag.import("std")

function newGame()
    current_frame = 1
    setupScore()
    newFrame()
    position_x = 0
    target_x = 0
    game_state = "waiting"
end

function resetPins()
    local head_x = 0
    local head_y = 15
    local spacing = 0.75
    for i = 1, 10 do
        if (pins[i] ~= nil) then
            pins[i]:destroy()
        end
    end
    local n = 1
    for row = 0, 3 do
        for j = 0, row do
            pins[n] = reference_pin:clone()
            pins[n]:setPosition(head_x - row * spacing / 2 + j * spacing,
                head_y + 0.866 * spacing * row,
                1 - pin_minz)
            n = n + 1
        end
    end
end

function setupStars()
    stars = ag.startList()
    local dist = 500
    for i = 1, 500 do
        local rx = math.random() * math.pi
        local ry = (math.random() - 0.5) * math.pi * 0.8
        local x = dist * math.cos(ry) * math.cos(rx)
        local y = dist * math.cos(ry) * math.sin(rx)
        local z = dist * math.sin(ry)
        local size = math.random(1, 6)
        ag.drawPoint(size, 1, 1, 1, x, y, z)
    end
    ag.endList()
end

function setupScore()
    current_frame = 1
    current_ball = 1
    score = {}
    for i = 1, 10 do
        local s = {}
        s[1] = -1
        s[2] = -1
        if (i == 10) then
            s[3] = -1
        end
        s["tot"] = -1
        score[i] = s
    end
end

function init_event()
    lane = ag.loadModel("bowling_lane", {static = true})
    local minx, miny, minz, maxx, maxy, maxz = lane:getAABB()
    local lane_bottom = minz
    local lane_top = maxz
    pins = {}
    reference_pin = ag.loadModel("bowling_pin",
        {scale = 1.0/4.3, reference = true})
    minx, miny, minz, maxx, maxy, maxz = reference_pin:getAABB()
    pin_minz = minz
    pin_maxz = maxz
    pin_standing_position = lane_top - pin_minz
    reference_ball = ag.loadModel("bowling_ball", {reference = true})
    reference_ball:setMass(10)
    setupStars()
    game_state = "waiting"
    mouse_moved = false
    newGame()
    last_update_time = ag.elapsedTime()
    setCamera(last_update_time)
end

function setCamera(now)
    if (game_state == "launched") then
        local pct = (now - launch_time) / 2500
        if (pct > 1) then
            pct = 1
        end
        ag.setCamera(position_x + (-position_x * pct), -10 + 10 * pct, 5 + 5 * pct, target_x, 20, 0)
    else
        ag.setCamera(position_x, -10, 5, target_x, 20, 0)
    end
end

function update_event()
    local now = ag.elapsedTime()
    if (game_state == "waiting") then
        if (ag.isKeyDown("a") or ag.isKeyDown("left")) then
            position_x = position_x - (now - last_update_time) / 300
            if (position_x < -2) then
                position_x = -2
            end
        end
        if (ag.isKeyDown("d") or ag.isKeyDown("right")) then
            position_x = position_x + (now - last_update_time) / 300
            if (position_x > 2) then
                position_x = 2
            end
        end
    end
    last_update_time = now
    if (game_state == "launched") then
        if (now - launch_time > 7000) then
            endBall()
        end
    end
    setCamera(now)

    ag.callList(stars)
end

function mouse_motion_event(x, y, xrel, yrel)
    if (mouse_moved and game_state == "waiting") then
        target_x = target_x + xrel / 100
        if (target_x < -3) then
            target_x = -3
        elseif (target_x > 3) then
            target_x = 3
        end
    end
    mouse_moved = true
end

function drawFrameScore(width, height, frame, fs)
    local size = 60
    local ball_size = 24
    local local_width = size
    if (frame == 10) then
        local_width = 80
    end
    local base_x = size / 2 + size * (frame - 1)
    local base_y = height - 2 * size
    local str = string.format("%d", frame)
    local w, h = ag.getTextSize(str, 16)
    ag.drawText(str, 1, 0.6, 0, 16,
        base_x + local_width / 2 - w / 2, base_y + size + 5)
    ag.drawRect(1, 0.6, 0, local_width, size,
        base_x + local_width / 2, base_y + size / 2)
    ag.drawRect(1, 0.6, 0, ball_size, ball_size,
        base_x + local_width - ball_size / 2, base_y + size - ball_size / 2)
    if (frame == 10) then
        ag.drawRect(1, 0.6, 0, ball_size, ball_size,
            base_x + local_width - ball_size * 1.5,
            base_y + size - ball_size / 2)
    end

    local balls = 2
    if (frame == 10) then
        balls = 3
    end
    for ball = 1, balls do
        if (fs[ball] ~= -1) then
            local ball_base_x = base_x + local_width - (1 + balls - ball) * ball_size + 7
            local ball_base_y = base_y + size - ball_size + 7
            if (fs[ball] == 10) then                            -- strike
                ag.drawLine(1, 0.6, 0, ball_base_x, ball_base_y,
                    ball_base_x + 10, ball_base_y + 10)
                ag.drawLine(1, 0.6, 0, ball_base_x, ball_base_y + 10,
                    ball_base_x + 10, ball_base_y)
            elseif (ball == 2 and fs[1] + fs[2] == 10) then     -- spare
                ag.drawLine(1, 0.6, 0, ball_base_x, ball_base_y,
                    ball_base_x + 10, ball_base_y + 10)
            else
                ag.drawText(fs[ball], 1, 0.6, 0, 14, ball_base_x, ball_base_y)
            end
            if (fs["tot"] ~= -1) then
                local w, h = ag.getTextSize(fs["tot"], 16)
                ag.drawText(fs["tot"], 1, 0.6, 0, 16, base_x + local_width - 10 - w, base_y + 10)
            end
        end
    end
end

function drawScore(width, height)
    ag.drawText("Score:", 1, 0.6, 0, 16, 30, height - 25)
    for i = 1, 10 do
        drawFrameScore(width, height, i, score[i])
    end
end

function update_overlay_event(width, height)
    drawScore(width, height)
    if (game_state == "get_power" or game_state == "get_accuracy" or game_state == "launched") then
        drawPowerBar(width, height)
    end
    if (game_state == "get_accuracy" or game_state == "launched") then
        drawAccuracyBar(width, height)
    end
    if (game_state == "waiting") then
        local cursor_size = 8
        ag.drawLine(0, 1, 0, width / 2, height / 2 - cursor_size, width / 2, height / 2 + cursor_size)
        ag.drawLine(0, 1, 0, width / 2 - cursor_size, height / 2, width / 2 + cursor_size, height / 2)
    end
    if (game_state == "gameover") then
        local w, h = ag.getTextSize("GAME OVER", 72)
        ag.drawText("GAME OVER", 1, 1, 0, 72, width / 2 - w / 2, height * 0.7 + 20)
        local score_str = string.format("Score: %d", score[10]["tot"])
        w, h = ag.getTextSize(score_str, 56)
        ag.drawText(score_str, 1, 1, 0, 56, width / 2 - w / 2, height * 0.7 - 20 - h / 2)
    else
        local str = string.format("%d", current_frame)
        local w, h = ag.getTextSize(str, 72)
        ag.drawText(str, 1, 0.6, 0, 72, 20, 20)
        ag.drawText("Frame:", 1, 0.6, 0, 24, 20, 40 + h)
        str = string.format("%d", current_ball)
        w, h = ag.getTextSize(str, 72)
        ag.drawText(str, 1, 0.6, 0, 72, 200, 20)
        ag.drawText("Ball:", 1, 0.6, 0, 24, 200, 40 + h)
    end
end

function key_down_event(key)
    if (key == "g") then
        for i = 1, 9 do
            score[i][1] = i
            score[i][2] = 0
        end
        current_frame = 10
        newFrame()
    end
end

function mousebutton_down_event(button, x, y)
    if (button == 1) then
        if (game_state == "waiting") then
            getPower()
        elseif (game_state == "get_power") then
            getAccuracy()
        elseif (game_state == "get_accuracy") then
            launchBall()
        elseif (game_state == "gameover") then
            newGame()
        end
    end
end

function getPower()
    game_state = "get_power"
    power = 0
    power_last_time = ag.elapsedTime()
    power_direction = 1
end

function getAccuracy()
    game_state = "get_accuracy"
    accuracy = 0
    accuracy_last_time = ag.elapsedTime()
    accuracy_direction = 1
end

function launchBall()
    game_state = "launched"
    launch_time = ag.elapsedTime()
    bowling_ball = reference_ball:clone()
    bowling_ball:setPosition(position_x, -10, 1.26)
    local fx = target_x + accuracy * 3 - position_x
    local fy = 30               -- should match setCamera()'s dy
    local d = math.sqrt(fx*fx + fy*fy)
    fx = fx / d
    fy = fy / d
    local force = 150000
    force = force * 0.5 + force * 0.5 * power
    bowling_ball:addForce(fx * force, fy * force, 0)
end

function endBall()
    game_state = "waiting"
    bowling_ball:destroy()
    -- find all the pins that were knocked over
    local pin_count = 0
    for i = 1, 10 do
        if (pins[i] ~= nil) then
            local x, y, z = pins[i]:getPosition()
            if (z < pin_standing_position - 0.05) then
                pins[i]:destroy()
                pins[i] = nil
                pin_count = pin_count + 1
            end
        end
    end
    updateScore(pin_count)
end

function getNextBall(frame, ball)
    ball = ball + 1
    if (ball > 2 and frame < 10) then
        frame = frame + 1
        ball = 1
    end
    return frame, ball
end

function getNextBallAfterStrike(frame, ball)
    if (frame == 10) then
        ball = ball + 1
    else
        frame = frame + 1
    end
    return frame, ball
end

function updateScore(pin_count)
    score[current_frame][current_ball] = pin_count
    -- update total score
    for f = 1, 10 do
        local prevscore = 0
        if (f > 1) then
            prevscore = score[f-1]["tot"]
        end
        if (score[f][1] == 10) then        -- strike
            local nf, nb = getNextBallAfterStrike(f, 1)
            if (score[nf][nb] == -1) then
                break
            else
                local nf2, nb2
                if (score[nf][nb] == 10) then
                    nf2, nb2 = getNextBallAfterStrike(nf, nb)
                else
                    nf2, nb2 = getNextBall(nf, nb)
                end
                if (score[nf2][nb2] == -1) then
                    break
                else
                    score[f]["tot"] = prevscore + 10 + score[nf][nb] + score[nf2][nb2]
                end
            end
        elseif (score[f][1] == -1 or score[f][2] == -1) then
            break
        elseif (score[f][1] + score[f][2] == 10) then     -- spare
            local nf, nb = getNextBall(f, 2)
            if (score[nf][nb] == -1) then
                break
            else
                score[f]["tot"] = prevscore + 10 + score[nf][nb]
            end
        else
            score[f]["tot"] = prevscore + score[f][1] + score[f][2]
        end
    end
    if (pin_count == 10 and current_frame < 10) then
        current_frame = current_frame + 1
        newFrame()
    elseif (current_frame == 10) then
        if ((current_ball == 1 and pin_count == 10)
            or (current_ball == 2 and score[10][1] + score[10][2] == 10)) then
            resetPins()
        end
        current_ball = current_ball + 1
        if ((current_ball == 3 and (score[10][1] + score[10][2]) < 10)
            or (current_ball == 4)) then
            gameOver()
        end
    else
        current_ball = current_ball + 1
        if (current_ball > 2) then
            current_frame = current_frame + 1
            newFrame()
        end
    end
end

function newFrame()
    current_ball = 1
    resetPins()
end

function gameOver()
    game_state = "gameover"
end

function drawPowerBar(width, height)
    local bar_width = 80
    local bar_height = 400
    if (game_state == "get_power") then
        local now = ag.elapsedTime()
        power = power + (now - power_last_time) / 250 * power_direction
        if (power > 1) then
            power = 1
            power_direction = -1
        elseif (power < 0) then
            power = 0
            power_direction = 1
        end
        power_last_time = now
    end
    ag.drawRect(1, 1, 1, bar_width, bar_height,
        width - 50 - bar_width / 2, height / 2)
    ag.fillRect(0.1, 1, 0.1, bar_width - 2, (bar_height - 2) * power,
        width - 50 - bar_width / 2, height / 2)
end

function drawAccuracyBar(width, height)
    local bar_width = 400
    local bar_height = 60
    if (game_state == "get_accuracy") then
        local now = ag.elapsedTime()
        accuracy = accuracy + (now - accuracy_last_time) / 300 * accuracy_direction
        if (accuracy > 1) then
            accuracy = 1
            accuracy_direction = -1
        elseif (accuracy < -1) then
            accuracy = -1
            accuracy_direction = 1
        end
        accuracy_last_time = now
    end
    ag.drawRect(1, 1, 1, bar_width, bar_height, width / 2, 50 + bar_height / 2)
    ag.drawLine(1, 1, 1, width / 2, 50, width / 2, 50 + bar_height)
    ag.fillRect(0.1, 1, 0.1, 3, bar_height - 2, width / 2 + (bar_width - 4) / 2 * accuracy, 50 + bar_height / 2)
end
