require "app/includes.rb"

$gtk.disable_framerate_warning!

def tick args
  args.state.game.start_fps = Time.now.to_f
  args.state.game.fps = Time.now.to_f
  settings_init(args)
  load_game_maps(args)

  init_world_minimap(args)
  if args.state.tick_count.zero? # or args.state.current_map != args.state.map_name
    set_map(args, "overworld")
  end

  create_buffer_rt(args)

  position = args.state.room_offset.copy
  args.outputs.primitives << {**position, w: 512, h: 352, path: :buffer, primitive_marker: :sprite}

  process_global_input(args)

  if !args.state.change_worlds
    process_room_movement(args)
    process_room(args)

    # draw_minimap(args)
  end

  draw_collisions(args) if args.state.should_draw_collisions
  draw_world_change(args) if args.state.change_worlds

  draw_fps(args)
  draw_hud(args)
  if args.state.show_debug_info
    draw_debug_info(args)
  end

  draw_tile_coords(args) if args.state.game.show_tile_coords

  show_controls_dialog(args) if args.state.show_controls_window
  draw_debug_rects(args)

  args.state.game.fps = Time.now.to_f - args.state.game.start_fps
end

def load_game_maps(args)
  return unless args.tick_count.zero?
  $gtk.notify! "Loading Game Maps", 90
  load_overworld(args)
  load_caves(args)
  
  set_map(args, "overworld")
  $gtk.notify! "Finished loading Game Maps", 90
end

def set_map(args, name)
  raise StandardError, "Map \"#{name}\" doesn't exist." unless args.state.maps.has_key?(name)
  # args.state.map_name = nil
  args.state.map_name = name
  args.state.current_map = args.state.maps[name]
  reset_ground_items(args)

end

def load_caves(args)
  caves = MapManager.new(args, "caves")

  args.state.maps["caves"] = caves
end

def load_overworld(args)
  map_name = "overworld"
  overworld = MapManager.new(args, map_name)

  # map = overworld.room_at(0, 0)

  args.state.maps[map_name] = overworld

  if overworld.main_spawn.x != nil
    args.state.player.start_room = overworld.start_room.copy
    args.state.player.x = overworld.main_spawn.x
    args.state.player.y = overworld.main_spawn.y
  end
  start_room = args.state.player.start_room

  puts "start room #{start_room}"
  args.state.player.current_room_coordinates = start_room.copy
end

def reset_ground_items(args)
  args.state.ground_items = [] if args.state.ground_items.count > 0
end

def add_item_money(args, x, y)
  x = x.to_i * 32
  y = y.to_i * 32
  money = Money.new(args, x, y)
  args.state.ground_items.append money
end

def add_item_bomb(args, x, y)
  x = x.to_i * 32
  y = y.to_i * 32
  bomb = Bomb.new(args, x, y)
  args.state.ground_items.append bomb
end

def add_item_life(args, x, y)
  x = x.to_i * 32
  y = y.to_i * 32
  money = Life.new(args, x, y)
  args.state.ground_items.append money
end

def process_ground_items(args)
  ground_items = []
  args.state.ground_items.each do | item |
    item.tick(args)
    ground_items.append item.render
  end

  args.state.ground_items.reject! do | item |
    item.should_die
  end
  args.outputs[:buffer].primitives << ground_items

  collided_item = args.state.ground_items.find do | item |
    if item.collision_rect.intersect_rect?  args.state.player.collision_rect
      puts "collided with item"
      item
    end
  end

  if collided_item
    pickup_item(args, collided_item)
  end
end

def pickup_item(args, item)
  case item
  when Life
    args.audio[:money_get] = {input: "sounds/Pickup_00.wav"}
  when Money
    args.audio[:money_get] = {input: "sounds/Coin01.wav"}
    add_money(args, item.value)
  when Bomb
    count = args.state.player.bomb_count
    max_count = args.state.player.bomb_count_max #currently 8
    args.state.player.bomb_count = min(count + 1, max_count)
    args.state.player.bomb_display.value = args.state.player.bomb_count
  end
  item.mark_dead

end

def add_money(args, value)
  args.state.player.money_count += value
  args.state.player.money_display.value = args.state.player.money_count
end

