require 'rbbt/entity'

module AssociationItem
  extend Entity

  annotation :knowledge_base
  annotation :database
  annotation :reverse

  property :name => :single do
    [source_entity, target_entity].collect{|e| e.respond_to?(:name)? e.name || e : e } * "~"
  end 

  property :full_name => :single do
    database ? [database, name] * ":" : name
  end 

  property :invert => :single do
    s,_sep,t= self.partition "~"
    inverted = self.annotate([t,s] * _sep)
    inverted.reverse = ! reverse
    inverted
  end

  property :namespace => :both do
    knowledge_base.namespace
  end

  property :part => :array2single do
    self.clean_annotations.collect{|p| p.partition("~") }
  end

  property :target => :array2single do
    self.part.collect{|p| p[2]}
  end

  property :source => :array2single do
    self.clean_annotations.collect{|p| p[/[^~]+/] }
  end

  property :target_entity_type => :both do
    Entity.formats[target_type].to_s
  end

  property :source_entity_type => :both do
    Entity.formats[source_type].to_s
  end

  property :target_type => :both do
    type = reverse ? knowledge_base.source(database) : knowledge_base.target(database)
  end

  property :source_type => :both do
    reverse ? knowledge_base.target(database) : knowledge_base.source(database)
  end

  property :undirected => :both do
    knowledge_base.undirected(database)
  end

  property :target_entity => :array2single do
    type = reverse ? knowledge_base.source(database) : knowledge_base.target(database)
    knowledge_base.annotate self.target, type, database #if self.target.any?
  end

  property :source_entity => :array2single do
    type = reverse ? knowledge_base.target(database) : knowledge_base.source(database)
    knowledge_base.annotate self.source, type, database #if self.source.any?
  end

  property :index => :both do |database|
    @index ||= knowledge_base.get_index(database)
  end
  property :value => :array2single do
    index = index(database)
    value = (reverse ? index.reverse : index).chunked_values_at self
    value.collect{|v| NamedArray.setup(v, index.fields)}
  end

  property :info_fields => :both do
    knowledge_base.index_fields(database)
  end

  property :info => :array2single do
    fields = self.info_fields

    return [{}] * self.length if fields.nil? or fields.empty?

    value = self.value
    value.collect{|v|
      raise "No info for pair; not registered in index" if v.nil?
      Hash[*fields.zip(v).flatten]
    }
  end

  property :tsv => :array do
    info_fields = self.info_fields
    fields = [self.source_type, self.target_type].concat info_fields
    type = [self.source_type, self.target_type] * "~"
    tsv = TSV.setup({}, :key_field => type, :fields => fields, :type => :list, :namespace => self.namespace)
    self.each do |match|
      tsv[match] = [match.source, match.target].concat match.info.values_at(*info_fields)
    end
    tsv.entity_options = {:organism => namespace}
    knowledge_base.entity_options.each do |type,options|
      tsv.entity_options.merge! options
    end
    tsv
  end

  property :filter => :array do |*args,&block|
    keys = tsv.select(*args,&block).keys
    keys = self.annotate Annotated.purge(keys)
    keys
  end

  def self.incidence(pairs, key_field = nil, &block)
    matrix = {}
    targets = []
    sources = []
    matches = {}

    pairs.inject([]){|acc,m| acc << m; acc << m.invert if m.respond_to?(:undirected) and m.undirected; acc  }.each do |p|
      s, sep, t = p.partition "~"

      sources << s
      targets << t
      if block_given?
        matches[s] ||= Hash.new{nil}
        value = block.call p
        matches[s][t] = value unless value.nil? or (mv = matches[s][t] and value > mv)
      else
        matches[s] ||= Hash.new{false}
        matches[s][t] ||= true 
      end
    end

    sources.uniq!
    targets = targets.uniq.sort

    matches.each do |s,hash|
      matrix[s] = hash.values_at(*targets)
    end

    defined?(TSV)? TSV.setup(matrix, :key_field => (key_field || "Source") , :fields => targets, :type => :list) : matrix
  end

  def self.adjacency(pairs, key_field = nil, &block)
    incidence = incidence(pairs, key_field, &block)

    targets = incidence.fields
    adjacency = TSV.setup({}, :key_field => incidence.key_field, :fields => ["Target"], :type => :double)
    TSV.traverse incidence, :into => adjacency, :unnamed => true do |k,values|
      target_values = targets.zip(values).reject{|t,v| v.nil? }.collect{|t,v| [t,v]}
      next if target_values.empty?
      [k, Misc.zip_fields(target_values)]
    end
  end

  def self._select_match(orig, elem)
    if Array === orig and Array === elem
      (orig & elem).any?
    elsif Array === orig
      orig.include? elem
    elsif Array === elem
      elem.include? orig
    else
      elem === orif
    end
    false
  end

  def self.select(list, method = nil, &block)
    if method and method.any?
      list.select do |item|
        method.collect do |key,value|
          case key
          when :target
            _select_match item.target, value
          when :source
            _select_match item.source, value
          else
            orig = item.info[key]
            orig = orig.split(";;") if String orig
            _select_match orig, value 
          end
        end
      end
    else
      list.select(&block)
    end
  end
end