require "psych"
module AR::Game::WorldBehavior 
   
  attr_accessor :events_engine
  attr_accessor :narrator
  attr_accessor :game_object_instantiator
  attr_accessor :new_game_event

  def instantiate_game_objects!
    game_object_instantiator.call self
  end
  
  def instantiate_game_objects &routine
    self.game_object_instantiator = routine
  end

  def self.init ar_object, world_data = {}
    ar_object.class.attr_serializeable :world_name, :time, :day
    ar_object.class.attr_serializeable :all_ar_objects do |all_objects, op|
      ret = [] 
      case op
      when :load
        all_objects.each do |info|
          klass = Object.const_get(info[:klass])
          o = klass.new info[:id]
          o.load_data info[:data]
          ret.push o
        end
      when :unload
        all_objects.each do |o|
          obj_element = {} 
          obj_element[:id] = o.id
          obj_element[:klass] = o.class.to_s
          obj_element[:data]  = o.unload_data
          ret.push obj_element
        end
      end
      ret
    end
    ar_object.time = 0 
    ar_object.day = 1 
    ar_object.all_ar_objects = []
    ar_object.events_engine = AR::Events::Engine.new ar_object
    ar_object.world_name = "default"
    AR::Events::Event.set_engine ar_object.events_engine
    AR::Game::Behavior.set_world ar_object 
  end

  def self.extended ar_object
    self.init ar_object
    ar_object.subscriptions << self.establish_self_as_world(ar_object)
    ar_object.resolutions << self.save_on_save_event
    ar_object.resolutions << self.hook_subscriptions_up(ar_object)
    ar_object.resolutions << self.remove_object_from_game_on_object_removed
    ar_object.hook_in_object ar_object
  end 

  def self.hook_subscriptions_up world
    #this is necessary to hook objects up after they've been instantiated.
    #We can't simply do this right jhen they are initialized in game base behavior.  Since it's the root behavior, the object doesn't have any subcriptions yet.
    AR::Events::Resolution.new _for: AR::Events::GameStartedEvent, as: "world" do |e|
     
      w=e.world
      w.all_ar_objects.each do |o|
        world.add_subscriptions_from o
      end

      nil
    end
  end

  def self.establish_self_as_world world
    s = AR::Events::Subscription.new do |e|
      world.hook_in_object e.ar_object 
    end
    s.event_class = AR::Events::ARObjectCreatedEvent
    s
  end 

  def save_to_file n
    
    d = self.unload_data 
    
    d=JSON.parse(d.to_json).to_yaml 
    Dir.mkdir AR.save_dir unless File.directory?(AR.save_dir)
    f=File.open("#{AR.save_dir}/#{n}_world_data.yml", "w")
    f.write d
    f.close

    d
  end

  def self.save_on_save_event
    AR::Events::Resolution.new event_class: AR::Events::GameSaveEvent, as: "world" do |e|
      world = e.world
      world.save_to_file world.world_name
    end
  end

  def self.remove_object_from_game_on_object_removed
    AR::Events::Resolution.new event_class: AR::Events::ObjectRemovedFromGame, as: "world" do |e|
      obj  = e.ar_object
      w = e.world
      w.all_ar_objects.delete obj
      w.remove_subscriptions_for obj
      nil
    end
  end


  def start!
    while events_engine.tick!
    end
  end

  def seed
    #should be given a block
    yield self
  end

  def scenes
    all_ar_objects.select{|o| o.behaviors.include? AR::Game::Inhabitable}   end



  def player
    all_ar_objects.select{|o| o.behaviors.include? AR::Game::PlayerControl }.first
  end


  def world
    all_ar_objects.select{|o| o.behaviors.include? AR::Game::WorldBehavior}.first
  end


  def find id
    return nil if id.nil?
    self.all_ar_objects.select{|o| o.id == id}.first
  end



  def hook_in_object o

    existing = find o.id
    unless existing.nil?
      AR.log("WORLD: WARNING: object #{existing.tag} was already existing it seems")
      all_ar_objects.delete existing
    end
    raise "You already added an object with id #{o.id}: #{find(o.id).class} (when trying to add a #{o.class})" unless (find o.id).nil?
    unless o == self
      o.world = self 
      add_object o
    end
    add_subscriptions_from o
  end

  def add_subscriptions_from o
    o.subscriptions.each do |subsc|
      unless self.events_engine.subscriptions.include? subsc
        self.events_engine.add_subscription subsc 
      else
        begin
        raise "Warning: You already added the subsciption #{subsc} for #{o.class}@#{o.id}" 
        rescue Exception => e
           puts "#{e}: #{e.backtrace.join("\n")}".colorize :yellow
        end
      end
    end
  end


  def rem_subscriptions_for o
    self.events_engine.remove_subscriptions_for o
  end



  def remove_subscriptions_for o
    rem_subscriptions_for o
  end

  def hook_in_objects o_arr
    o_arr.each{|o| hook_in_object o}
  end

  def add_object o
    self.all_ar_objects << o
  end





  
  def incr_time!
    self.time+=1

    if self.time >=24
      self.time = 0
      self.day += 1
    end
  end


  #should be called from a load
  def seed_for_load!
    events_engine.trigger AR::Events::ClockTickEvent, ({world: self})
  end

  def seed_for_new!
    instantiate_game_objects! 
    events_engine.pipe all_gauge_managers.map{|obj| AR::Events::GaugesInitialized.new gauge_manager: obj} 
    events_engine.pipe new_game_event 
  end

  def all_gauge_managers 
    all_ar_objects.select{|ar_obj| ar_obj.has_behavior? AR::Game::GaugeManagement}
  end



end
