module QED

  # Applique is a module built per-script from the +applique+ dirctory.
  # Applique scripts are loaded at the start of a session.
  #
  # <i>The Applique</i> is whole collection of applique that apply to given
  # demonstrandum. The applique that apply are the scripts located in the
  # directory relative to the demonstrandum script and all such directories
  # above this upto and the project's root directory.
  #
  # All scripts in the Applique must be compatible/consistant. For two demos to
  # have separate applique must be kept in separate directores.
  #
  class Applique < Module

    # Load cache.
    def self.cache
      @cache ||= {}
    end

    class << self
      alias_method :_new, :new
    end

    # New method caches Applique based-on +file+, if given.
    #--
    # TODO: may need to expand file to be absolute path
    #++
    def self.new(file=nil)
      if file
        cache[file] ||= _new(file)
      else
        _new(file)
      end
    end

    #
    def initialize(file=nil)
      super()
      extend self

      @__matchers__ = []
      @__signals__  = {}

      if file
        @file = file
        module_eval(File.read(file), file)
      end
    end

    #
    def initialize_copy(other)
      @__matchers__ = other.__matchers__.dup
      @__signals__  = other.__signals__.dup
    end

    # Array of matchers.
    attr :__matchers__

    # Hash of signals.
    attr :__signals__

    # Pattern matchers and "upon" events.
    def When(*patterns, &procedure)
      if patterns.size == 1 && Symbol === patterns.first
        type = "#{patterns.first}".to_sym
        @__signals__[type] = procedure
        #define_method(type, &procedure)
      else
        @__matchers__ << [patterns, procedure]
      end
    end

    # Before advice.
    def Before(type=:code, &procedure)
      type = "before_#{type}".to_sym
      @__signals__[type] = procedure
      #define_method(type, &procedure)
    end

    # After advice.
    def After(type=:code, &procedure)
      type = "after_#{type}".to_sym
      @__signals__[type] = procedure
      #define_method(type, &procedure)
    end

    # Code match-and-transform procedure.
    #
    # This is useful to transform human readable code examples
    # into proper exectuable code. For example, say you want to
    # run shell code, but want to make if look like typical
    # shelle examples:
    #
    #    $ cp fixture/a.rb fixture/b.rb
    #
    # You can use a transform to convert lines starting with '$'
    # into executable Ruby using #system.
    #
    #    system('cp fixture/a.rb fixture/b.rb')
    #
    #def Transform(pattern=nil, &procedure)
    #
    #end

    # Redirect missing constants to Object class 
    # to simulate TOPLEVEL.
    #
    # TODO: Clean backtrace when constant is not found.
    def const_missing(name)
      Object.const_get(name)
    end

  end

end