######################################
# WARNING! Highly expiremental code! #
######################################

# This is brute force predicate logic system for Ruby.
# See the test case below to get an understanding of its functionality.

module Predicate

  class Predicate
    attr_reader :predicate, :facts
    def initialize( name )
      @predicate = name
      @facts = []
    end
    def <<(args)
      @facts << args
    end
  end

  # Criterion is a psuedo-method probe  
  class Criterion
    attr_accessor :expression, :matches
    def initialize(exp)
      @expression = exp
      @matches = []
    end
    def match( value )
      if @expression.is_a?(Regexp)
        @matches << value if value =~ @expression
      else
        @matches << value if value == @expression
      end
    end
  end

  class Tester
    attr_reader :value
    def initialize(v)
      @value = v
    end
  end


  module LogicInclusion

    def predicate( st, *args )
      spred = ( (@predicates ||= {})[st] ||= Predicate.new(st) )
      if args.any? { |a| a.is_a?(Regexp) || a.is_a?(Criterion) }
        # every arugument is a criterion probe
        cargs = args.collect { |a| (a.is_a?(Criterion) ? a : Criterion.new(a)) }
        # update criterion matches
        spred.facts.each { |f| f.each_with_index { |a,i| cargs[i].match(a) } }
        # criterion probe
        send("#{st}_?", *cargs)
        # collect the match arrays
        nargs = cargs.collect { |c| c.matches }
        # deduce
        perms = LogicExtension.permutations(nargs)
        deductions = perms.collect { |_p|
          _t = _p.collect { |v| Tester.new(v) }
          _p if send(st,*_t)
        }.compact
        return spred.facts + deductions
      elsif args.all? { |a| a.is_a?(Tester) }
        if spred.facts.include?( args.collect { |t| t.value } )
          return true
        else
          return send("#{st}_?", *args)
        end
      else
        #puts "Defining fact #{st}(" + args.join(',') + ')'
        spred << args
      end
    end  

  end  # LogicInclusion


  module LogicExtension

    def self.permutations( arr, prepend=[] )
      head = arr[0]
      tail = arr[1..-1]
      perms = []
      if tail.empty?
        head.each { |h| perms << (prepend + [h]) }
      else
        head.each { |h| perms = perms | permutations(tail, (prepend + [h])) }
      end
      return perms
    end

    def method_added( st )
      #if st.to_s[-1..-1] != '?' && !method_defined?("#{st}_?")
      if !@prevent_method_added
        @prevent_method_added = true
        #puts "Defining predicate #{st}"
        alias_method("#{st}_?".intern, st)
        module_eval %Q{
          def #{st}(*args)
            predicate(:#{st}, *args)
          end
          def #{st}?(*args)
            targs = args.collect { |a| Tester.new(a) }
            #{st}(*targs)
          end
        }
        @prevent_method_added = false
      end
    end

  end  # LogicalExtension

end  # PredicateLogic



=begin test

  require 'test/unit'

  module TestLogic

    include Predicate::LogicInclusion
    extend Predicate::LogicExtension

    def man(x)
    end

    def woman(x)
    end

    def tool(x)
    end

    def mortal(x)
      woman(x) | man(x)
    end

    def can_use(x, y)
      ( man(x) | woman(x) ) & tool(y)
    end

  end

  class TC_Predicate < Test::Unit::TestCase

    include TestLogic

    def setup
      man('socrates')
      woman('dido')
      tool('hammer')
      can_use('me', 'ball')
    end

    def test_facts
      assert(man?('socrates'), "man?('socrates')")
      assert(woman?('dido'), "woman?('dido')")
      assert(tool?('hammer'), "tool?('hammer')")
      assert(can_use?('me', 'ball'), "can_use?('me', 'ball')")
    end

    def test_deductions
      assert(mortal?('socrates'))
      assert(mortal?('dido'))
      assert(can_use?('socrates','hammer'))
      assert(can_use?('dido','hammer'))
    end

    def test_queries
      assert(man(/.*/).include?(['socrates']))
      assert(woman(/.*/).include?(['dido']))
      assert(mortal(/.*/).include?(['socrates']))
      assert(mortal(/.*/).include?(['dido']))
      assert(can_use(/.*/,/.*/).include?(['socrates', 'hammer']))
      assert(can_use(/.*/,/.*/).include?(['dido', 'hammer']))
      assert(can_use(/.*/,/.*/).include?(['me', 'ball']))
    end

  end

=end