module AR::Game::Perception
  EXP_THRESHOLDS = [1, 4, 8, 13, 20, 27, 34, 41, 48]
  def known_objects
    self.known_object_ids.map{|id| self.world.find id}
  end

  def known_objects=obj_arr
    self.known_object_ids = obj_arr.map(&:id)
  end


  def self.init ar_object
    ar_object.class.attr_serializeable :known_object_ids 
    ar_object.class.attr_serializeable :perception_exp

    ar_object.register_gauge "perception"
    ar_object.register_gauge "critical_hit"
    ar_object.after_gauges_init do |obj|
      perc = obj.gauge("perception")
      crit = obj.gauge("critical_hit")
      crit.change_max 100
      perc.parents! crit, with: AR::Game::GaugeBehavior::LinearGaugeRelation, data: {factor: 1}
      perc.change_current_and_max 1
      perc.manual_child_sync
    end
    

    ar_object.class.attr_serializeable :perception_account do |perc_acc_data, op, inst|
      case op
      when :load
        ret = {}
        perc_acc_data.each do |k, v|
          if k.to_s.to_i == 0
            ret[k.to_s] = v
          else
            ret[k.to_s.to_i] = v
          end
        end
        ret
      when :unload
        perc_acc_data
      end
    end

    ar_object.class.attr_serializeable :weak_point_accounts do |weak_point_data, op, inst|
      case op
      when :load
        ret = {}
        weak_point_data.each do |k, v|
          if k.to_s.to_i == 0
            ret[k.to_s] = v
          else
            ret[k.to_s.to_i] = v
          end
        end
        ret
      when :unload
        weak_point_data
      end
    end

    ar_object.perception_account = {}
    ar_object.weak_point_accounts = {}
    ar_object.known_object_ids = []
    ar_object.perception_exp = 0
  end

  def perception_lvl
    gauge("perception").modified_value
  end

  def supposed_perception_lvl
    currently = perception_lvl
    lvl = currently
    EXP_THRESHOLDS.each_with_index do |threshold, i|
      lvl = i+1
      if perception_exp >= threshold
        next
      else
        break
      end
    end
    lvl
  end



  def self.emit_discovery_when_necessary
    AR::Events::Resolution.new event_class: AR::Events::PerceptionAccountChangedEvent, as: "perceiver" do |e|
      results=[]
      perceiver = e.perceiver
      discoverable = e.discoverable
      change       = e.change

      can_see = perceiver.can_see? discoverable
      perceiver.change_perception_for discoverable, change
      potential_to_see = perceiver.potential_to_see? discoverable

      if potential_to_see != can_see
        if potential_to_see
          results << (e.engine.create AR::Events::DiscoveryMadeEvent, ({perceiver: perceiver, discovered: discoverable}))
        else
          results << (e.engine.create AR::Events::DiscoveryLostEvent, ({perceiver: perceiver, undiscovered: discoverable}))
        end
      end 

      results
    end
  end

  def self.add_to_known_objects_on_discovery
    AR::Events::Resolution.new event_class: AR::Events::DiscoveryMadeEvent, as: "perceiver" do |e|
      
      perceiver = e.perceiver
      discovered = e.discovered
      if perceiver.can_see? discovered
        e.dont_narrate = true
      else
        perceiver.known_object_ids.push discovered.id
      end
      nil
    end
  end

  def self.rem_from_known_objects_on_undiscovered
    AR::Events::Resolution.new as: "perceiver", event_class: AR::Events::DiscoveryLostEvent do |e|
      perceiver = e.perceiver
      undiscovered= e.undiscovered
      perceiver.known_object_ids.delete undiscovered.id
      #undoing this. Doesn't it make sense that you keep perception of certain objects through time?
      #perceiver.perception_account.delete undiscovered.id
      perceiver.weak_point_accounts.delete undiscovered.id
      nil
    end
  end

  def reset_weak_points_for discoverable
    weak_point_accounts.delete discoverable.id
  end


  def self.discovery_lost_on_object_removed_from_game ar_object
    AR::Events::Subscription.new event_class: AR::Events::ObjectRemovedFromGame do |e|
      removed = e.ar_object 
      if  ar_object.can_see? removed and ar_object != removed
          AR::Events::DiscoveryLostEvent.new perceiver: ar_object, undiscovered: removed 
      else
        nil
      end
    end
  end
  def self.discovery_lost_on_item_added_to_inventory ar_object
    AR::Events::Subscription.new event_class: AR::Events::ItemAddedToInventory do |e|
      item = e.item
      if  ar_object.can_see? item
          (ar_object.create AR::Events::DiscoveryLostEvent, ({perceiver: ar_object, undiscovered: item}))
      else
        nil
      end
    end
  end


  def self.add_weak_points_to_characters_on_inspected 
    AR::Events::Resolution.new event_class: AR::Events::DiscoverableInspectedEvent, as: "inspector" do |e|
      perceiver = e.inspector
      discoverable = e.discoverable
      if discoverable.is_a? AR::Game::Character
        amount = perceiver.gauge("perception").modified_current * 4
        #you get at least 1
        amount = 1 if amount == 0 
        AR.log("PERCEPTION: #{perceiver.tag} discovered #{amount} weak point(s) on #{discoverable.tag}")
        perceiver.add_weak_points_for discoverable, amount
        nil
      end
    end
  end

  def self.add_perception_exp_on_inspect
    AR::Events::Resolution.new event_class: AR::Events::DiscoverableInspectedEvent, as: "inspector" do |e|
      perceiver = e.inspector
      discoverable = e.discoverable
      if discoverable.is_a? AR::Game::Character and perceiver.should_be_in_battle?
        perceiver.perception_exp += 1
        AR.log("#{perceiver.tag} is gaining an exp point, supposed lvl: #{perceiver.supposed_perception_lvl}")
        nil
      end
    end
  end


  def self.rem_weak_points_from_characters_on_crit
    AR::Events::Resolution.new event_class: AR::Events::HitSuccess, as: "hitter" do |e|
      hitter = e.hitter
      hitee = e.hitee
      if e.is_critical
        AR.log("PERCEPTION: #{hitter.tag} got a crit on #{hitee.tag}, so removing weak point counters")
        hitter.reset_weak_points_for hitee
      end
      nil
    end
  end

  def self.gain_perception_lvl_on_slept
    AR::Events::Resolution.new event_class: AR::Events::CharacterSlept, as: "rester" do |e|
      c = e.rester
      currently = c.perception_lvl
      supposed = c.supposed_perception_lvl
      delta = supposed - currently
      if delta > 0
        AR::Events::ExpLevelGained.new leveler: c, gauge: c.gauge("perception"), amount: delta
      end
    end
  end

  def self.extended ar_object
    self.init ar_object

    ar_object.resolutions << emit_discovery_when_necessary
    ar_object.resolutions << add_to_known_objects_on_discovery
    ar_object.resolutions << rem_from_known_objects_on_undiscovered
    ar_object.resolutions << add_weak_points_to_characters_on_inspected 
    ar_object.resolutions << add_perception_exp_on_inspect
    ar_object.resolutions << rem_weak_points_from_characters_on_crit
    ar_object.resolutions << gain_perception_lvl_on_slept
    ar_object.subscriptions << discovery_lost_on_item_added_to_inventory(ar_object)
    ar_object.subscriptions << discovery_lost_on_object_removed_from_game(ar_object)
  end

  #should really just be called internally by the extending class:w


  def perception_for object
    init_perception_to object if perception_account[object.id].nil?
    perception_account[object.id]
  end

  #should change as a resolution for the 
  #perception gained event
  def change_perception_for discoverable, amount
    p_v = perception_for discoverable
    perception_account[discoverable.id] = p_v + amount

    #do object lost sight/gained sight events if nessecary
    perception_account[discoverable.id]
  end

  def add_weak_points_for discoverable, num
    weak_point_accounts[discoverable.id] = 0 if weak_point_accounts[discoverable.id].nil?
    weak_point_accounts[discoverable.id] += num
  end

  def weak_points_for discoverable
    weak_point_accounts[discoverable.id] = 0 if weak_point_accounts[discoverable.id].nil?
    weak_point_accounts[discoverable.id] 
  end



  def can_see? discoverable
    known_objects.include? discoverable
  end 

  def now_sees discoverable
    known_object_ids.push discoverable.id
  end

  def potential_to_see? discoverable
    discoverable.discovery_threshold <= perception_for(discoverable)
  end

  def init_perception_to object
    perception_account[object.id] = 0
  end

  def del_perception_to discoverable
    perception_account.delete object.id
  end

end