def process_room(args)
  if args.state.room_transitioning
    reset_ground_items(args)
    draw_transition(args, args.state.player.direction)
  else
    # puts "process room"
    current_room = args.state.player.current_room_coordinates

    current_map = args.state.current_map#args.state.maps[args.state.current_map]
    current_map.tick args, current_room.x, current_room.y
    
    if can_process_player_input(args)
      process_player_input(args)
    end
    args.state.world.minimap.tick(args)
    draw_room(args)
    process_ground_items(args)

    # args.state.player.minimap.tick(args)
    check_room_transition(args)
  end

  if args.state.player.inventory.is_closed?
    draw_player(args)
  end
end

def can_process_player_input(args)
  current_room = args.state.player.current_room_coordinates
  current_map = args.state.current_map

  args.state.player.inventory.is_closed?
end

def draw_room args
  current_room = args.state.player.current_room_coordinates

  current_map = args.state.current_map#args.state.maps[args.state.current_map]
  map = current_map.room_at(current_room.x, current_room.y)
  scale = args.state.draw_scale

  map.draw(args, :buffer)
  draw_display_name(args, current_map) if args.state.game.show_room_names
  draw_map_links(args, current_map) if args.state.game.show_room_links
  draw_interactive_items(args, current_map)
  draw_npcs(args, current_map)
  draw_typing_labels(args, current_map)
  draw_enemies(args, current_map)
  draw_projectiles(args, current_map)

  args.state.player.inventory.tick(args)
end

def draw_display_name(args, map)
  current_room = args.state.player.current_room_coordinates

  display_name = map.display_name_at(current_room.x, current_room.y)
  if display_name
    hash = display_name.to_h
    hash.font = args.state.text_font
    if display_name.respond_to?(:color)
      hash.merge!(display_name.color.to_h)
    end
    args.outputs[:buffer].labels << hash
  end
end

def draw_map_links(args, map)
  current_room = args.state.player.current_room_coordinates

  links = map.links_for_map(current_room.x, current_room.y)
  if not links.nil?
    rects = []
    rects = links.map do | link |
      rect = link.collision_rect
      rect.merge!({**Color::RED.copy, path: :pixel})
    end
    args.outputs[:buffer].primitives << rects
  end
end

def draw_interactive_items(args, map)
  current_room = args.state.player.current_room_coordinates

  ground_items = map.ground_items_at(current_room.x, current_room.y)

  return if ground_items.nil?
  return if ground_items.empty?


  ground_items.each do | item |
    args.outputs[:buffer].primitives << item.to_h
  end
end

def draw_npcs(args, map)
  current_room = args.state.player.current_room_coordinates

  npcs = map.npcs_at(current_room.x, current_room.y)

  return if npcs.nil?
  return if npcs.empty?

  npcs.each do | npc |
    args.outputs[:buffer].primitives << npc.render
  end
end

def draw_typing_labels(args, map)
  current_room = args.state.player.current_room_coordinates

  tick_objects = map.tick_objects_at(current_room.x, current_room.y)

  return if tick_objects.nil?
  return if tick_objects.empty?

  tick_objects.each do | obj |
    args.outputs[:buffer].primitives << obj.to_h
  end

  # puts tick_objects
end

def draw_enemies(args, map)
  current_room = args.state.player.current_room_coordinates
  enemies = map.enemies_at(current_room.x, current_room.y)

  # puts enemies
  # return if enemies.nil?
  return if enemies.empty?

  args.outputs[:buffer].primitives << enemies
end

# def draw_minimap(args)
#   # puts args.state.world.minimap
#   w = args.state.world.minimap.w
#   h = args.state.world.minimap.h

#   args.state.world.minimap.draw(args)
#   #args.outputs.primitives << {x: 300, y: 500, w: w * 3, h: h*3, path: :world_minimap, flip_vertically: true, primitive_marker: :sprite}

# end

