local Player = require("player")
local Spawner = require("spawner")

local Game = {
  state = "title",
  score = 0,
  highScore = 0,
  lives = 3,
  stars = {},
  meteors = {},
  particles = {},
  backdrop = {},
  meteorTimer = 0,
  starTimer = 0,
  shake = 0,
  elapsed = 0,
  sounds = {},
}

local function circlesOverlap(a, b)
  local dx, dy = a.x - b.x, a.y - b.y
  local radii = a.radius + b.radius
  return dx * dx + dy * dy < radii * radii
end

local function makeTone(frequency, duration, volume)
  local sampleRate = 44100
  local sampleCount = math.floor(sampleRate * duration)
  local data = love.sound.newSoundData(sampleCount, sampleRate, 16, 1)

  for index = 0, sampleCount - 1 do
    local time = index / sampleRate
    local attack = math.min(1, time / 0.01)
    local release = 1 - index / sampleCount
    local sample = math.sin(math.pi * 2 * frequency * time)
    data:setSample(index, sample * volume * attack * release)
  end

  -- Sources made from SoundData are always static.
  return love.audio.newSource(data)
end

local function play(source)
  source:stop()
  source:play()
end

local function burst(x, y, color, amount)
  for _ = 1, amount do
    local angle = love.math.random() * math.pi * 2
    local speed = love.math.random(60, 190)
    table.insert(Game.particles, {
      x = x,
      y = y,
      vx = math.cos(angle) * speed,
      vy = math.sin(angle) * speed,
      life = love.math.random() * 0.35 + 0.35,
      maxLife = 0.7,
      color = color,
    })
  end
end

local function saveHighScore()
  if Game.score <= Game.highScore then return end
  Game.highScore = Game.score
  local success, message = love.filesystem.write("highscore.txt", tostring(Game.highScore))
  if not success then print("Could not save high score: " .. tostring(message)) end
end

local function resetGame()
  Game.state = "playing"
  Game.score = 0
  Game.lives = 3
  Game.stars = {}
  Game.meteors = {}
  Game.particles = {}
  Game.meteorTimer = 0.7
  Game.starTimer = 0
  Game.shake = 0
  Game.elapsed = 0
  Game.player = Player.new()
end

function Game.load()
  love.graphics.setBackgroundColor(0.035, 0.03, 0.07)
  Game.font = love.graphics.newFont(18)
  Game.bigFont = love.graphics.newFont(52)
  Game.sounds.collect = makeTone(660, 0.11, 0.22)
  Game.sounds.hit = makeTone(110, 0.22, 0.28)

  if love.filesystem.getInfo("highscore.txt", "file") then
    Game.highScore = tonumber(love.filesystem.read("highscore.txt")) or 0
  end

  for _ = 1, 90 do
    table.insert(Game.backdrop, {
      x = love.math.random() * love.graphics.getWidth(),
      y = love.math.random() * love.graphics.getHeight(),
      size = love.math.random() * 1.6 + 0.4,
      alpha = love.math.random() * 0.55 + 0.2,
    })
  end
end

local function updateParticles(dt)
  for index = #Game.particles, 1, -1 do
    local particle = Game.particles[index]
    particle.x = particle.x + particle.vx * dt
    particle.y = particle.y + particle.vy * dt
    particle.vx = particle.vx * (1 - math.min(1, 3 * dt))
    particle.vy = particle.vy * (1 - math.min(1, 3 * dt))
    particle.life = particle.life - dt
    if particle.life <= 0 then table.remove(Game.particles, index) end
  end
end

function Game.update(dt)
  updateParticles(dt)
  Game.shake = math.max(0, Game.shake - dt)

  if Game.state ~= "playing" then return end

  Game.elapsed = Game.elapsed + dt
  Player.update(Game.player, dt)

  Game.starTimer = Game.starTimer - dt
  if Game.starTimer <= 0 and #Game.stars < 3 then
    table.insert(Game.stars, Spawner.star(love.graphics.getDimensions()))
    Game.starTimer = 1.2
  end

  Game.meteorTimer = Game.meteorTimer - dt
  if Game.meteorTimer <= 0 then
    table.insert(Game.meteors, Spawner.meteor(love.graphics.getWidth(), Game.score / 6))
    Game.meteorTimer = math.max(0.3, 1.05 - Game.score * 0.018)
  end

  for index = #Game.stars, 1, -1 do
    local star = Game.stars[index]
    star.life = star.life - dt
    star.phase = star.phase + dt * 4

    if circlesOverlap(Game.player, star) then
      Game.score = Game.score + 1
      burst(star.x, star.y, {1, 0.83, 0.47}, 14)
      play(Game.sounds.collect)
      table.remove(Game.stars, index)
    elseif star.life <= 0 then
      table.remove(Game.stars, index)
    end
  end

  for index = #Game.meteors, 1, -1 do
    local meteor = Game.meteors[index]
    meteor.x = meteor.x + meteor.drift * dt
    meteor.y = meteor.y + meteor.speed * dt
    meteor.spin = meteor.spin + dt * 2

    if meteor.y - meteor.radius > love.graphics.getHeight() then
      table.remove(Game.meteors, index)
    elseif Game.player.invulnerable <= 0 and circlesOverlap(Game.player, meteor) then
      Game.lives = Game.lives - 1
      Game.player.invulnerable = 1.15
      Game.shake = 0.3
      burst(meteor.x, meteor.y, {1, 0.35, 0.48}, 22)
      play(Game.sounds.hit)
      table.remove(Game.meteors, index)

      if Game.lives <= 0 then
        Game.state = "gameover"
        saveHighScore()
      end
    end
  end
