# these are the properties that you can sent on args.audio
def spawn_new_sound args, name, path
  # Spawn randomly in an area that won't be covered by UI.
  screenx = (rand * 600.0) + 200.0
  screeny = (rand * 400.0) + 100.0

  id = new_sound_id! args
  # you can hang anything on the audio hashes you want, so we store the
  #  actual screen position in here for convenience.
  args.audio[id] = {
    name: name,
    input: path,
    screenx: screenx,
    screeny: screeny,
    x: ((screenx / 1279.0) * 2.0) - 1.0,  # scale to -1.0 - 1.0 range
    y: ((screeny / 719.0) * 2.0) - 1.0,   # scale to -1.0 - 1.0 range
    z: 0.0,
    gain: 1.0,
    pitch: 1.0,
    looping: true,
    paused: false
  }

  args.state.selected = id
end

# these are values you can change on the ~args.audio~ data structure
def input_panel args
  return unless args.state.panel
  return if args.state.dragging

  audio_entry = args.audio[args.state.selected]
  results = args.state.panel

  if args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.pitch_slider_rect.rect)
    audio_entry.pitch = 2.0 * ((args.inputs.mouse.x - results.pitch_slider_rect.x).to_f / (results.pitch_slider_rect.w - 1.0))
  elsif args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.playtime_slider_rect.rect)
    audio_entry.playtime = audio_entry.length_ * ((args.inputs.mouse.x - results.playtime_slider_rect.x).to_f / (results.playtime_slider_rect.w - 1.0))
  elsif args.state.mouse_state == :held && args.inputs.mouse.position.inside_rect?(results.gain_slider_rect.rect)
    audio_entry.gain = (args.inputs.mouse.x - results.gain_slider_rect.x).to_f / (results.gain_slider_rect.w - 1.0)
  elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.looping_checkbox_rect.rect)
    audio_entry.looping = !audio_entry.looping
  elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.paused_checkbox_rect.rect)
    audio_entry.paused = !audio_entry.paused
  elsif args.inputs.mouse.click && args.inputs.mouse.position.inside_rect?(results.delete_button_rect.rect)
    args.audio.delete args.state.selected
  end
end

def render_sources args
  args.outputs.primitives << args.audio.keys.map do |k|
    s = args.audio[k]

    isselected = (k == args.state.selected)

    color = isselected ? [ 0, 255, 0, 255 ] : [ 0, 0, 255, 255 ]
    [
      [s.screenx, s.screeny, args.state.boxsize, args.state.boxsize, *color].solid,

      {
        x: s.screenx + args.state.boxsize.half,
        y: s.screeny,
        text: s.name,
        r: 255,
        g: 255,
        b: 255,
        alignment_enum: 1
      }.label!
    ]
  end
end

def playtime_str t
  minutes = (t / 60.0).floor
  seconds = t - (minutes * 60.0).to_f
  return minutes.to_s + ':' + seconds.floor.to_s + ((seconds - seconds.floor).to_s + "000")[1..3]
end

def label_with_drop_shadow x, y, text
  [
    { x: x + 1, y: y + 1, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r:   0, g:   0, b:   0 }.label!,
    { x: x + 2, y: y + 0, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r:   0, g:   0, b:   0 }.label!,
    { x: x + 0, y: y + 1, text: text, vertical_alignment_enum: 1, alignment_enum: 1, r: 200, g: 200, b: 200 }.label!
  ]
end

def check_box opts = {}
  checkbox_template = opts.args.layout.rect(w: 0.5, h: 0.5, col: 2)
  final_rect = checkbox_template.center_inside_rect_y(opts.args.layout.rect(row: opts.row, col: opts.col))
  color = { r:   0, g:   0, b:   0 }
  color = { r: 255, g: 255, b: 255 } if opts.checked

  {
    rect: final_rect,
    primitives: [
      (final_rect.to_solid color)
    ]
  }
end

def progress_bar opts = {}
  outer_rect  = opts.args.layout.rect(row: opts.row, col: opts.col, w: 5, h: 1)
  color = opts.percentage * 255
  baseline_progress_bar = opts.args
                              .layout
                              .rect(w: 5, h: 0.5)

  final_rect = baseline_progress_bar.center_inside_rect(outer_rect)
  center = final_rect.rect_center_point

  {
    rect: final_rect,
    primitives: [
      final_rect.merge(r: color, g: color, b: color, a: 128).solid!,
      label_with_drop_shadow(center.x, center.y, opts.text)
    ]
  }
