require "enumerator"


class Product
    
    PRODUCTS= {[Object, Object] => Product}
    
    def initialize(lhs, rhs)
      @lhs= lhs
      @rhs= rhs
    end
    
    def inspect()
      return "product(%p, %p)" % [lhs, rhs]
    end
    
    attr_reader(:lhs, :rhs)
    
    def self.class_product(lhs, rhs, create= true)
      product= PRODUCTS[[lhs, rhs]]
      if !product
        if create
          product= Class.new(superproduct(lhs, rhs))
          product.const_set(:LHS_CLASS, lhs)
          product.const_set(:RHS_CLASS, rhs)
          def product.inspect()
            return "%p*%p" % [self::LHS_CLASS, self::RHS_CLASS]
          end
          PRODUCTS[[lhs, rhs]]= product
        else
          product= superproduct(lhs, rhs)
        end
      end
      return product
    end
    
    def self.object_product(lhs, rhs)
      if lhs.is_a?(Class) && rhs.is_a?(Class)
        return class_product(lhs, rhs)
      else
        return class_product(lhs.class, rhs.class, false).new(lhs, rhs)
      end
    end
    
    def self.superproduct(lhs, rhs)
      cands= []
      each_base(lhs) do |bl|
        each_base(rhs) do |br|
          cands.push([bl, br]) if PRODUCTS[[bl, br]]
        end
      end
      for cl, cr in cands
        if cands.all?(){ |tl, tr| (base?(cl, tl) && base?(cr, tr)) || (base?(tl, cl) && base?(tr, cr)) }
          return class_product(cl, cr)
        end
      end
      raise("Never reach here")
    end
    
    def self.each_base(klass, &block)
      while klass
        yield(klass)
        klass= klass.superclass
      end
    end
    
    def self.base?(base, derived)
      return enum_for(:each_base, derived).include?(base)
    end
    
end


def def_product(product, &block)
  product.class_eval(&block)
end


def product(lhs, *rhses)
  result= lhs
  for rhs in rhses
    result= Product.object_product(result, rhs)
  end
  return result
end


module Producable
    
    def *(rhs)
      return product(self, rhs)
    end
    
end


class Class
    
    include(Producable)
    
end


if __FILE__==$0
  
  class Acid
      include(Producable)
      attr_accessor(:anion)
  end
  
  class Base
      include(Producable)
      attr_accessor(:cation)
  end
  
  def_product(Acid*Base) do
      def salt
        return rhs.cation + lhs.anion
      end
  end
  
  h_cl= Acid.new()
  h_cl.anion= "Cl"
  na_oh= Base.new()
  na_oh.cation= "Na"
  
  p (h_cl*na_oh).salt    # => "NaCl"
  p (h_cl*na_oh).class   # => Acid*Base
  
  class WeakBase < Base
  end
  
  def_product(Acid*WeakBase) do
      def acid?
        return true
      end
  end
  
  nh4_oh= WeakBase.new()
  nh4_oh.cation= "NH4"
  
  p (h_cl*nh4_oh).salt               # => "NH4Cl"
  p (h_cl*nh4_oh).acid?              # => true
  p (h_cl*nh4_oh).is_a?(Acid*Base)   # => true
  
  def_product(Array*Array) do
      def map(&block)
        return (0...lhs.size).map(){ |i| yield(lhs[i], rhs[i]) }
      end
  end
  
  p product([1, 2], [3, 4]).map(){ |a, b| a+b }   # => [4, 6]
  
  def_product(Object*Symbol*Array) do
      def call()
        obj= lhs.lhs
        sym= lhs.rhs
        ary= rhs
        obj.send(sym, *ary)
      end
  end
  
  p product("hoge", :gsub, [/o/, "e"]).call()   # => "hege"
  
end