def draw_transition(args, direction)
  scale = args.state.draw_scale #this works but doens't... just keep it at 1.0 for now
  draw_size = {w: 512, h: 352}

  current_room = args.state.player.current_room_coordinates #this is the room coordinates {x: , y: }
  map = args.state.current_map
  old_room = map.room_at(current_room.x, current_room.y)
  new_room = nil

  case direction
  when :right
    #if we haven't started the transition yet, set it to true and tell the transition code which rooms we want to show
    if not args.state.room_started_trasition
      args.state.room_started_trasition = true
      args.state.room_transitioning = true

      args.state.old_room_x = 0
      args.state.new_room_x = args.state.room_width
    end
    new_room = map.room_at(current_room.x + 1, current_room.y) #.maps[current_room.x + 1][current_room.y]
  
    #use the transition speed from the global state.
    args.state.old_room_x -= args.state.room_trasition_speed.x
    args.state.new_room_x -= args.state.room_trasition_speed.x
    
    #lets make suere our player never hits the backside of the room, when transitioning right
    if (args.state.player.x - args.state.room_trasition_speed.x > (args.state.room_trasition_speed.x / 2)) #and args.state.room_transitioning
      args.state.player.x -= args.state.room_trasition_speed.x
    end

    #when we are done with the transition, set values back to what they'd normally be. for the direction, right
    if args.state.new_room_x <= 0
      args.state.room_transitioning = false
      args.state.player.current_room_coordinates.x += 1
    end
  when :left
    #if we haven't started the transition yet, set it to true and tell the transition code which rooms we want to show
    if not args.state.room_started_trasition
      args.state.room_started_trasition = true
      args.state.room_transitioning = true

      args.state.old_room_x = 0
      args.state.new_room_x = -args.state.room_width
    end
    # puts "transition left"
    new_room = map.room_at(current_room.x - 1, current_room.y)#.maps[current_room.x - 1][current_room.y]

    args.state.old_room_x += args.state.room_trasition_speed.x
    args.state.new_room_x += args.state.room_trasition_speed.x

    #lets make suere our player never hits the backside of the room, when transitioning right
    if (args.state.player.x + args.state.room_trasition_speed.x) < (args.state.room_width - args.state.player.w)
      args.state.player.x += args.state.room_trasition_speed.x
    end

    #when we are done with the transition, set values back to what they'd normally be. for the direction, left
    if args.state.new_room_x >= 0
      args.state.room_transitioning = false
      args.state.player.current_room_coordinates.x -= 1
    end
  when :up
    if not args.state.room_started_trasition
      args.state.room_started_trasition = true
      args.state.room_transitioning = true

      args.state.old_room_y = 0
      args.state.new_room_y = args.state.room_height
    end

    new_room = map.room_at(current_room.x, current_room.y - 1)#.maps[current_room.x][current_room.y - 1]

    #set state values to the new and old room x values here
    args.state.old_room_y -= args.state.room_trasition_speed.y
    args.state.new_room_y -= args.state.room_trasition_speed.y

    #lets make suere our player never hits the backside of the room, when transitioning right
    if (args.state.player.y - args.state.room_trasition_speed.y) > 0
      args.state.player.y -= args.state.room_trasition_speed.y
    end

    #when we are done with the transition, set values back to what they'd normally be. for the direction, left
    if args.state.new_room_y <= 0
      args.state.room_transitioning = false
      args.state.player.current_room_coordinates.y -= 1

    end
  when :down
    if not args.state.room_started_trasition
      args.state.room_started_trasition = true

      args.state.old_room_y = 0
      args.state.new_room_y = -args.state.room_height
    end

    new_room = map.room_at(current_room.x, current_room.y + 1) #.maps[current_room.x][current_room.y + 1]

    #set state values to the new and old room x values here
    args.state.old_room_y += args.state.room_trasition_speed.y
    args.state.new_room_y += args.state.room_trasition_speed.y
    #lets make suere our player never hits the backside of the room, when transitioning right
    if (args.state.player.y + args.state.room_trasition_speed.y) < (args.state.room_height - args.state.player.h)
      args.state.player.y += args.state.room_trasition_speed.y
    end

    #when we are done with the transition, set values back to what they'd normally be. for the direction, left
    if args.state.new_room_y >= 0
      args.state.room_transitioning = false
      args.state.player.current_room_coordinates.y += 1
    end
  end

  #draw stuff to buffer
  # args.outputs[:buffer].solids << {x: 0, y: 0, **draw_size, **Color::BLUE} #so we can see when a room is empty or there's an error

  #create old/new room render targets for the transition
  args.outputs[:old_room].transient!
  args.outputs[:old_room].w = 512
  args.outputs[:old_room].h = 352

  args.outputs[:new_room].transient!
  args.outputs[:new_room].w = 512
  args.outputs[:new_room].h = 352


  old_room.draw(args, :old_room)
  new_room.draw(args, :new_room)
  rooms = [
    {x: 0, y: 0, **draw_size, **Color::BLUE, path: :sprite},
    {x: args.state.new_room_x, y: args.state.new_room_y, **draw_size, path: :new_room}#.to_sprite
  ]

  if args.state.room_transitioning
    rooms << {x: args.state.old_room_x, y: args.state.old_room_y, **draw_size, path: :old_room}#.to_sprite
  end

  args.outputs[:buffer].primitives << rooms
  
  #now draw to the normal output
  args.outputs.primitives << {**args.state.room_offset, w: draw_size.w * scale, h: draw_size.h * scale, path: :buffer}#.to_sprite

  if not args.state.room_transitioning
    reset_room_positions(args)
    args.state.room_started_trasition = false

    args.state.map = new_room
  end
