=begin

 APIs listing that haven't been encountered in previous sample apps:

 - Symbol (:): Ruby object with a name and an internal ID. Symbols are useful
   because with a given symbol name, you can refer to the same object throughout
   a Ruby program.

   In this sample app, we're using symbols for our buttons. We have buttons that
   light fires, save, load, etc. Each of these buttons has a distinct symbol like
   :light_fire, :save_game, :load_game, etc.

 - to_sym: Returns the symbol corresponding to the given string; creates the symbol
   if it does not already exist.
   For example,
   'car'.to_sym
   would return the symbol :car.

 - last: Returns the last element of an array.

 Reminders:

 - num1.lesser(num2): finds the lower value of the given options.
   For example, in the statement
   a = 4.lesser(3)
   3 has a lower value than 4, which means that the value of a would be set to 3,
   but if the statement had been
   a = 4.lesser(5)
   4 has a lower value than 5, which means that the value of a would be set to 4.

 - num1.fdiv(num2): returns the float division (will have a decimal) of the two given numbers.
   For example, 5.fdiv(2) = 2.5 and 5.fdiv(5) = 1.0

 - String interpolation: uses #{} syntax; everything between the #{ and the } is evaluated
   as Ruby code, and the placeholder is replaced with its corresponding value or result.

 - args.outputs.labels: An array. Values generate a label.
   Parameters are [X, Y, TEXT, SIZE, ALIGN, RED, GREEN, BLUE, ALPHA, FONT STYLE]
   For more information, go to mygame/documentation/02-labels.md.

 - ARRAY#inside_rect?: An array with at least two values is considered a point. An array
   with at least four values is considered a rect. The inside_rect? function returns true
   or false depending on if the point is inside the rect.

=end

# This code allows users to perform different tasks, such as saving and loading the game.
# Users also have options to reset the game and light a fire.

class TextedBasedGame

  # Contains methods needed for game to run properly.
  # Increments tick count by 1 each time it runs (60 times in a single second)
  def tick
    default
    show_intro
    state.engine_tick_count += 1
    tick_fire
  end

  # Sets default values.
  # The ||= ensures that a variable's value is only set to the value following the = sign
  # if the value has not already been set before. Intialization happens only in the first frame.
  def default
    state.engine_tick_count ||= 0
    state.active_module     ||= :room
    state.fire_progress     ||= 0
    state.fire_ready_in     ||= 10
    state.previous_fire     ||= :dead
    state.fire              ||= :dead
  end

  def show_intro
    return unless state.engine_tick_count == 0 # return unless the game just started
    set_story_line "awake." # calls set_story_line method, sets to "awake"
  end

  # Sets story line.
  def set_story_line story_line
    state.story_line    = story_line # story line set to value of parameter
    state.active_module = :alert # active module set to alert
  end

  # Clears story line.
  def clear_storyline
    state.active_module = :none # active module set to none
    state.story_line = nil # story line is cleared, set to nil (or empty)
  end

  # Determines fire progress (how close the fire is to being ready to light).
  def tick_fire
    return if state.active_module == :alert # return if active module is alert
    state.fire_progress += 1 # increment fire progress
    # fire_ready_in is 10. The fire_progress is either the current value or 10, whichever has a lower value.
    state.fire_progress = state.fire_progress.lesser(state.fire_ready_in)
  end

  # Sets the value of fire (whether it is dead or roaring), and the story line
  def light_fire
    return unless fire_ready? # returns unless the fire is ready to be lit
    state.fire = :roaring # fire is lit, set to roaring
    state.fire_progress = 0 # the fire progress returns to 0, since the fire has been lit
    if state.fire != state.previous_fire
      set_story_line "the fire is #{state.fire}." # the story line is set using string interpolation
      state.previous_fire = state.fire
    end
  end

  # Checks if the fire is ready to be lit. Returns a boolean value.
  def fire_ready?
    # If fire_progress (value between 0 and 10) is equal to fire_ready_in (value of 10),
    # the fire is ready to be lit.
    state.fire_progress == state.fire_ready_in
  end

  # Divides the value of the fire_progress variable by 10 to determine how close the user is to
  # being able to light a fire.
  def light_fire_progress
    state.fire_progress.fdiv(10) # float division
  end

  # Defines fire as the state.fire variable.
  def fire
    state.fire
  end

  # Sets the title of the room.
  def room_title
    return "a room that is dark" if state.fire == :dead # room is dark if the fire is dead
    return "a room that is lit" # room is lit if the fire is not dead
  end

  # Sets the active_module to room.
  def go_to_room
    state.active_module = :room
  end

  # Defines active_module as the state.active_module variable.
  def active_module
    state.active_module
  end

  # Defines story_line as the state.story_line variable.
  def story_line
    state.story_line
  end

  # Update every 60 frames (or every second)
  def should_tick?
    Kernel.tick_count.zmod?(60)
  end

  # Sets the value of the game state provider.
  def initialize game_state_provider
    @game_state_provider = game_state_provider
  end

  # Defines the game state.
  # Any variable prefixed with an @ symbol is an instance variable.
  def state
    @game_state_provider.state
  end

  # Saves the state of the game in a text file called game_state.txt.
  def save
    GTK.serialize_state('game_state.txt', state)
  end

  # Loads the game state from the game_state.txt text file.
  # If the load is unsuccessful, the user is informed since the story line indicates the failure.
  def load
    parsed_state = GTK.deserialize_state('game_state.txt')
    if !parsed_state
      set_story_line "no game to load. press save first."
    else
      GTK.args.state = parsed_state
    end
  end

  # Resets the game.
  def reset
    GTK.reset
  end