end

def panel_primitives args, audio_entry
  results = { primitives: [] }

  return results unless audio_entry

  # this uses DRGTK's layout apis to layout the controls
  # imagine the screen is split into equal cells (24 cells across, 12 cells up and down)
  # args.layout.rect returns a hash which we merge values with to create primitives
  # using args.layout.rect removes the need for pixel pushing

  # args.outputs.debug << args.layout.debug_primitives(r: 255, g: 255, b: 255)

  white_color = { r: 255, g: 255, b: 255 }
  label_style = white_color.merge(vertical_alignment_enum: 1)

  # panel background
  results.primitives << args.layout.rect(row: 0, col: 0, w: 7, h: 6, include_col_gutter: true, include_row_gutter: true)
                                   .border!(r: 255, g: 255, b: 255)

  # title
  results.primitives << args.layout.point(row: 0, col: 3.5, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text:           "Source #{args.state.selected} (#{args.audio[args.state.selected].name})",
                                          size_enum:      3,
                                          alignment_enum: 1)

  # seperator line
  results.primitives << args.layout.rect(row: 1, col: 0, w: 7, h: 0)
                                   .line!(white_color)

  # screen location
  results.primitives << args.layout.point(row: 1.0, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "screen:")

  results.primitives << args.layout.point(row: 1.0, col: 2, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "(#{audio_entry.screenx.to_i}, #{audio_entry.screeny.to_i})")

  # position
  results.primitives << args.layout.point(row: 1.5, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "position:")

  results.primitives << args.layout.point(row: 1.5, col: 2, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "(#{audio_entry[:x].round(5).to_s[0..6]}, #{audio_entry[:y].round(5).to_s[0..6]})")

  results.primitives << args.layout.point(row: 2.0, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "pitch:")

  results.pitch_slider_rect = progress_bar(row: 2.0, col: 2,
                                           percentage: audio_entry.pitch / 2.0,
                                           text: "#{audio_entry.pitch.to_sf}",
                                           args: args)

  results.primitives << results.pitch_slider_rect.primitives

  results.primitives << args.layout.point(row: 2.5, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "playtime:")

  results.playtime_slider_rect = progress_bar(args: args,
                                              row:  2.5,
                                              col:  2,
                                              percentage: audio_entry.playtime / audio_entry.length_,
                                              text: "#{playtime_str(audio_entry.playtime)} / #{playtime_str(audio_entry.length_)}")

  results.primitives << results.playtime_slider_rect.primitives

  results.primitives << args.layout.point(row: 3.0, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "gain:")

  results.gain_slider_rect = progress_bar(args: args,
                                          row:  3.0,
                                          col:  2,
                                          percentage: audio_entry.gain,
                                          text: "#{audio_entry.gain.to_sf}")

  results.primitives << results.gain_slider_rect.primitives


  results.primitives << args.layout.point(row: 3.5, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "looping:")

  checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2)

  results.looping_checkbox_rect = check_box(args: args, row: 3.5, col: 2, checked: audio_entry.looping)
  results.primitives << results.looping_checkbox_rect.primitives

  results.primitives << args.layout.point(row: 4.0, col: 0, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "paused:")

  checkbox_template = args.layout.rect(w: 0.5, h: 0.5, col: 2)

  results.paused_checkbox_rect = check_box(args: args, row: 4.0, col: 2, checked: !audio_entry.paused)
  results.primitives << results.paused_checkbox_rect.primitives

  results.delete_button_rect = { rect: args.layout.rect(row: 5, col: 0, w: 7, h: 1) }

  results.primitives << results.delete_button_rect.to_solid(r: 180)

  results.primitives << args.layout.point(row: 5, col: 3.5, row_anchor: 0.5)
                                   .merge(label_style)
                                   .merge(text: "DELETE", alignment_enum: 1)

  return results
end

def render_panel args
  args.state.panel = nil
  audio_entry = args.audio[args.state.selected]
  return unless audio_entry

  mouse_down = (args.state.mouse_held >= 0)
  args.state.panel = panel_primitives args, audio_entry
  args.outputs.primitives << args.state.panel.primitives