end

def reset_room_positions(args)
  args.state.old_room_x = 0
  args.state.old_room_y = 0
  args.state.new_room_x = 0
  args.state.new_room_y = 0
end


def check_room_transition args
  player = args.state.player
  should_transition = args.state.should_transition
  direction = nil
  case player.direction
  when :up
    if not player.transition_rects.up.inside_rect?(args.state.room_transition_rect) or args.state.room_transitioning
      should_transition = true
      direction = :up
      args.state.room_transitioning = true

      args.state.old_room_x = 0
      args.state.old_room_y = 0
      args.state.new_room_x = 0
      args.state.new_room_y = 352

    end
  when :right
    if not player.transition_rects.right.inside_rect?(args.state.room_transition_rect) or args.state.room_transitioning
      should_transition = true
      direction = :right
      args.state.room_transitioning = true

      args.state.old_room_x = 0
      args.state.old_room_y = 0
      args.state.new_room_x = 512
      args.state.new_room_y = 0
    end
  when :down
    if not player.transition_rects.down.inside_rect?(args.state.room_transition_rect) or args.state.room_transitioning
      should_transition = true
      direction = :down
      args.state.room_transitioning = true

      args.state.old_room_x = 0
      args.state.old_room_y = 0
      args.state.new_room_x = 0
      args.state.new_room_y = 352
    end
  when :left
    if not player.transition_rects.left.inside_rect?(args.state.room_transition_rect) or args.state.room_transitioning
      should_transition = true
      direction = :left
      args.state.room_transitioning = true

      args.state.old_room_x = 0
      args.state.old_room_y = 0
      args.state.new_room_x = -352
      args.state.new_room_y = 0
    end
  end

  return {should_transition: should_transition, direction: direction }
end

def draw_fps args
  time_diff_txt = format("%.4f", args.state.game.fps.to_f % 0.04)
  
  fps_label = {x: 140.from_right, y: 40.from_top, **Color::WHITE, text: time_diff_txt , font: args.state.text_font}
  args.outputs.labels << fps_label
  args.outputs.labels << { x: 120.from_right, y: 10.from_top, **Color::WHITE, text: "#{args.gtk.current_framerate.to_sf}", font: args.state.text_font }

  # room_rect = {x:  args.state.room_offset.x - 30, y: args.state.room_offset.y - 30, w: args.state.room_width + 60, h: args.state.room_height + 60}
  # args.outputs.primitives << room_rect.merge(Color::WHITE).merge({primitive_marker: :border})

end

def draw_projectiles(args, map)
  current_room = args.state.player.current_room_coordinates
  projectiles = map.projectiles_at(current_room.x, current_room.y, true)
  return if projectiles.nil?
  return if projectiles.empty?
  
  args.outputs[:buffer].primitives << projectiles
  # args.outputs.primitives << projectiles
end

def create_buffer_rt args
  draw_size = {w: 32 * 16, h: 32 * 11}
  
  args.outputs[:buffer].transient!
  args.outputs[:buffer].w = draw_size.w
  args.outputs[:buffer].h = draw_size.h
end

def create_room_rt(args, rt_name)
  raise ArgumentError.new("args must be of type GTK::Args") unless args.class == GTK::Args
  raise ArgumentError.new("rt_name must be a Symbol") unless rt_name.class == Symbol

  args.outputs[rt_name].transient!
  args.outputs[rt_name].w = 512
  args.outputs[rt_name].h = 352