end

class TextedBasedGamePresenter
  attr_accessor :state, :outputs, :inputs

  # Creates empty collection called highlights.
  # Calls methods necessary to run the game.
  def tick
    state.layout.highlights ||= []
    game.tick if game.should_tick?
    render
    process_input
  end

  # Outputs a label of the tick count (passage of time) and calls all render methods.
  def render
    outputs.labels << [10, 30, Kernel.tick_count]
    render_alert
    render_room
    render_highlights
  end

  # Outputs a label onto the screen that shows the story line, and also outputs a "close" button.
  def render_alert
    return unless game.active_module == :alert

    outputs.labels << [640, 480, game.story_line, 5, 1]  # outputs story line label
    outputs.primitives << button(:alert_dismiss, 490, 380, "close")  # positions "close" button under story line
  end

  def render_room
    return unless game.active_module == :room
    outputs.labels << [640, 700, game.room_title, 4, 1] # outputs room title label at top of screen

    # The parameters for these outputs are (symbol, x, y, text, value/percentage) and each has a y value
    # that positions it 60 pixels lower than the previous output.

    # outputs the light_fire_progress bar, uses light_fire_progress for its percentage (which changes bar's appearance)
    outputs.primitives << progress_bar(:light_fire, 490, 600, "light fire", game.light_fire_progress)
    outputs.primitives << button(       :save_game, 490, 540, "save") # outputs save button
    outputs.primitives << button(       :load_game, 490, 480, "load") # outputs load button
    outputs.primitives << button(      :reset_game, 490, 420, "reset") # outputs reset button
    outputs.labels << [640, 30, "the fire is #{game.fire}", 0, 1] # outputs fire label at bottom of screen
  end

  # Outputs a collection of highlights using an array to set their values, and also rejects certain values from the collection.
  def render_highlights
    state.layout.highlights.each do |h| # for each highlight in the collection
        h.lifetime -= 1 # decrease the value of its lifetime
      end

      outputs.solids << state.layout.highlights.map do |h| # outputs highlights collection
        [h.x, h.y, h.w, h.h, h.color, 255 * h.lifetime / h.max_lifetime] # sets definition for each highlight
        # transparency changes; divide lifetime by max_lifetime, multiply result by 255
      end

      # reject highlights from collection that have no remaining lifetime
      state.layout.highlights = state.layout.highlights.reject { |h| h.lifetime <= 0 }
  end

  # Checks whether or not a button was clicked.
  # Returns a boolean value.
  def process_input
    button = button_clicked? # calls button_clicked? method
  end

  # Returns a boolean value.
  # Finds the button that was clicked from the button list and determines what method to call.
  # Adds a highlight to the highlights collection.
  def button_clicked?
    return nil unless click_pos # return nil unless click_pos holds coordinates of mouse click
      button = @button_list.find do |k, v| # goes through button_list to find button clicked
        click_pos.inside_rect? v[:primitives].last.rect # was the mouse clicked inside the rect of button?
      end
      return unless button # return unless a button was clicked
      method_to_call = "#{button[0]}_clicked".to_sym # sets method_to_call to symbol (like :save_game or :load_game)
      if self.respond_to? method_to_call # returns true if self responds to the given method (method actually exists)
        border = button[1][:primitives].last # sets border definition using value of last key in button list hash

        # declares each highlight as a new entity, sets properties
        state.layout.highlights << state.new_entity(:highlight) do |h|
            h.x = border.x
            h.y = border.y
            h.w = border.w
            h.h = border.h
            h.max_lifetime = 10
            h.lifetime = h.max_lifetime
            h.color = [120, 120, 180] # sets color to shade of purple
          end

          self.send method_to_call # invoke method identified by symbol
        else # otherwise, if self doesn't respond to given method
          border = button[1][:primitives].last # sets border definition using value of last key in hash

          # declares each highlight as a new entity, sets properties
          state.layout.highlights << state.new_entity(:highlight) do |h|
            h.x = border.x
            h.y = border.y
            h.w = border.w
            h.h = border.h
            h.max_lifetime = 4 # different max_lifetime than the one set if respond_to? had been true
            h.lifetime = h.max_lifetime
            h.color = [120, 80, 80] # sets color to dark color
          end

          # instructions for users on how to add the missing method_to_call to the code
          puts "It looks like #{method_to_call} doesn't exists on TextedBasedGamePresenter. Please add this method:"
          puts "Just copy the code below and put it in the #{TextedBasedGamePresenter} class definition."
          puts ""
          puts "```"
          puts "class TextedBasedGamePresenter <--- find this class and put the method below in it"
          puts ""
          puts "  def #{method_to_call}"
          puts "    puts 'Yay that worked!'"
          puts "  end"
          puts ""
          puts "end <-- make sure to put the #{method_to_call} method in between the `class` word and the final `end` statement."
          puts "```"
          puts ""
      end
  end

  # Returns the position of the mouse when it is clicked.
  def click_pos
    return nil unless inputs.mouse.click # returns nil unless the mouse was clicked
    return inputs.mouse.click.point # returns location of mouse click (coordinates)
  end

  # Creates buttons for the button_list and sets their values using a hash (uses symbols as keys)
  def button id, x, y, text
    @button_list[id] ||= { # assigns values to hash keys
      id: id,
      text: text,
      primitives: [
        [x + 10, y + 30, text, 2, 0].label, # positions label inside border
        [x, y, 300, 50].border,             # sets definition of border
      ]
    }

    @button_list[id][:primitives] # returns label and border for buttons
  end

  # Creates a progress bar (used for lighting the fire) and sets its values.
  def progress_bar id, x, y, text, percentage
    @button_list[id] = { # assigns values to hash keys
      id: id,
      text: text,
      primitives: [
        [x, y, 300, 50, 100, 100, 100].solid, # sets definition for solid (which fills the bar with gray)
        [x + 10, y + 30, text, 2, 0].label, # sets definition for label, positions inside border
        [x, y, 300, 50].border, # sets definition of border
      ]
    }

    # Fills progress bar based on percentage. If the fire was ready to be lit (100%) and we multiplied by
    # 100, only 1/3 of the bar would only be filled in. 200 would cause only 2/3 to be filled in.
    @button_list[id][:primitives][0][2] = 300 * percentage
    @button_list[id][:primitives]
  end

  # Defines the game.
  def game
    @game
  end

  # Initalizes the game and creates an empty list of buttons.
  def initialize
    @game = TextedBasedGame.new self
    @button_list ||= {}
  end

  # Clears the storyline and takes the user to the room.
  def alert_dismiss_clicked
    game.clear_storyline
    game.go_to_room
  end

  # Lights the fire when the user clicks the "light fire" option.
  def light_fire_clicked
    game.light_fire
  end

  # Saves the game when the user clicks the "save" option.
  def save_game_clicked
    game.save
  end

  # Resets the game when the user clicks the "reset" option.
  def reset_game_clicked
    game.reset
  end

  # Loads the game when the user clicks the "load" option.
  def load_game_clicked
    game.load
  end
end

$text_based_rpg = TextedBasedGamePresenter.new

def tick args
  $text_based_rpg.state = args.state
  $text_based_rpg.outputs = args.outputs
  $text_based_rpg.inputs = args.inputs
  $text_based_rpg.tick
end