end

def new_sound_id! args
  args.state.sound_id ||= 0
  args.state.sound_id  += 1
  args.state.sound_id
end

def render_launcher args
  args.outputs.primitives << args.state.spawn_sound_buttons.map(&:primitives)
end

def render_ui args
  render_launcher args
  render_panel args
end

def tick args
  defaults args
  render args
  input args
end

def input args
  if !args.audio[args.state.selected]
    args.state.selected = nil
    args.state.dragging = nil
  end

  # spawn button and node interaction
  if args.inputs.mouse.click
    spawn_sound_button = args.state.spawn_sound_buttons.find { |b| args.inputs.mouse.inside_rect? b.rect }

    audio_click_key, audio_click_value = args.audio.find do |k, v|
      args.inputs.mouse.inside_rect? [v.screenx, v.screeny, args.state.boxsize, args.state.boxsize]
    end

    if spawn_sound_button
      args.state.selected = nil
      spawn_new_sound args, spawn_sound_button.name, spawn_sound_button.path
    elsif audio_click_key
      args.state.selected = audio_click_key
    end
  end

  if args.state.mouse_state == :held && args.state.selected
    v = args.audio[args.state.selected]
    if args.inputs.mouse.inside_rect? [v.screenx, v.screeny, args.state.boxsize, args.state.boxsize]
      args.state.dragging = args.state.selected
    end

    if args.state.dragging
      s = args.audio[args.state.selected]
      # you can hang anything on the audio hashes you want, so we store the
      #  actual screen position so it doesn't scale weirdly vs your mouse.
      s.screenx = args.inputs.mouse.x - (args.state.boxsize / 2)
      s.screeny = args.inputs.mouse.y - (args.state.boxsize / 2)

      s.screeny = 50 if s.screeny < 50
      s.screeny = (719 - args.state.boxsize) if s.screeny > (719 - args.state.boxsize)
      s.screenx = 0 if s.screenx < 0
      s.screenx = (1279 - args.state.boxsize) if s.screenx > (1279 - args.state.boxsize)

      s.x = ((s.screenx / 1279.0) * 2.0) - 1.0  # scale to -1.0 - 1.0 range
      s.y = ((s.screeny / 719.0) * 2.0) - 1.0   # scale to -1.0 - 1.0 range
    end
  elsif args.state.mouse_state == :released
    args.state.dragging = nil
  end

  input_panel args
end

def defaults args
  args.state.mouse_state      ||= :released
  args.state.dragging_source  ||= false
  args.state.selected         ||= 0
  args.state.next_sound_index ||= 0
  args.state.boxsize          ||= 30
  args.state.sound_files      ||= [
    { name: :tada,   path: "sounds/tada.wav"   },
    { name: :splash, path: "sounds/splash.wav" },
    { name: :drum,   path: "sounds/drum.wav"   },
    { name: :spring, path: "sounds/spring.wav" },
    { name: :music,  path: "sounds/music.ogg"  }
  ]

  # generate buttons based off the sound collection above
  args.state.spawn_sound_buttons ||= begin
    # create a group of buttons
    # column centered (using col_offset to calculate the column offset)
    # where each item is 2 columns apart
    rects = args.layout.rect_group row:   11,
                                   col_offset: {
                                     count: args.state.sound_files.length,
                                     w:     2
                                   },
                                   dcol:  2,
                                   w:     2,
                                   h:     1,
                                   group: args.state.sound_files

    # now that you have the rects
    # construct the metadata for the buttons
    rects.map do |rect|
      {
        rect: rect,
        name: rect.name,
        path: rect.path,
        primitives: [
          rect.to_border(r: 255, g: 255, b: 255),
          rect.to_label(x: rect.center_x,
                        y: rect.center_y,
                        text: "#{rect.name}",
                        alignment_enum: 1,
                        vertical_alignment_enum: 1,
                        r: 255, g: 255, b: 255)
        ]
      }
    end
  end

  if args.inputs.mouse.up
    args.state.mouse_state = :released
    args.state.dragging_source = false
  elsif args.inputs.mouse.down
    args.state.mouse_state = :held
  end

  args.outputs.background_color = [ 0, 0, 0, 255 ]
end

def render args
  render_ui args
  render_sources args
end
