module Hanuman

  module GraphInstanceMethods
    def each_stage &block
      stages.values.each(&block)
    end

    def descendents stage=nil
      links.find_all do |link|
        stage ? link.from == stage.label : true
      end.map(&:into).uniq.map { |label| stages[label] }.compact
    end

    def ancestors stage=nil
      links.find_all do |link|
        stage ? link.into == stage.label : true
      end.map(&:from).uniq.map { |label| stages[label] }.compact
    end

    def add_stage stage
      stages[stage.label] = stage
    end

    def has_link? from, into
      links.detect { |link| link.from == from.label && link.into == into.label } ? true : false
    end

    def add_link type, from, into
      add_stage(from)
      add_stage(into)
      self.links << Hanuman::LinkFactory.connect(type, from.linkable_name(:in), into.linkable_name(:out))
    end
  end

  class Graph < Stage
    include GraphInstanceMethods
    
    field :stages, Hash,  :default => {}
    field :links,  Array, :default => []
  end

  class GraphBuilder < StageBuilder

    include GraphInstanceMethods
    
    field :stages, Hash,  :default => {}
    field :links,  Array, :default => []

    def define(&blk)     
      graph = for_class || define_class(label)
      self.instance_eval(&blk) if block_given?
      extract_links!
      graph.register
    end

    def build(options = {})
      attrs  = serialize
      stages = attrs.delete(:stages).inject({}){ |hsh, (name, builder)| hsh[name] = builder.build(stage_specific_options(name, options)) ; hsh }
      for_class.receive attrs.merge(stages: stages)
    end

    def stage_specific_options(stage, options)
      scope = options.delete(stage) || {}
      options.merge(scope)
    end

    def namespace() Hanuman::Graph ; end

    def handle_dsl_arguments_for(stage, *args, &blk)
      options = args.extract_options!
      stage.merge!(options)
      stage
    end

    def extract_links!
      self.links.replace([])
      stages.each_pair{ |name, builder| links << builder.links }
      links.flatten!
    end
    
    def serialize
      attrs = attributes
      args  = attrs.delete(:args)
      attrs.delete(:for_class)
      attrs.merge(args)      
    end

    def clone
      cloned_attrs  = Hash[ serialize.select{ |key, val| key != :stages }.map{ |key, val| dup_key = key.dup rescue key ; dup_val = val.dup rescue val ; [ dup_key, dup_val ] } ]
      cloned_links  = links.map{ |link| link.dup }
      cloned_stages = Hash[ stages.map{ |stage| stage.clone } ]
      self.class.receive(cloned_attrs.merge(links: cloned_links).merge(stages: cloned_stages).merge(for_class: for_class))
    end

  end
    
end