end

local function drawBackdrop()
  for _, point in ipairs(Game.backdrop) do
    love.graphics.setColor(0.72, 0.69, 1, point.alpha)
    love.graphics.circle("fill", point.x, point.y, point.size)
  end
end

local function drawWorld()
  for _, star in ipairs(Game.stars) do
    local pulse = math.sin(star.phase) * 2
    love.graphics.setColor(1, 0.83, 0.47, 0.18)
    love.graphics.circle("fill", star.x, star.y, star.radius + 9 + pulse)
    love.graphics.setColor(1, 0.83, 0.47)
    love.graphics.circle("fill", star.x, star.y, star.radius + pulse * 0.25)
  end

  for _, meteor in ipairs(Game.meteors) do
    love.graphics.push()
    love.graphics.translate(meteor.x, meteor.y)
    love.graphics.rotate(meteor.spin)
    love.graphics.setColor(0.98, 0.31, 0.44)
    love.graphics.polygon(
      "fill",
      -meteor.radius, -meteor.radius * 0.4,
      -meteor.radius * 0.25, -meteor.radius,
      meteor.radius, -meteor.radius * 0.2,
      meteor.radius * 0.55, meteor.radius,
      -meteor.radius * 0.65, meteor.radius * 0.7
    )
    love.graphics.pop()
  end

  for _, particle in ipairs(Game.particles) do
    local alpha = math.max(0, particle.life / particle.maxLife)
    love.graphics.setColor(particle.color[1], particle.color[2], particle.color[3], alpha)
    love.graphics.circle("fill", particle.x, particle.y, 3)
  end

  Player.draw(Game.player)
end

local function centered(text, y, font)
  love.graphics.setFont(font)
  love.graphics.printf(text, 0, y, love.graphics.getWidth(), "center")
end

function Game.draw()
  drawBackdrop()

  love.graphics.push()
  if Game.shake > 0 then
    love.graphics.translate(love.math.random(-5, 5), love.math.random(-5, 5))
  end
  if Game.player then drawWorld() end
  love.graphics.pop()

  love.graphics.setFont(Game.font)
  love.graphics.setColor(1, 1, 1)
  love.graphics.print("Score  " .. Game.score, 22, 18)
  love.graphics.printf("Lives  " .. Game.lives, 0, 18, love.graphics.getWidth() - 22, "right")

  if Game.state == "title" then
    love.graphics.setColor(0, 0, 0, 0.55)
    love.graphics.rectangle("fill", 0, 0, love.graphics.getDimensions())
    love.graphics.setColor(0.33, 0.91, 0.84)
    centered("ORBIT RUNNER", 178, Game.bigFont)
    love.graphics.setColor(1, 1, 1)
    centered("Collect gold signals. Dodge red meteors.", 250, Game.font)
    centered("Press Enter to launch", 286, Game.font)
    centered("Move with WASD or arrow keys", 326, Game.font)
  elseif Game.state == "gameover" then
    love.graphics.setColor(0, 0, 0, 0.68)
    love.graphics.rectangle("fill", 0, 0, love.graphics.getDimensions())
    love.graphics.setColor(1, 0.49, 0.62)
    centered("RUN OVER", 178, Game.bigFont)
    love.graphics.setColor(1, 1, 1)
    centered("Score: " .. Game.score .. "   Best: " .. Game.highScore, 252, Game.font)
    centered("Press Enter to try again", 292, Game.font)
  end
end

function Game.keypressed(key)
  if key == "return" and Game.state ~= "playing" then
    resetGame()
  elseif key == "escape" then
    love.event.quit()
  end
end

return Game
