module main

import (
  std.math math
  vendor.raylib rl
)

let calculate_flap_velocity(): f64 = math.sqrt(-2.0 * gravity * flap_height)

let random_pipe_gap_y(): f64 {
  let margin: f64 = 0.05
  let minimum_gap_y = base_height_fraction + pipe_gap_size / 2.0 + margin
  let maximum_gap_y = 1.0 - pipe_gap_size / 2.0 - margin

  let random_value = rl.get_random_value(0, 1000).(f64) / 1000.0

  return minimum_gap_y + random_value * (maximum_gap_y - minimum_gap_y)
}

let initialise_pipes(pipes: []mut Pipe) {
  let first_pipe_x: f64 = 1.2

  for let mut i: usz = 0; i < @len(pipes); i += 1 {
    pipes[i].x = first_pipe_x + i.(f64) * pipe_spacing
    pipes[i].gap_y = random_pipe_gap_y()
    pipes[i].passed = false
  }
}

let update_bird(state: *mut GameState, assets: *GameAssets, flap: bool) {
  if flap {
    state.bird_velocity_y = calculate_flap_velocity()
    rl.play_sound(assets.wing)
  }

  state.bird_velocity_y = max(
    state.bird_velocity_y + gravity * state.delta_time,
    terminal_velocity,
  )
  state.bird_y = state.bird_y + state.bird_velocity_y * state.delta_time

  let floor_y = bird_size / 2.0
  let ceiling_y = 1.0 - bird_size / 2.0

  if state.bird_y <= floor_y {
    state.bird_y = floor_y
    state.bird_velocity_y = 0.0
    state.lost = true
    return
  }

  if state.bird_y >= ceiling_y {
    state.bird_y = ceiling_y
    state.bird_velocity_y = 0.0
    state.lost = true
  }
}

let find_rightmost_pipe_x(pipes: []Pipe): f64 {
  let mut rightmost_x = pipes[0].x

  for pipe in pipes {
    rightmost_x = max(rightmost_x, pipe.x)
  }

  return rightmost_x
}

let update_pipes(state: *mut GameState): bool {
  let speed_multiplier = if state.double_pipe_speed {
    double_pipe_speed_multiplier
  } else {
    1.0
  }
  let movement = pipe_speed * speed_multiplier * state.delta_time
  let mut passed_pipe = false

  for let mut i: usz = 0; i < @len(state.pipes); i += 1 {
    state.pipes[i].x = state.pipes[i].x - movement

    if !state.pipes[i].passed && state.pipes[i].x < bird_x {
      state.pipes[i].passed = true
      passed_pipe = true
    }
  }

  let pipe_half_width = pipe_width / 2.0
  let mut rightmost_x = find_rightmost_pipe_x(state.pipes)

  // Reuse pipes that have left the screen instead of shifting or allocating.
  for let mut i: usz = 0; i < @len(state.pipes); i += 1 {
    if state.pipes[i].x + pipe_half_width < 0.0 {
      state.pipes[i].x = rightmost_x + pipe_spacing
      state.pipes[i].gap_y = random_pipe_gap_y()
      state.pipes[i].passed = false
      rightmost_x = state.pipes[i].x
    }
  }

  if passed_pipe {
    state.points += if state.double_pipe_speed {
      double_pipe_points_multiplier
    } else {
      1
    }
  }

  return passed_pipe
}

let reset_game(state: *mut GameState) {
  state.bird_y = 0.5
  state.bird_velocity_y = 0.0
  state.delta_time = 0.0
  state.points = 0
  state.lost = false
  state.time_since_loss = 0.0

  initialise_pipes(state.pipes)
}

let play_loss_sounds(assets: *GameAssets) {
  rl.play_sound(assets.hit)
  rl.play_sound(assets.die)
}

let update(state: *mut GameState, assets: *GameAssets) {
  state.delta_time = rl.get_frame_time()
  let action_pressed = rl.is_key_pressed(.Space) || rl.is_mouse_button_pressed(.Left)

  if state.started && !state.lost && rl.is_key_pressed(.Escape) {
    state.paused = !state.paused
  }

  if rl.is_key_pressed(.F3) {
    state.show_collision_boxes = !state.show_collision_boxes
  }

  if (!state.started || state.lost) && rl.is_key_pressed(.F9) {
    state.double_pipe_speed = !state.double_pipe_speed
  }

  if !state.started {
    if action_pressed {
      state.started = true
    } else {
      return
    }
  }

  if state.paused && action_pressed {
    state.paused = false
  }

  if state.paused {
    return
  }

  if state.lost {
    state.time_since_loss += state.delta_time

    if state.time_since_loss < restart_grace_period || !action_pressed {
      return
    }

    reset_game(state)
  }

  state.background_scroll = state.background_scroll + pipe_speed * background_scroll_speed * state.delta_time
  if state.background_scroll >= 1.0 {
    state.background_scroll = state.background_scroll - 1.0
  }

  state.base_scroll = state.base_scroll + pipe_speed * base_scroll_speed * state.delta_time
  if state.base_scroll >= 1.0 {
    state.base_scroll = state.base_scroll - 1.0
  }

  update_bird(state, assets, action_pressed)
  if state.lost {
    play_loss_sounds(assets)
    return
  }

  if update_pipes(state) {
    rl.play_sound(assets.point)
  }

  if bird_collides_with_pipes(state) {
    state.lost = true
    play_loss_sounds(assets)
  }
}