module Observable private

def each_variable_in(vars)
  for var in vars
    unless instance_methods(true).include? "when_#{var}"
      unless instance_methods(true).include?(var.to_s)
        class_eval %{attr_reader :#{var}}
      end

      writer = "#{var}="
      if instance_methods(false).include?(writer)
        real_writer = "___#{var}___writer___"
        alias_method real_writer.intern, writer.intern
      elsif instance_methods(true).include?(writer)
        real_writer = "super"
      else
        real_writer = "@#{var}="
      end

      yield var, writer, real_writer

    end
  end
end

def observable_state(*vars)
  each_variable_in(vars) do |var, writer, real_writer|
    add_observable_state_methods var, writer, real_writer
    add_observer_methods var
  end
end
alias :observable :observable_state

def observable_signal(*vars)
  each_variable_in(vars) do |var, writer, real_writer|
    add_observable_signal_methods var, writer, real_writer
    add_observer_methods var
  end
end
alias :signal :observable_signal

def add_observable_state_methods var, writer, real_writer
  class_eval %{
    def #{writer} value
      old_value = #{var}
      unless value == old_value
        #{real_writer} value
        observer_map = @#{var}__observer_map
        if observer_map
          for (observer, pattern), block in observer_map
            if pattern === value
              case block.arity
              when 2
                block[value, old_value]
              else
                block[value]
              end
            end
          end
        end
      end
    end

    def when_#{var} pattern=Object, &block
      observer_map = @#{var}__observer_map ||= {}
      if block
        observer = eval "self", block
        observer_map[[observer, pattern]] = block

        value = #{var}

        if pattern === value # is there already a match?
          case block.arity
          when 2
            block[value, nil]
          else
            block[value]
          end
        end
      else
        $stderr.puts "Observable: warning: no block given for:\n" +
                     "when_#{var} \#\{pattern.inspect\}"
      end
    end
  }
end

def add_observable_signal_methods var, writer, real_writer
  class_eval %{
    def #{writer} value
      if value
        #{real_writer} value
        observer_map = @#{var}__observer_map
        if observer_map
          for (observer, pattern), block in observer_map
            if pattern === value
              block[value]
            end
          end
        end
        #{real_writer} nil
      end
    end

    def when_#{var} pattern=Object, &block
      observer_map = @#{var}__observer_map ||= {}
      if block
        observer = eval "self", block
        observer_map[[observer, pattern]] = block
      else
        $stderr.puts "Observable: warning: no block given for:\n" +
                     "when_#{var} \#\{pattern.inspect\}"
      end
    end
  }
end

def add_observer_methods var
  class_eval %{
    def cancel_when_#{var} pattern, observer
      observer_map = @#{var}__observer_map
      observer_map.delete [observer, pattern] if observer_map
    end
  }
end

module Match
  # Some constants, classes, and functions for matching

  BOOLEAN = Object.new
  def BOOLEAN.===(value)
    value == true || value == false
  end

  class MatchProc < Proc
    def ===(arg)
      call(arg)
    end
  end

  EQUAL = proc { |value|
    MatchProc.new { |test_value| test_value == value }
  }

  NOT_EQUAL = proc { |value|
    MatchProc.new { |test_value| test_value != value }
  }

  NOT_MATCH = proc { |value|
    MatchProc.new { |test_value| !(value === test_value) }
  }

  CHANGES = Object   # simpler!
  EXISTS = MatchProc.new {|v| v}

end

end