=begin

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

 - args.state.new_entity: Used when we want to create a new object, like a sprite or button.
   For example, if we want to create a new button, we would declare it as a new entity and
   then define its properties. (Remember, you can use state to define ANY property and it will
   be retained across frames.)

   If you have a solar system and you're creating args.state.sun and setting its image path to an
   image in the sprites folder, you would do the following:
   (See samples/99_sample_nddnug_workshop for more details.)

   args.state.sun ||= args.state.new_entity(:sun) do |s|
   s.path = 'sprites/sun.png'
   end

 - 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.

   For example, if we have a variable
   name = "Ruby"
   then the line
   puts "How are you, #{name}?"
   would print "How are you, Ruby?" to the console.
   (Remember, string interpolation only works with double quotes!)

 - Ternary operator (?): Similar to if statement; first evaluates whether a statement is
   true or false, and then executes a command depending on that result.
   For example, if we had a variable
   grade = 75
   and used the ternary operator in the command
   pass_or_fail = grade > 65 ? "pass" : "fail"
   then the value of pass_or_fail would be "pass" since grade's value was greater than 65.

 Reminders:

 - args.grid.(left|right|top|bottom): Pixel value for the boundaries of the virtual
   720 p screen (Dragon Ruby Game Toolkits's virtual resolution is always 1280x720).

 - Numeric#shift_(left|right|up|down): Shifts the Numeric in the correct direction
   by adding or subtracting.

 - 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.

 - ARRAY#intersect_rect?: Returns true or false depending on if the two rectangles intersect.

 - args.inputs.mouse.click: This property will be set if the mouse was clicked.

 - args.inputs.keyboard.key_up.KEY: The value of the properties will be set
   to the frame  that the key_up event occurred (the frame correlates
   to args.state.tick_count).

 - args.state.labels:
   The parameters for a label are
   1. the position (x, y)
   2. the text
   3. the size
   4. the alignment
   5. the color (red, green, and blue saturations)
   6. the alpha (or transparency)

 - args.state.lines:
   The parameters for a line are
   1. the starting position (x, y)
   2. the ending position (x2, y2)
   3. the color (red, green, and blue saturations)
   4. the alpha (or transparency)

 - args.state.solids (and args.state.borders):
   The parameters for a solid (or border) are
   1. the position (x, y)
   2. the width (w)
   3. the height (h)
   4. the color (r, g, b)
   5. the alpha (or transparency)

 - args.state.sprites:
   The parameters for a sprite are
   1. the position (x, y)
   2. the width (w)
   3. the height (h)
   4. the image path
   5. the angle
   6. the alpha (or transparency)

=end

# This sample app shows different objects that can be used when making games, such as labels,
# lines, sprites, solids, buttons, etc. Each demo section shows how these objects can be used.

# Also note that state.tick_count refers to the passage of time, or current frame.

class TechDemo
  attr_accessor :inputs, :state, :outputs, :grid, :args

  # Calls all methods necessary for the app to run properly.
  def tick
    labels_tech_demo
    lines_tech_demo
    solids_tech_demo
    borders_tech_demo
    sprites_tech_demo
    keyboards_tech_demo
    controller_tech_demo
    mouse_tech_demo
    point_to_rect_tech_demo
    rect_to_rect_tech_demo
    button_tech_demo
    export_game_state_demo
    window_state_demo
    render_seperators
  end

  # Shows output of different kinds of labels on the screen
  def labels_tech_demo
    outputs.labels << [grid.left.shift_right(5), grid.top.shift_down(5), "This is a label located at the top left."]
    outputs.labels << [grid.left.shift_right(5), grid.bottom.shift_up(30), "This is a label located at the bottom left."]
    outputs.labels << [ 5, 690, "Labels (x, y, text, size, align, r, g, b, a)"]
    outputs.labels << [ 5, 660, "Smaller label.",  -2]
    outputs.labels << [ 5, 630, "Small label.",    -1]
    outputs.labels << [ 5, 600, "Medium label.",    0]
    outputs.labels << [ 5, 570, "Large label.",     1]
    outputs.labels << [ 5, 540, "Larger label.",    2]
    outputs.labels << [300, 660, "Left aligned.",    0, 2]
    outputs.labels << [300, 640, "Center aligned.",  0, 1]
    outputs.labels << [300, 620, "Right aligned.",   0, 0]
    outputs.labels << [175, 595, "Red Label.",       0, 0, 255,   0,   0]
    outputs.labels << [175, 575, "Green Label.",     0, 0,   0, 255,   0]
    outputs.labels << [175, 555, "Blue Label.",      0, 0,   0,   0, 255]
    outputs.labels << [175, 535, "Faded Label.",     0, 0,   0,   0,   0, 128]
  end

  # Shows output of lines on the screen
  def lines_tech_demo
    outputs.labels << [5, 500, "Lines (x, y, x2, y2, r, g, b, a)"]
    outputs.lines  << [5, 450, 100, 450]
    outputs.lines  << [5, 430, 300, 430]
    outputs.lines  << [5, 410, 300, 410, state.tick_count % 255, 0, 0, 255] # red saturation changes
    outputs.lines  << [5, 390 - state.tick_count % 25, 300, 390, 0, 0, 0, 255] # y position changes
    outputs.lines  << [5 + state.tick_count % 200, 360, 300, 360, 0, 0, 0, 255] # x position changes
  end

  # Shows output of different kinds of solids on the screen
  def solids_tech_demo
    outputs.labels << [  5, 350, "Solids (x, y, w, h, r, g, b, a)"]
    outputs.solids << [ 10, 270, 50, 50]
    outputs.solids << [ 70, 270, 50, 50, 0, 0, 0]
    outputs.solids << [130, 270, 50, 50, 255, 0, 0]
    outputs.solids << [190, 270, 50, 50, 255, 0, 0, 128]
    outputs.solids << [250, 270, 50, 50, 0, 0, 0, 128 + state.tick_count % 128] # transparency changes
  end

  # Shows output of different kinds of borders on the screen
  # The parameters for a border are the same as the parameters for a solid
  def borders_tech_demo
    outputs.labels <<  [  5, 260, "Borders (x, y, w, h, r, g, b, a)"]
    outputs.borders << [ 10, 180, 50, 50]
    outputs.borders << [ 70, 180, 50, 50, 0, 0, 0]
    outputs.borders << [130, 180, 50, 50, 255, 0, 0]
    outputs.borders << [190, 180, 50, 50, 255, 0, 0, 128]
    outputs.borders << [250, 180, 50, 50, 0, 0, 0, 128 + state.tick_count % 128] # transparency changes
  end

  # Shows output of different kinds of sprites on the screen
  def sprites_tech_demo
    outputs.labels <<  [   5, 170, "Sprites (x, y, w, h, path, angle, a)"]
    outputs.sprites << [  10, 40, 128, 101, 'dragonruby.png']
    outputs.sprites << [ 150, 40, 128, 101, 'dragonruby.png', state.tick_count % 360] # angle changes
    outputs.sprites << [ 300, 40, 128, 101, 'dragonruby.png', 0, state.tick_count % 255] # transparency changes
  end

  # Holds size, alignment, color (black), and alpha (transparency) parameters
  # Using small_font as a parameter accounts for all remaining parameters
  # so they don't have to be repeatedly typed
  def small_font
    [-2, 0, 0, 0, 0, 255]
  end

  # Sets position of each row
  # Converts given row value to pixels that DragonRuby understands
  def row_to_px row_number

    # Row 0 starts 5 units below the top of the grid.
    # Each row afterward is 20 units lower.
    grid.top.shift_down(5).shift_down(20 * row_number)
  end

  # Uses labels to output current game time (passage of time), and whether or not "h" was pressed
  # If "h" is pressed, the frame is output when the key_up event occurred
  def keyboards_tech_demo
    outputs.labels << [460, row_to_px(0), "Current game time: #{state.tick_count}", small_font]
    outputs.labels << [460, row_to_px(2), "Keyboard input: inputs.keyboard.key_up.h", small_font]
    outputs.labels << [460, row_to_px(3), "Press \"h\" on the keyboard.", small_font]

    if inputs.keyboard.key_up.h # if "h" key_up event occurs
      state.h_pressed_at = state.tick_count # frame it occurred is stored
    end

    # h_pressed_at is initially set to false, and changes once the user presses the "h" key.
    state.h_pressed_at ||= false

    if state.h_pressed_at # if h is pressed (pressed_at has a frame number and is no longer false)
      outputs.labels << [460, row_to_px(4), "\"h\" was pressed at time: #{state.h_pressed_at}", small_font]
    else # otherwise, label says "h" was never pressed
      outputs.labels << [460, row_to_px(4), "\"h\" has never been pressed.", small_font]
    end

    # border around keyboard input demo section
    outputs.borders << [455, row_to_px(5), 360, row_to_px(2).shift_up(5) - row_to_px(5)]
  end

  # Sets definition for a small label
  # Makes it easier to position labels in respect to the position of other labels
  def small_label x, row, message
    [x, row_to_px(row), message, small_font]
  end

  # Uses small labels to show whether the "a" button on the controller is down, held, or up.
  # y value of each small label is set by calling the row_to_px method
  def controller_tech_demo
    x = 460
    outputs.labels << small_label(x, 6, "Controller one input: inputs.controller_one")
    outputs.labels << small_label(x, 7, "Current state of the \"a\" button.")
    outputs.labels << small_label(x, 8, "Check console window for more info.")

    if inputs.controller_one.key_down.a # if "a" is in "down" state
      outputs.labels << small_label(x, 9, "\"a\" button down: #{inputs.controller_one.key_down.a}")
      puts "\"a\" button down at #{inputs.controller_one.key_down.a}" # prints frame the event occurred
    elsif inputs.controller_one.key_held.a # if "a" is held down
      outputs.labels << small_label(x, 9, "\"a\" button held: #{inputs.controller_one.key_held.a}")
    elsif inputs.controller_one.key_up.a # if "a" is in up state
      outputs.labels << small_label(x, 9, "\"a\" button up: #{inputs.controller_one.key_up.a}")
      puts "\"a\" key up at #{inputs.controller_one.key_up.a}"
    else # if no event has occurred
      outputs.labels << small_label(x, 9, "\"a\" button state is nil.")
    end

    # border around controller input demo section
    outputs.borders << [455, row_to_px(10), 360, row_to_px(6).shift_up(5) - row_to_px(10)]
  end

  # Outputs when the mouse was clicked, as well as the coordinates on the screen
  # of where the click occurred
  def mouse_tech_demo
    x = 460

    outputs.labels << small_label(x, 11, "Mouse input: inputs.mouse")

    if inputs.mouse.click # if click has a value and is not nil
      state.last_mouse_click = inputs.mouse.click # coordinates of click are stored
    end

    if state.last_mouse_click # if mouse is clicked (has coordinates as value)
      # outputs the time (frame) the click occurred, as well as how many frames have passed since the event
      outputs.labels << small_label(x, 12, "Mouse click happened at: #{state.last_mouse_click.created_at}, #{state.last_mouse_click.created_at_elapsed}")
      # outputs coordinates of click
      outputs.labels << small_label(x, 13, "Mouse click location: #{state.last_mouse_click.point.x}, #{state.last_mouse_click.point.y}")
    else # otherwise if the mouse has not been clicked
      outputs.labels << small_label(x, 12, "Mouse click has not occurred yet.")
      outputs.labels << small_label(x, 13, "Please click mouse.")
    end
  end

  # Outputs whether a mouse click occurred inside or outside of a box
  def point_to_rect_tech_demo
    x = 460

    outputs.labels << small_label(x, 15, "Click inside the blue box maybe ---->")

    box = [765, 370, 50, 50, 0, 0, 170] # blue box
    outputs.borders << box

    if state.last_mouse_click # if the mouse was clicked
      if state.last_mouse_click.point.inside_rect? box # if mouse clicked inside box
        outputs.labels << small_label(x, 16, "Mouse click happened inside the box.")
      else # otherwise, if mouse was clicked outside the box
        outputs.labels << small_label(x, 16, "Mouse click happened outside the box.")
      end
    else # otherwise, if was not clicked at all
      outputs.labels << small_label(x, 16, "Mouse click has not occurred yet.") # output if the mouse was not clicked
    end

    # border around mouse input demo section
    outputs.borders << [455, row_to_px(14), 360, row_to_px(11).shift_up(5) - row_to_px(14)]
  end

  # Outputs a red box onto the screen. A mouse click from the user inside of the red box will output
  # a smaller box. If two small boxes are inside of the red box, it will be determined whether or not
  # they intersect.
  def rect_to_rect_tech_demo
    x = 460

    outputs.labels << small_label(x, 17.5, "Click inside the red box below.") # label with instructions
    red_box = [460, 250, 355, 90, 170, 0, 0] # definition of the red box
    outputs.borders << red_box # output as a border (not filled in)

    # If the mouse is clicked inside the red box, two collision boxes are created.
    if inputs.mouse.click
      if inputs.mouse.click.point.inside_rect? red_box
        if !state.box_collision_one # if the collision_one box does not yet have a definition
          # Subtracts 25 from the x and y positions of the click point in order to make the click point the center of the box.
          # You can try deleting the subtraction to see how it impacts the box placement.
          state.box_collision_one = [inputs.mouse.click.point.x - 25, inputs.mouse.click.point.y - 25, 50, 50, 180, 0,   0, 180]  # sets definition
        elsif !state.box_collision_two # if collision_two does not yet have a definition
          state.box_collision_two = [inputs.mouse.click.point.x - 25, inputs.mouse.click.point.y - 25, 50, 50,   0, 0, 180, 180] # sets definition
        else
          state.box_collision_one = nil # both boxes are empty
          state.box_collision_two = nil
        end
      end
    end

    # If collision boxes exist, they are output onto screen inside the red box as solids
    if state.box_collision_one
      outputs.solids << state.box_collision_one
    end

    if state.box_collision_two
      outputs.solids << state.box_collision_two
    end

    # Outputs whether or not the two collision boxes intersect.
    if state.box_collision_one && state.box_collision_two # if both collision_boxes are defined (and not nil or empty)
      if state.box_collision_one.intersect_rect? state.box_collision_two # if the two boxes intersect
        outputs.labels << small_label(x, 23.5, 'The boxes intersect.')
      else # otherwise, if the two boxes do not intersect
        outputs.labels << small_label(x, 23.5, 'The boxes do not intersect.')
      end
    else
      outputs.labels << small_label(x, 23.5, '--') # if the two boxes are not defined (are nil or empty), this label is output
    end
  end

  # Creates a button and outputs it onto the screen using labels and borders.
  # If the button is clicked, the color changes to make it look faded.
  def button_tech_demo
    x, y, w, h = 460, 160, 300, 50
    state.button        ||= state.new_entity(:button_with_fade)

    # Adds w.half to x and h.half + 10 to y in order to display the text inside the button's borders.
    state.button.label  ||= [x + w.half, y + h.half + 10, "click me and watch me fade", 0, 1]
    state.button.border ||= [x, y, w, h]

    if inputs.mouse.click && inputs.mouse.click.point.inside_rect?(state.button.border) # if mouse is clicked, and clicked inside button's border
      state.button.clicked_at = inputs.mouse.click.created_at # stores the time the click occurred
    end

    outputs.labels << state.button.label
    outputs.borders << state.button.border

    if state.button.clicked_at # if button was clicked (variable has a value and is not nil)

      # The appearance of the button changes for 0.25 seconds after the time the button is clicked at.
      # The color changes (rgb is set to 0, 180, 80) and the transparency gradually changes.
      # Change 0.25 to 1.25 and notice that the transparency takes longer to return to normal.
      outputs.solids << [x, y, w, h, 0, 180, 80, 255 * state.button.clicked_at.ease(0.25.seconds, :flip)]
    end
  end

  # Creates a new button by declaring it as a new entity, and sets values.
  def new_button_prefab x, y, message
    w, h = 300, 50
    button        = state.new_entity(:button_with_fade)
    button.label  = [x + w.half, y + h.half + 10, message, 0, 1] # '+ 10' keeps label's text within button's borders
    button.border = [x, y, w, h] # sets border definition
    button
  end

  # If the mouse has been clicked and the click's location is inside of the button's border, that means
  # that the button has been clicked. This method returns a boolean value.
  def button_clicked? button
    inputs.mouse.click && inputs.mouse.click.point.inside_rect?(button.border)
  end

  # Determines if button was clicked, and changes its appearance if it is clicked
  def tick_button_prefab button
    outputs.labels << button.label # outputs button's label and border
    outputs.borders << button.border

    if button_clicked? button # if button is clicked
      button.clicked_at = inputs.mouse.click.created_at # stores the time that the button was clicked
    end

    if button.clicked_at # if clicked_at has a frame value and is not nil
      # button is output; color changes and transparency changes for 0.25 seconds after click occurs
      outputs.solids << [button.border.x, button.border.y, button.border.w, button.border.h,
                         0, 180, 80, 255 * button.clicked_at.ease(0.25.seconds, :flip)] # transparency changes for 0.25 seconds
    end
  end

  # Exports the app's game state if the export button is clicked.
  def export_game_state_demo
    state.export_game_state_button ||= new_button_prefab(460, 100, "click to export app state")
    tick_button_prefab(state.export_game_state_button) # calls method to output button
    if button_clicked? state.export_game_state_button # if the export button is clicked
      args.gtk.export! "Exported from clicking the export button in the tech demo." # the export occurs
    end
  end

  # The mouse and keyboard focus are set to "yes" when the Dragonruby window is the active window.
  def window_state_demo
    m = $gtk.args.inputs.mouse.has_focus ? 'Y' : 'N' # ternary operator (similar to if statement)
    k = $gtk.args.inputs.keyboard.has_focus ? 'Y' : 'N'
    outputs.labels << [460, 20, "mouse focus: #{m}   keyboard focus: #{k}", small_font]
  end

  #Sets values for the horizontal separator (divides demo sections)
  def horizontal_seperator y, x, x2
    [x, y, x2, y, 150, 150, 150]
  end

  #Sets the values for the vertical separator (divides demo sections)
  def vertical_seperator x, y, y2
    [x, y, x, y2, 150, 150, 150]
  end

  # Outputs vertical and horizontal separators onto the screen to separate each demo section.
  def render_seperators
    outputs.lines << horizontal_seperator(505, grid.left, 445)
    outputs.lines << horizontal_seperator(353, grid.left, 445)
    outputs.lines << horizontal_seperator(264, grid.left, 445)
    outputs.lines << horizontal_seperator(174, grid.left, 445)

    outputs.lines << vertical_seperator(445, grid.top, grid.bottom)

    outputs.lines << horizontal_seperator(690, 445, 820)
    outputs.lines << horizontal_seperator(426, 445, 820)

    outputs.lines << vertical_seperator(820, grid.top, grid.bottom)
  end
end

$tech_demo = TechDemo.new

def tick args
  $tech_demo.inputs = args.inputs
  $tech_demo.state = args.state
  $tech_demo.grid = args.grid
  $tech_demo.args = args
  $tech_demo.outputs = args.render_target(:mini_map)
  $tech_demo.tick
  args.outputs.labels  << [830, 715, "Render target:", [-2, 0, 0, 0, 0, 255]]
  args.outputs.sprites << [0, 0, 1280, 720, :mini_map]
  args.outputs.sprites << [830, 300, 675, 379, :mini_map]
  tick_instructions args, "Sample app shows all the rendering apis available."
end

def tick_instructions args, text, y = 715
  return if args.state.key_event_occurred
  if args.inputs.mouse.click ||
     args.inputs.keyboard.directional_vector ||
     args.inputs.keyboard.key_down.enter ||
     args.inputs.keyboard.key_down.escape
    args.state.key_event_occurred = true
  end

  args.outputs.debug << [0, y - 50, 1280, 60].solid
  args.outputs.debug << [640, y, text, 1, 1, 255, 255, 255].label
  args.outputs.debug << [640, y - 25, "(click to dismiss instructions)" , -2, 1, 255, 255, 255].label
end