end

def process_room_movement(args)
  return unless args.inputs.keyboard.key_down.shift
  current_room = args.state.player.current_room_coordinates
  if args.inputs.keyboard.key_down.left
    if current_room.x - 1 >= 0
      args.state.player.current_room_coordinates.x -= 1
    end
  end
  if args.inputs.keyboard.key_down.right
    if (current_room.x + 1) < args.state.overworld.room_count.x
      args.state.player.current_room_coordinates.x += 1
    end
  end
  if args.inputs.keyboard.key_down.up
    if (current_room.y - 1) >= 0
      args.state.player.current_room_coordinates.y -= 1
    end
  end
  if args.inputs.keyboard.key_down.down
    if (current_room.y + 1) < args.state.overworld.room_count.y
      args.state.player.current_room_coordinates.y += 1
    end
  end
end

def check_collisions(args, player_rect)
  current_room = args.state.player.current_room_coordinates
  map = args.state.current_map

  room = map.room_at(current_room.x, current_room.y)
  player_collided = room.collision_objects.any? do |object|
    next if object.empty?
    object.intersect_rect?(player_rect)
  end

  player_collided
end

def init_world_minimap(args)
  return unless args.tick_count.zero?
  puts "make minimap"
  args.state.world.minimap ||= WorldMinimap.new(args)
end

def draw_collisions(args)
  current_room = args.state.player.current_room_coordinates

  map = args.state.current_map
  room = map.room_at(current_room.x, current_room.y)
  collision_objects = room.collision_objects

  color = {r: 0, g: 0, b: 0, a: 128}
  rects = collision_objects.each do | object |
    {x: object.x, y: object.y, w: object.w, h: object.h, color: color, primitive_marker: :solid}
  end

  args.outputs[:buffer].borders << rects
end

def draw_world_change(args)
  rt_name = :change_buffer
  create_room_rt(args, rt_name)
  old_room = args.state.transition.room_refs[:old_room]
  new_room = args.state.transition.room_refs[:new_room]

  room_to_draw = old_room
  if args.state.transition.start_animation && args.state.transition.exiting
    args.state.transition.last_tick = args.state.tick_count
    args.state.transition_bar.left += 8
    args.state.transition_bar.right -= 8
    room_to_draw = old_room
  end

  if args.state.transition.start_animation && args.state.transition.entering
    args.state.transition_bar.left -= 16
    args.state.transition_bar.right += 16
    room_to_draw = new_room
  end

  room_to_draw.draw(args, rt_name)
  args.outputs[rt_name].primitives << {x: args.state.transition_bar.left, y: 0, w: 256, h: 352, **Color::BLACK, primitive_marker: :sprite}
  args.outputs[rt_name].primitives << {x: args.state.transition_bar.right, y: 0, w: 256, h: 352, **Color::BLACK, primitive_marker: :sprite}

  args.outputs.primitives << {**args.state.room_offset, w: 512, h: 352, path: rt_name, primitive_marker: :sprite}

  if args.state.transition_bar.left == 0
    args.state.transition.exiting = false
    if args.tick_count - args.state.transition.last_tick > 15 #delay a little before starting the entering transition
      args.state.transition.entering = true
    end
  end

  if args.state.transition_bar.left == -256 && args.state.transition.entering
    args.state.transition.exiting = false
    args.state.transition.entering = false
    args.state.transition.finished = true
    args.state.transition.start_animation = false
  end

  if args.state.transition.finished
    args.state.change_worlds = false
    args.state.transition.exiting = false
    args.state.transition.entering = false
    args.state.transition.finished = false

    args.state.transition_bar = {left: -256, right: 512}
    args.state.exiting_world_start = 0

    link = args.state.transition.link
    if link
      args.state.player.current_room_coordinates = link.destination_coordinates.copy
      set_map(args, args.state.transition.link.destination_name)
    end
  end
end


def tele_room(x, y)
  args.state.player.current_room_coordinates = {x: x, y: y}
end

def player_pos(args, tile_x ,tile_y)
  args.state.player.x = tile_x * 32
  args.state.player.y = tile_y * 32
end

