class YouSoBasicGorillas
  attr_accessor :outputs, :grid, :state, :inputs

  def tick
    defaults
    render
    calc
    process_inputs
  end

  def defaults
    outputs.background_color = [33, 32, 87]
    state.building_spacing       = 1
    state.building_room_spacing  = 15
    state.building_room_width    = 10
    state.building_room_height   = 15
    state.building_heights       = [4, 4, 6, 8, 15, 20, 18]
    state.building_room_sizes    = [5, 4, 6, 7]
    state.gravity                = 0.25
    state.first_strike         ||= :player_1
    state.buildings            ||= []
    state.holes                ||= []
    state.player_1_score       ||= 0
    state.player_2_score       ||= 0
    state.wind                 ||= 0
  end

  def render
    render_stage
    render_value_insertion
    render_gorillas
    render_holes
    render_banana
    render_game_over
    render_score
    render_wind
  end

  def render_score
    outputs.primitives << [0, 0, 1280, 31, fancy_white].solid
    outputs.primitives << [1, 1, 1279, 29].solid
    outputs.labels << [  10, 25, "Score: #{state.player_1_score}", 0, 0, fancy_white]
    outputs.labels << [1270, 25, "Score: #{state.player_2_score}", 0, 2, fancy_white]
  end

  def render_wind
    outputs.primitives << [640, 12, state.wind * 500 + state.wind * 10 * rand, 4, 35, 136, 162].solid
    outputs.lines     <<  [640, 30, 640, 0, fancy_white]
  end

  def render_game_over
    return unless state.over
    outputs.primitives << [grid.rect, 0, 0, 0, 200].solid
    outputs.primitives << [640, 370, "Game Over!!", 5, 1, fancy_white].label
    if state.winner == :player_1
      outputs.primitives << [640, 340, "Player 1 Wins!!", 5, 1, fancy_white].label
    else
      outputs.primitives << [640, 340, "Player 2 Wins!!", 5, 1, fancy_white].label
    end
  end

  def render_stage
    return unless state.stage_generated
    return if state.stage_rendered

    outputs.static_solids << [grid.rect, 33, 32, 87]
    outputs.static_solids << state.buildings.map(&:solids)
    state.stage_rendered = true
  end

  def render_gorilla gorilla, id
    return unless gorilla
    if state.banana && state.banana.owner == gorilla
      animation_index  = state.banana.created_at.frame_index(3, 5, false)
    end
    if !animation_index
      outputs.sprites << [gorilla.solid, "sprites/#{id}-idle.png"]
    else
      outputs.sprites << [gorilla.solid, "sprites/#{id}-#{animation_index}.png"]
    end
  end

  def render_gorillas
    render_gorilla state.player_1, :left
    render_gorilla state.player_2, :right
  end

  def render_value_insertion
    return if state.banana
    return if state.over

    if    state.current_turn == :player_1_angle
      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}_",    fancy_white]
    elsif state.current_turn == :player_1_velocity
      outputs.labels << [  10, 710, "Angle:    #{state.player_1_angle}",     fancy_white]
      outputs.labels << [  10, 690, "Velocity: #{state.player_1_velocity}_", fancy_white]
    elsif state.current_turn == :player_2_angle
      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}_",    fancy_white]
    elsif state.current_turn == :player_2_velocity
      outputs.labels << [1120, 710, "Angle:    #{state.player_2_angle}",     fancy_white]
      outputs.labels << [1120, 690, "Velocity: #{state.player_2_velocity}_", fancy_white]
    end
  end

  def render_banana
    return unless state.banana
    rotation = state.tick_count.%(360) * 20
    rotation *= -1 if state.banana.dx > 0
    outputs.sprites << [state.banana.x, state.banana.y, 15, 15, 'sprites/banana.png', rotation]
  end

  def render_holes
    outputs.sprites << state.holes.map do |s|
      animation_index = s.created_at.frame_index(7, 3, false)
      if animation_index
        [s.sprite, [s.sprite.rect, "sprites/explosion#{animation_index}.png" ]]
      else
        s.sprite
      end
    end
  end

  def calc
    calc_generate_stage
    calc_current_turn
    calc_banana
  end

  def calc_current_turn
    return if state.current_turn

    state.current_turn = :player_1_angle
    state.current_turn = :player_2_angle if state.first_strike == :player_2
  end

  def calc_generate_stage
    return if state.stage_generated

    state.buildings << building_prefab(state.building_spacing + -20, *random_building_size)
    8.numbers.inject(state.buildings) do |buildings, i|
      buildings <<
        building_prefab(state.building_spacing +
                        state.buildings.last.right,
                        *random_building_size)
    end

    building_two = state.buildings[1]
    state.player_1 = new_player(building_two.x + building_two.w.fdiv(2),
                               building_two.h)

    building_nine = state.buildings[-3]
    state.player_2 = new_player(building_nine.x + building_nine.w.fdiv(2),
                               building_nine.h)
    state.stage_generated = true
    state.wind = 1.randomize(:ratio, :sign)
  end

  def new_player x, y
    state.new_entity(:gorilla) do |p|
      p.x = x - 25
      p.y = y
      p.solid = [p.x, p.y, 50, 50]
    end
  end

  def calc_banana
    return unless state.banana

    state.banana.x  += state.banana.dx
    state.banana.dx += state.wind.fdiv(50)
    state.banana.y  += state.banana.dy
    state.banana.dy -= state.gravity
    banana_collision = [state.banana.x, state.banana.y, 10, 10]

    if state.player_1 && banana_collision.intersect_rect?(state.player_1.solid)
      state.over = true
      if state.banana.owner == state.player_2
        state.winner = :player_2
      else
        state.winner = :player_1
      end

      state.player_2_score += 1
    elsif state.player_2 && banana_collision.intersect_rect?(state.player_2.solid)
      state.over = true
      if state.banana.owner == state.player_2
        state.winner = :player_1
      else
        state.winner = :player_2
      end
      state.player_1_score += 1
    end

    if state.over
      place_hole
      return
    end

    return if state.holes.any? do |h|
      h.sprite.scale_rect(0.8, 0.5, 0.5).intersect_rect? [state.banana.x, state.banana.y, 10, 10]
    end

    return unless state.banana.y < 0 || state.buildings.any? do |b|
      b.rect.intersect_rect? [state.banana.x, state.banana.y, 1, 1]
    end

    place_hole
  end

  def place_hole
    return unless state.banana

    state.holes << state.new_entity(:banana) do |b|
      b.sprite = [state.banana.x - 20, state.banana.y - 20, 40, 40, 'sprites/hole.png']
    end

    state.banana = nil
  end

  def process_inputs_main
    return if state.banana
    return if state.over

    if inputs.keyboard.key_down.enter
      input_execute_turn
    elsif inputs.keyboard.key_down.backspace
      state.as_hash[state.current_turn] ||= ""
      state.as_hash[state.current_turn]   = state.as_hash[state.current_turn][0..-2]
    elsif inputs.keyboard.key_down.char
      state.as_hash[state.current_turn] ||= ""
      state.as_hash[state.current_turn]  += inputs.keyboard.key_down.char
    end
  end

  def process_inputs_game_over
    return unless state.over
    return unless inputs.keyboard.key_down.truthy_keys.any?
    state.over = false
    outputs.static_solids.clear
    state.buildings.clear
    state.holes.clear
    state.stage_generated = false
    state.stage_rendered = false
    if state.first_strike == :player_1
      state.first_strike = :player_2
    else
      state.first_strike = :player_1
    end
  end

  def process_inputs
    process_inputs_main
    process_inputs_game_over
  end

  def input_execute_turn
    return if state.banana

    if state.current_turn == :player_1_angle && parse_or_clear!(:player_1_angle)
      state.current_turn = :player_1_velocity
    elsif state.current_turn == :player_1_velocity && parse_or_clear!(:player_1_velocity)
      state.current_turn = :player_2_angle
      state.banana =
        new_banana(state.player_1,
                   state.player_1.x + 25,
                   state.player_1.y + 60,
                   state.player_1_angle,
                   state.player_1_velocity)
    elsif state.current_turn == :player_2_angle && parse_or_clear!(:player_2_angle)
      state.current_turn = :player_2_velocity
    elsif state.current_turn == :player_2_velocity && parse_or_clear!(:player_2_velocity)
      state.current_turn = :player_1_angle
      state.banana =
        new_banana(state.player_2,
                   state.player_2.x + 25,
                   state.player_2.y + 60,
                   180 - state.player_2_angle,
                   state.player_2_velocity)
    end

    if state.banana
      state.player_1_angle = nil
      state.player_1_velocity = nil
      state.player_2_angle = nil
      state.player_2_velocity = nil
    end
  end

  def random_building_size
    [state.building_heights.sample, state.building_room_sizes.sample]
  end

  def int? v
    v.to_i.to_s == v.to_s
  end

  def random_building_color
    [[ 99,   0, 107],
     [ 35,  64, 124],
     [ 35, 136, 162],
     ].sample
  end

  def random_window_color
    [[ 88,  62, 104],
     [253, 224, 187]].sample
  end

  def windows_for_building starting_x, floors, rooms
    floors.-(1).combinations(rooms - 1).map do |floor, room|
      [starting_x +
       state.building_room_width.*(room) +
       state.building_room_spacing.*(room + 1),
       state.building_room_height.*(floor) +
       state.building_room_spacing.*(floor + 1),
       state.building_room_width,
       state.building_room_height,
       random_window_color]
    end
  end

  def building_prefab starting_x, floors, rooms
    state.new_entity(:building) do |b|
      b.x      = starting_x
      b.y      = 0
      b.w      = state.building_room_width.*(rooms) +
                 state.building_room_spacing.*(rooms + 1)
      b.h      = state.building_room_height.*(floors) +
                 state.building_room_spacing.*(floors + 1)
      b.right  = b.x + b.w
      b.rect   = [b.x, b.y, b.w, b.h]
      b.solids = [[b.x - 1, b.y, b.w + 2, b.h + 1, fancy_white],
                  [b.x, b.y, b.w, b.h, random_building_color],
                  windows_for_building(b.x, floors, rooms)]
    end
  end

  def parse_or_clear! game_prop
    if int? state.as_hash[game_prop]
      state.as_hash[game_prop] = state.as_hash[game_prop].to_i
      return true
    end

    state.as_hash[game_prop] = nil
    return false
  end

  def new_banana owner, x, y, angle, velocity
    state.new_entity(:banana) do |b|
      b.owner     = owner
      b.x         = x
      b.y         = y
      b.angle     = angle % 360
      b.velocity  = velocity / 5
      b.dx        = b.angle.vector_x(b.velocity)
      b.dy        = b.angle.vector_y(b.velocity)
    end
  end

  def fancy_white
    [253, 252, 253]
  end
end

$you_so_basic_gorillas = YouSoBasicGorillas.new

def tick args
  $you_so_basic_gorillas.outputs = args.outputs
  $you_so_basic_gorillas.grid    = args.grid
  $you_so_basic_gorillas.state    = args.state
  $you_so_basic_gorillas.inputs  = args.inputs
  $you_so_basic_gorillas.tick
end