module AR::Game

  class Menu

    attr_accessor :options
    attr_accessor :prompt_class
    attr_accessor :prompt_tick
    attr_accessor :name

    def initialize prompt_class = AR::Input::CommandPrompt
      self.name = ""
      self.options = []
      self.prompt_class = prompt_class
    end

    #present should return resulting events from choices
    def present 
      option = prompt.listen
      while option.nil? #should just mean invalid input 
        option = prompt(true).listen
      end
      option.chosen.call
    end

    def prompt no_menu_display=false
      prompt_text = no_menu_display ? "" : menu_display
      prompt_proc = Proc.new do |input|
        if input.class ==  Fixnum
          input = input.chr
        else
          input = input.chomp
          input = input.strip
        end
        if input == ""

        elsif invalid? input
          AR.put_s "invalid choice '#{input}'... try again..."
          sleep(1.0)
          nil
        elsif ambiguous? input 
          clarify suggessions_from input
        else
          option_from input
        end
      end 

      @cmd_prompt = prompt_class.new prompt_text, prompt_proc,(prompt_tick || "[#{name}]> ")
      @cmd_prompt
    end

    def option_from input
      suggestions_from(input).last
    end

    def suggestions_from choice
      options.select{|o| o.ids.include? choice}
    end

    def menu_display 
      title = self.name.split(" ").map(&:capitalize).join(" ")
      header = "\n=========[#{title}]=========="
      header + "\n" + 
        options.map{|o| "#{o.usage} -> #{o.description}"}.join("\n") + "\n" + 
        "=" * header.size 
    end




    def clarify suggestions
      narrow_down = proc.new do |input|
        num = input.to_i 
        suggestions[num-1]
      end

      prompt_text = "what are you trying to do?  (enter a number)" 
      prompt_text += suggestions.map{|s,i| "#{i}. #{s.id}"}.join("\n")
      prompt_tick = "[#{self.name}]> "
      prompt = AR::Input::KeyPushPrompt.new prompt_text, narrow_down, prompt_tick 
      prompt.listen
    end

    def invalid? input
      suggestions_from(input).empty?
    end

    def ambiguous? input
      suggestions_from(input).size > 1
    end

  end

  class BinaryMenu < Menu
    def initialize
      super AR::Input::KeyPushPrompt
      y = Option.new do |opt| 
        opt.ids = ["y", "Y"]
        opt.usage = "(y) yes"
        opt.chosen = Proc.new do 
          self.yes
        end
      end
      self.options.push y

      n = Option.new do |opt| 
        opt.ids = ["n", "N"]
        opt.usage = "(n) no"
        opt.chosen = Proc.new do 
          self.no
        end
      end
      self.options.push n
    end

    def yes &bl
      if bl.nil?
        if @yes
          @yes.call
        end
      else
        @yes = bl
      end
    end
    def no &bl
      if bl.nil?
        if @no
          @no.call
        end
      else
        @no = bl
      end
    end
  end

  class MainMenu < Menu 
    def initialize
      super
      quit = AR::Game::QuitOption.new 
      new_game = AR::Game::NewGameOption.new
      load_game= AR::Game::LoadGameOption.new
      self.options = [new_game]

      self.options.push load_game if File.exists? "#{AR.save_dir}/default_world_data.yml"
      self.options.push quit 
      self.name = "main menu"
    end 
  end 

  class ExplorationMenu < Menu
    attr_accessor :player
    def initialize player
      super AR::Input::CommandPrompt
      self.player = player 
      action_points_left = player.action_points

      #removed: ["Put", "Give"] temporarily.  As there is really not much of a point yet....
      action_classnames = ["Wait", "Attack",  "Inspect", "Talk", "Travel", "Loot", "Grab", "Consume", "Equip", "Unequip",  "Status", "Rest", "Journal", "Quit"]

      #hacky way to remove travel options if battle mode is active
      action_classnames -= ["Talk", "Travel", "Rest", "Journal"] if player.is_in_combat

      self.options = action_classnames.map do |acn|
        action_class = Object.const_get("AR::Game::#{acn}Action")
        action_choice_class = Object.const_get("AR::Game::#{acn}Choice")
        if action_class.can_fill_params?(player) and action_class.ap_cost <= action_points_left
          action_choice_class.new player
        end
      end.compact
    end 
    def present
      self.name = "action menu | ap: #{player.action_points}"
      super
    end

  end 

  class ReactionMenu < Menu
    attr_accessor :player, :attacker
    def initialize player, attacker

      super AR::Input::CommandPrompt
      self.player = player 
      self.attacker = attacker
      action_points_left = player.action_points

      action_classnames = ["Defend"]
      self.options = action_classnames.map do |acn|
        action_class = Object.const_get("AR::Game::#{acn}Action")
        action_choice_class = Object.const_get("AR::Game::#{acn}Choice")
        if action_class.can_fill_params?(player) and action_class.ap_cost <= action_points_left
          action_choice_class.new player, attacker
        end
      end.compact

      o = AR::Game::Option.new do |o|
        o.ids = ["n", "no", "nothing"]
        o.description = "Do nothing"
        o.usage = "(n)othing"
        o.chosen = Proc.new do 
          nil
        end
      end
      self.options.push o

    end 


    def present
      self.name = "reaction menu | ap: #{player.action_points}"
      super
    end
  end 



  class PlayerBreakMenu < Menu
    attr_accessor :player
    def initialize player
      super AR::Input::KeyPushPrompt
      self.player = player 

      save = Option.new do |opt| 
        opt.ids = ["s"]
        opt.usage = "(s) to save"
        opt.chosen = Proc.new do 
          AR.put_s "Attempting to save your world's data..."
          AR::Events::GameSaveEvent.new({world: player.world})
        end
      end

      quit = Option.new do |opt| 
        opt.ids = ["q"]
        opt.usage = "(q) to quit"
        opt.chosen = Proc.new do 
          key = nil
          while key.nil? 
            AR.put_s "Really quit? (Y/y N/n)"
            key = (AR::Input.take_key.chr.downcase rescue nil)
            key = nil unless ["n", "y"].include? key
          end

          AR::Events::GameOverEvent.new if key == "y"
        end
      end

      self.prompt_tick = ""
      self.options = [save, quit] 
    end


    #same as subclass, but only listens one time
    def present 
      AR.print_s "\n"
      option = prompt.listen
      ret = option.chosen.call unless option.nil?
      ret
    end

    def prompt no_menu_display=false
      prompt_text = no_menu_display ? "" : menu_display
      prompt_proc = Proc.new do |input|
        if input.class ==  Fixnum
          input = input.chr
        else
          input = input.chomp
          input = input.strip
        end
        option_from input
      end 

      @cmd_prompt = prompt_class.new prompt_text, prompt_proc,(prompt_tick || "[#{name}]> ")
      @cmd_prompt
    end




    def menu_display 
      title = self.name.split(" ").map(&:capitalize).join(" ")
      "(any key) to continue  |  #{options.map{|o| o.usage}.join("  |  ")}"
    end
  end 
end