def process_global_input(args)
  kb = args.inputs.keyboard
  if kb.key_down.back_slash
    $gtk.notify! "DragonRuby Version #{$gtk.version}", 90
  end
  if kb.key_down.equal_sign
    args.state.player.move_speed += 1
    $gtk.notify! "Setting player move speed to #{args.state.player.move_speed}", 90
  end
  if kb.key_down.minus
    args.state.player.move_speed -= 1
    $gtk.notify! "Setting player move speed to #{args.state.player.move_speed}", 90
  end
  if kb.key_down.one
    $gtk.notify! "Setting player move speed to 5", 90
    args.state.player.move_speed = 5
  end
  
  if kb.key_down.exclamation_point
    $gtk.notify! "Toggle show room names", 90
    args.state.game.show_room_names ^= true#!args.state.game.show_room_names
  end
  if kb.key_down.at
    $gtk.notify! "Toggle show room links", 90
    args.state.game.show_room_links ^= true#!args.state.game.show_room_names
  end
  
  if kb.key_down.r # and args.inputs.keyboard.key_held.shift
    # puts "reload current map room"
    $gtk.notify! "Reloading current map room", 90
    current_map = args.state.maps[args.state.map_name]
    position = args.state.player.current_room_coordinates
    current_map.reload_room_at!(args, position.x, position.y)
  end
  
  if kb.key_down.r and kb.key_held.shift
    args.gtk.reset_next_tick
  end

  if kb.key_down.one
    $gtk.notify "Toggle Background Color", 90

    if args.state.bg_color == Color::LIGHTYELLOW
      args.state.bg_color = Color::BLACK
      args.state.text_color = Color::WHITE
    else
      args.state.bg_color = Color::LIGHTYELLOW
      args.state.text_color = Color::BLACK
    end
  end

  if kb.key_down.five
    $gtk.notify "Starting GC", 90

    GC.enable
    GC.start
  end

  if kb.key_down.percent_sign
    $gtk.notify "Disabling GC", 90
    GC.disable
  end

  if kb.key_down.eight
    $gtk.notify "Setting player position to 256, 128", 90
    args.state.player.x = 32 * 8
    args.state.player.y = 32 * 4
  end

  if kb.key_down.seven
    args.state.should_draw_collisions ^= true#!args.state.should_draw_collisions
  end

  if kb.key_down.nine
    $gtk.notify "Toggle Debug Info", 90
    args.state.show_debug_info ^= true#!args.state.show_debug_info
  end
  if kb.key_down.zero
    $gtk.notify "Toggle Debug Rects", 90
    args.state.show_debug_rects ^= true#!args.state.show_debug_rects
  end

  if kb.key_down.close_round_brace
    $gtk.notify "Toggle Tile Coordinates Display", 90
    args.state.game.show_tile_coords ^= true
  end

  if kb.key_down.i
    $gtk.notify "Toggle Controls Dialog", 90
    args.state.show_controls_window ^= true
  end

  if kb.key_down.space && (args.state.inventory_x == 0 || args.state.inventory_x == -96)
    $gtk.notify "Toggle Inventory", 90
    args.state.player.inventory.toggle_display
  end

end

def draw_hud args
  args.outputs[:hud].w = 512
  args.outputs[:hud].h = 200
  args.outputs[:hud].transient!

  output = []

  attack_key_display = args.state.hud.weapon_display.draw(args)
  attack_key_display.merge!({x: 265 + 40, y: 10, w: 55, h: 45})
  
  use_item_key_display = args.state.hud.use_item_key_display.draw(args)
  use_item_key_display.merge!({x: 265, y: 10, w: 55, h: 45})

  output << attack_key_display
  if args.state.player.has_sword
    draw_sword args
  end
  output << use_item_key_display
  
  energy_output = args.state.player.energy.draw(args).merge({x: 350, y: 10})

  output << args.state.player.money_display.draw(args).merge({x: 175, y: 45})
  output << args.state.player.bomb_display.draw(args).merge({x: 175, y: 25})
  output << args.state.player.key_display.draw(args).merge({x: 175, y: 5})
  output << energy_output


  args.state.world.minimap.draw(args)
  args.outputs[:hud].primitives << output

  args.outputs.primitives << {x: args.state.room_offset.x, y: 447, w: 512, h: 200, path: :hud, primitive_marker: :sprite }
end

#define a min method because... well DR doesn't have it implemented.
def min(first, second)
  return first if first < second
  return second
end