module AR
  module Game
    class Action
      attr_accessor :performer
      attr_accessor :parameters

      @@action_ap_costs = {}


      def self.ap_cost
        @@action_ap_costs[self] || 1
      end
      def self.ap_cost=(n)
        @@action_ap_costs[self] = n
      end


      def name
        self.class.to_s.split("::").last.gsub(/Action/, "").downcase
      end


      #actions can override this method to expose the primary target
      #first use case was so defense actions can be driven by being the subject
      #of an attack
      def primary_target
        nil
      end

      def initialize performer, parameters = []
        @performer = performer 
        @parameters = parameters
      end

      def performable?
        parameters.select{|p| !p.filled? }.empty?
      end

      def engine
        performer.world.events_engine
      end

      def ap_cost
        self.class.ap_cost 
      end





      def perform
        raise "#{self.class} needs to override perform"
        #return some events
      end

      def self.can_fill_params? performer
        can_fill = true 
        stub_action = self.new performer
        stub_action.parameters.map do |p|
          opts = p.filter performer
          if opts.compact.empty?
            can_fill = false
            break
          else
            p.argument = opts.first
          end
        end 
        can_fill
      end


      def parameter name
        self.parameters.select{|p| p.name == name}.last
      end
      def next_param_to_fill
        self.parameters.select{|p| !p.filled? }.first
      end



      class Parameter
        attr_accessor :name
        attr_accessor :prompt_text
        attr_accessor :argument
        attr_accessor :argument_filter
        attr_accessor :action


        def initialize 
          @name = "param" 
          @action = nil
          @prompt_text = "What/Who/How to do action"
          @argument = nil
          @argument_filter = nil
          yield self
        end

        def filled?
          !argument.nil?
        end

        def filter performer
          return performer.known_objects if argument_filter.nil?
          argument_filter.call performer.known_objects, performer 
        end

      end

    end


    class WaitAction < Action
      
      self.ap_cost = 0
      def initialize performer,  parameters = [] 
        super performer, parameters
      end

      def perform
      end
    end


    class QuitAction < Action
      self.ap_cost = 0
      def initialize performer,  parameters = [] 
        super performer, parameters
      end

      def perform
        AR::Events::GameOverEvent.new
      end
    end


    class JournalAction < Action
      self.ap_cost = 0
      def initialize performer,  parameters = [] 
        super
      end
      def perform
        engine.create AR::Events::GameSaveEvent, {world: performer.world}
      end
    end


    class InspectAction < Action

      def initialize performer,  parameters =[] 
        super performer, parameters
        self.parameters << Parameter.new do |p|
          p.name = "target"
          p.prompt_text = "What do you inspect?"
          p.action = self
          p.argument_filter = Proc.new do |arg_set|
            monsters = arg_set.select{|o| o.is_a? AR::Game::Monster}
            non_monst = arg_set - monsters
            monsters + non_monst
          end
        end

      end

      def perform
        #contests can go here
        engine.create AR::Events::DiscoverableInspectedEvent, ({discoverable: parameter("target").argument, inspector: performer})
      end
    end

    class RestAction < Action
      self.ap_cost = 0

      def initialize performer,  parameters =[] 
        super performer, parameters
        self.parameters << Parameter.new do |p|
          p.name = "target"
          p.prompt_text = "Where do you rest?"
          p.action = self
          p.argument_filter = Proc.new do |arg_set|
            arg_set.select{|o| o.has_behavior? AR::Game::RestStation}
          end
        end
      end

      def perform
        #contests can go here
        AR::Events::CharacterSlept.new rester: performer, station: parameter("target").argument 
      end
    end






    class StatusAction < Action
      self.ap_cost = 0
      def initialize performer,  parameters =[] 
        super performer, parameters
      end
      def perform
        AR::Events::StatusReview.new player: performer 
      end
    end


    class TravelAction < Action
      self.ap_cost = 2
      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.argument_filter = Proc.new do |arg_set|
            arg_set.select{|o| o.behaviors.include? AR::Game::Gateway}
          end
          p.name = "gateway"
          p.prompt_text = "Which path do you take?"
          p.action = self
        end
      end

      def perform
        #contests can go here
        engine.create AR::Events::ARObjectTravelledEvent, ({traveller: performer, gateway: parameter("gateway").argument})
      end
    end


    class TalkAction < Action

      def self.filter target_set, performer
      end

      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "npc"
          p.argument_filter = Proc.new do |arg_set|

            arg_set.select{|o| o.behaviors.include? AR::Game::NpcDialogBehavior and !o.root_dialog_node_class.nil?}
          end
          p.action = self
          p.prompt_text = "Who do you talk to?"
        end
      end

      def perform
        #contests can go here
        npc = parameter("npc").argument
        engine.create AR::Events::DialogBeganEvent, player: npc.world.player, npc: npc
      end
    end


    
    class LootAction < Action
      def self.filter target_set, performer
      end

      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "target"
          p.argument_filter = Proc.new do |arg_set|
            initial = arg_set.select{|o| o.has_behavior? AR::Game::LootableContainer}
            initial.select{|o| !o.inventory_item_ids.empty?} 
          end
          p.action = self
          p.prompt_text = "What do you loot?"
        end
      end

      def perform
        #contests can go here
        AR::Events::Loot.new looter: performer, container: parameter("target").argument
      end
    end


    
    class GrabAction < Action
      def self.filter target_set, performer
      end

      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "item"
          p.argument_filter = Proc.new do |arg_set|
            initial = arg_set.select{|o| o.behaviors.include? AR::Game::ItemBehavior}
            initial.select{|o| !self.performer.inventory_items.include?(o)}
          end
          p.action = self
          p.prompt_text = "What item do you grab?"
        end
      end

      def perform
        #contests can go here
        engine.create AR::Events::ItemAddedToInventory, ({inventory_owner: performer, item: parameter("item").argument, dont_narrate: true})
      end
    end

    class PutAction < Action
      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "item"
          p.argument_filter = Proc.new do |arg_set|
            initial = self.performer.inventory_items
            initial.select{|o| o.behaviors.include? AR::Game::ItemBehavior}
          end
          p.action = self
          p.prompt_text = "What item do you put down??"
        end
      end

      def perform
        #contests can go here
        results = []
        results.push (engine.create AR::Events::ItemRemovedFromInventory, ({inventory_owner: performer, item: parameter("item").argument}))
        results.push (engine.create AR::Events::ARObjectEnteredInhabitableEvent, ({inhabitable: performer.parent, enterer: parameter("item").argument, placement: "on the ground",  dont_narrate: true}))
        results
      end
    end

    class GiveAction < Action
      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "item"
          p.argument_filter = Proc.new do |arg_set|
            initial = self.performer.inventory_items
            initial.select{|o| o.behaviors.include? AR::Game::ItemBehavior}
          end
          p.action = self
          p.prompt_text = "What item do you give?"
        end
        self.parameters << Parameter.new do |p|
          p.name = "receiver"
          p.argument_filter = Proc.new do |arg_set|
            initial = arg_set.select{|o| o.behaviors.include? AR::Game::InventoryBehavior and o.is_a? AR::Game::Character}
            initial - [self.performer]
          end
          p.action = self
          p.prompt_text = "Who do you give it to?"
        end

      end

      def perform
        #contests can go here
        results = []
        results.push engine.create AR::Events::ItemRemovedFromInventory, ({inventory_owner: performer, item: parameter("item").argument})
        results.push engine.create AR::Events::ItemAddedToInventory, ({inventory_owner: parameter("receiver").argument, item: parameter("item").argument})
      end
    end

    class ConsumeAction < Action
      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "item"
          p.argument_filter = Proc.new do |arg_set|
            initial = self.performer.inventory_items
            initial.select{|o| o.behaviors.include? AR::Game::Consumable}
          end
          p.action = self
          p.prompt_text = "What do you consume?"
        end
      end

      def perform
        AR::Events::ItemConsumed.new consumer: performer,  item: parameter("item").argument
      end
    end

    class EquipAction < Action
      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "item"
          p.argument_filter = Proc.new do |arg_set|
            initial = self.performer.inventory_items
            initial.select{|o| o.behaviors.include? AR::Game::Equippable}
          end
          p.action = self
          p.prompt_text = "What do you equip?"
        end
      end

      def perform
        results = [] 
        to_equip = parameter("item").argument
        equipped = performer.equipped_on to_equip.slot 
        unless equipped.nil?
          results.push(AR::Events::ItemUnequipped.new equipper: performer, item: equipped)
        end
        results.push(AR::Events::ItemEquipped.new equipper: performer,  item: to_equip)
        results
      end
    end

    class UnequipAction < Action
      def initialize performer,  parameters =[] 
        super 
        self.parameters << Parameter.new do |p|
          p.name = "item"
          p.argument_filter = Proc.new do |arg_set|
            self.performer.all_equipped_items
          end
          p.action = self
          p.prompt_text = "What do you unequip?"
        end
      end

      def perform
        results = [] 
        to_unequip = parameter("item").argument
        results.push(AR::Events::ItemUnequipped.new equipper: performer, item: to_unequip)
        results
      end
    end




    class AttackAction < Action
      self.ap_cost = 2
      def initialize performer, parameters = []
        super
        self.parameters << Parameter.new do |p|
          p.name = "target"
          p.prompt_text = "Who/What do you attack?"
          p.argument_filter = Proc.new do |arg_set|
            arg_set.select{|o| o.behaviors.include? AR::Game::Health and o != self.performer}
          end
          p.action = self
        end
        self.parameters << Parameter.new do |p|
          p.name = "technique"
          p.prompt_text = "What technique do you use?"
          p.argument_filter = Proc.new do |arg_set|
            self.performer.attack_techniques
          end
          p.action = self
        end
      end

      def perform
        #contests can go here
        #techs have success events and failure events
        #have a way to resolve a contest
        tech = parameter("technique").argument
        target = parameter("target").argument
        AR::Events::HitAttempt.new hitter: self.performer, hitee: target, technique: tech
      end
    end



    class DefendAction < Action
      attr_accessor :attacker
      def initialize performer, attacker=nil, parameters = []
        super(performer, parameters)
        self.attacker = attacker
        self.parameters << Parameter.new do |p|
          p.name = "technique"
          p.prompt_text = "What defense technique do you use?"
          p.argument_filter = Proc.new do |arg_set|
            #defense techniques become options.
            self.performer.defense_techniques
          end
          p.action = self
        end
      end

      def perform
        #contests can go here
        #techs have success events and failure events
        #have a way to resolve a contest
        tech = parameter("technique").argument
        tech.perform performer: performer, attacker: attacker
      end
    end

    class DefendAction::Technique

      attr_accessor :perform_proc
      attr_accessor :name
      
      def initialize
      end

      def when_perform &perform
        self.perform_proc = perform
      end

      def perform params
        self.perform_proc.call params
      end
    end



    #just a way to group and easily create new techs 
    class AttackAction::Technique

      attr_accessor :perform_proc
      attr_accessor :name
      attr_accessor :source
      
      def initialize
      end

      def when_perform &perform
        self.perform_proc = perform
      end

      def perform params
        self.perform_proc.call params
      end
    end

    class Punch <  AttackAction::Technique
      def initialize
        self.name = "punch"
        super 
        when_perform do |params|
          performer = params[:performer]
          target = params[:target]
          
          default_dice = performer.unarmed_damage_dice
          amount = rand(1..default_dice)
          AR.log("Punch damage roll (1d#{default_dice}): #{amount}")
          AR::Events::DamageReceived.new damaged: target, damager: performer, damage_amount: amount
        end 
      end
    end


    class SlashTech <  AttackAction::Technique
      def initialize
        self.name = "slash"
        super 
        when_perform do |params|
          performer = params[:performer]
          target = params[:target]
          wep = performer.equipped_on(:main_hand)

          amount = rand(1..(wep.damage_dice))
          AR.log("Slash damage roll (1d#{wep.damage_dice}): #{amount}")

          AR::Events::DamageReceived.new damaged: target, damager: performer, damage_amount: amount 
        end 
      end
    end

    class StabTech <  AttackAction::Technique
      def initialize
        self.name = "stab"
        super 
        when_perform do |params|
          performer = params[:performer]
          target = params[:target]
          wep = performer.equipped_on(:main_hand)
          amount = rand(1..(wep.damage_dice))
          AR.log("Stab damage roll (1d#{wep.damage_dice}): #{amount}")
          AR::Events::DamageReceived.new damaged: target, damager: performer, damage_amount: amount
        end 
      end
    end


    class BlockTech < DefendAction::Technique
      def initialize
        self.name = "block"
        super 
        when_perform do |params|
          performer = params[:performer]
          attacker = params[:attacker]
          shield = performer.equipped_on(:off_hand)
          AR.log "ACTION: performer is blocking: #{performer.tag}"
          buff = AR::Game::BlockBuff.new.subscribe!
          subj = performer.gauge("damage_resist")
          v = [performer.gauge("strength").modified_value, shield.block_rating].min
          

          buff.value = v
          buff.attacker = attacker 
          buff.on(subj)
           
          AR::Events::BuffApplied.new buff: buff, subject: subj
        end 

      end
    end




    class DodgeTech < DefendAction::Technique
      def initialize
        self.name = "dodge"
        super 
        when_perform do |params|
          performer = params[:performer]
          attacker = params[:attacker]
          AR.log "performer is dodging: #{performer.class}"
          buff = AR::Game::DodgeBuff.new.subscribe!
          subj = performer.gauge("avoidance")
          v = performer.gauge("dexterity").modified_value 

          buff.value = v
          buff.attacker = attacker 
          buff.on(subj)
           
          AR::Events::BuffApplied.new buff: buff, subject: subj
        end 

      end
    end

  end
end

