

module ObjectTeam

  class AbstractMethodCall < StandardError
  end

  class TeamException < StandardError
  end

  # Regexp to match weaved methods inside call stack
  Caller_RE = /^[^:]+:\d+:in\s`((call)|(call_role)|(call_method)|(method_missing)|(get_callin)|(send)|(#{Interceptor::PREFIX}.+))'$/


  # This module implements delegating functionality.
  # Any expected method will be forwarded to handle_expected
  # and delegated to base. Therefore it is important, to call
  # set_base during initialization.
  module Role
    # The receiver of the message queue (delegate). 
    attr_accessor :__baseproxy
    # the basecall-object. only valid during a replacement call.
    attr_accessor :__basecall

    # Indicates, if we have a valid basecall.
    def has_base?
      return (not @__basecall.nil?)
    end
    
    # Call base method of current executed replaced method.
    # If the current method is not an replaced method, this 
    # will throw an exexption!
    def base(*parameter, &block)
      if (@__basecall.nil?)
        raise ObjectTeam::TeamException, "No replacement-method!\n base()-calls are only valid via a replacement callin", caller
      end
      return @__basecall.call_base(*parameter, &block)
    end
    
    # Get current associated connector. 
    # The connector should be used only to suspend/resume the team.
    def get_connector()
      return @__baseproxy.connector
    end

    def myTeam()
      nestedstructure = self.class.name.split(/::/)
      nestedstructure.pop
      klass = Object
      nestedstructure.each { |nestlevel|
        klass = klass.const_get(nestlevel)
      }
      return klass
    end

    if (ROTConfig::WEAKREFERENCE)
      def __baseobject()
        @baseobject.ref
      end
      def __baseobject=(base)
        @baseobject = WeakReference.new(base)
      end
    else
      attr_accessor :__baseobject
    end

  end

  # This module enhances including classes to register/unregister
  # Callin-Bindings that will be handled as additional methods.
  module Compound
    # classname -> {methodname -> call-object}
    # Callins[Class] = ClassHash => ClassHash[String] = Call
    Callins=Hash.new

    # classname -> {roleclass -> [connectors]}
    # Roles[Class] = RoleHash => RoleHash[RoleClass] = Array of connectors
    Roles=Hash.new

    # Get a call object to the given methodname.
    # If no callin exists, take the real method, whose name is {Interceptor::PREFIX}#{classname}_method
    #   [methodname] the methodname as string
    #   [wrapmethod] indicates, if no callin is found, the specified method
    #                should be wrapped by a MethodCallin object
    #   [return] a Call-Object
    def get_callin(methodname, wrapmethod=false)
      #search for callins, starting by self until Object
      aclass = self
      acallin = nil
      #climb until object
      while (aclass!=Object and acallin.nil?)
        classcallins = Callins[aclass]
        if (!classcallins.nil? and !classcallins[methodname].nil?)
          acallin = classcallins[methodname]
        end
        aclass = aclass.superclass
      end
      
      #found no callin? - shall we generate a method wrapper
      if (acallin.nil? and wrapmethod)
        #climb until method is defined
        aclass = self
        while (aclass!=Object and acallin.nil?)
          if (self.method_defined?(Interceptor::PREFIX+aclass.name+"_"+methodname))
            acallin = MethodCall.new(Interceptor::PREFIX+aclass.name+"_"+methodname) 
          end
          aclass = aclass.superclass
        end
        raise(NoMethodError, "undefined method `#{methodname}' for #{self.inspect}", caller) if (acallin.nil?)
      end
      #could be nil
      return acallin
    end

    # Declare a given Call-object under a given name.
    # If a method with the given name is called, this call object is used.
    #   [methodname] the method name as string
    #   [callin] the call object
    def set_callin(methodname, callin)
      classcallins = Callins[self]
      #ensure validness, because of lazy init
      if (classcallins.nil?)
        classcallins = Hash.new
        Callins[self] = classcallins
        Callins.rehash
      end
      #set 
      classcallins[methodname] = callin
      classcallins.rehash
    end

    # Deactivate all callins, that are active for the given connector.
    # Only the first level of callins must be searched, because 
    # activation/deactivation happens in a ordered manner.
    def deactivate(connector)
      classcallins = Callins[self]
      if (!classcallins.nil?)
        classcallins.each { |name, callin|
          basecallin = callin.get_base_of(connector)
          if (basecallin.nil? or !basecallin.is_a?(RoleCall))
            classcallins.delete(name)
          else
            #normal behaviour: actual callin is bound by the connector => basecallin!=actualcallin
            actualcallin = classcallins[name]
            if (actualcallin!=basecallin)
              #set basecall as callin for name
              classcallins[name] = basecallin
            else
              #walk through queue - ensure all call objects are deleted
              flag = true
              while(actualcallin.is_a?(CallWeaved) and flag)
                  savecallin = actualcallin
                  actualcallin = actualcallin.basecall
                  if (actualcallin.is_a?(RoleCall) and actualcallin.connector==connector)
                    if (actualcallin.instance_of?(RoleCall))
                      #set null operation
                      savecallin.basecall = NoCall.new
                    else #CallWeaved
                      #found callobject of connector -> connect savecallin with actualcallinbase
                      savecallin.basecall=actualcallin.get_base_of(connector)
                    end
                    flag = false
                  end
              end
            end
          end
        }
        classcallins.rehash
      end
    end

    # Get all callins, that are registered for this class (self).
    # [includeSuper] indicates, if all callins of superclass shoulb be included.
    # [return] hash (basemethodname -> call)
    def callins(includeSuper=false)
      result = nil
      if (!includeSuper)
        result = (Callins[self] or Hash.new)
      else
        result = Hash.new
        klass = self
        while (klass!=Object)
          classcallins = Callins[klass]
          if (!classcallins.nil?)
            classcallins.each { |name,call|
              if (!result.has_key?(name))
                result[name] = call
              end
            }
          end
          klass = klass.superclass
        end
      end
      return result
    end

    # Decalre a given class as roleclass of this class. This is usually done
    # by a connector. The same role can more than once activated for the same
    # class. 
    # [connector] the connector, that binds the base to this role.
    # [roleclass] the role that this base class plays.
    def add_role(connector, roleclass)
      #get all roles of this class
      classroles = Roles[self]
      if (classroles.nil?)
        classroles = Hash.new
        Roles[self] = classroles
        Roles.rehash
      end
      #get all current active connectors
      connectors = classroles[roleclass]
      if (connectors.nil?)
        connectors = Array.new
        classroles[roleclass] = connectors
        classroles.rehash
      end
      #add connector to list
      connectors.push(connector)
    end

    # Remove a given role from this baseclass. Since roles can be activated
    # more than once, has_role? can return true after calling remove_role (if
    # more than one activation is active). If the last activation is removed,
    # the roleclass is removed too.
    # [connector] the connector, that binds the base to this role.
    # [roleclass] the role that this base class plays.
    def remove_role(connector, roleclass)
      #get all roles of this class
      classroles = Roles[self]
      if (!classroles.nil?)
        #get all current active connectors
        connectors = classroles[roleclass]
        if (!connectors.nil?)
          if (connectors.length == 1)
            #last active connector -> remove
            classroles.delete(roleclass)
            classroles.rehash
          else (!connectors.nil?)
            #delete the active connector
            connectors.delete(connector)
          end
        end
      end
    end

    # Similar to instance_of? for objects, a class can be asked, if it is
    # playing a role inside a team.
    # [roleclass] the roleclass to ask for.
    # [return] true, if this class plays the given role - otherwise false.
    def has_role?(roleclass)
      #get all roles of this class
      classroles = Roles[self]
      result = false
      if (!classroles.nil?) 
        if (!classroles[roleclass].nil?)
          result = true
        else
          classroles.each { |role, connectors|
            #is roleclass a superclass of role?
            result = (role<roleclass)
            break if result
          }
        end
      end
      return result
    end

    # Get all roles, this class has.
    # roleclass -> array of binding connectors
    def roles()
      return (Roles[self] or Hash.new)
    end

    # Extended version of Class.name. This returns the name with all rolenames
    # appended as "as" clauses. Eg: "Window as ObserverPattern::Observer"
    def full_name()
      result = self.name
      classroles = Roles[self]
      if (!classroles.nil?)
        classroles.each { |role, connectors|
          result += " as "+ role.name + (connectors.length>0 ? "(#{connectors.length})" : "")
        }
      end
      return result
    end

    # Write a little status report to stdout. This is most useful for debugging purposes.
    # The status displays all callin bindings for this class and all super classes.
    def status(formatted=false)
      klass = self
      stack = Array.new
      while (klass!=Object)
        stack.push(klass)
        klass = klass.superclass
      end
      puts "\nCallin definition of #{self.name}: "+("="*(57-klass.name.length))+"\n"
      stack.reverse.each { |klass|
        puts "+#{klass.name}:"
        klass.callins.sort.each { |method, call|
          if (formatted)
            puts " -#{method}:\n"+call.to_string
          else
            puts "  -#{method} -> #{call.to_s} (#{call.connector.class.name})"
          end
        }
      }
      puts "================================================================================\n"
    end
  end

  # This module just handles all calls, that are not known to the class.
  # All such methods will be handled by the dispatcher-method.
  # A missing method could be a callin binding - or a really missing method.
  module Missing
    # This method is called, whenever a method is not found
    def method_missing(method, *params, &block)
      methodname = method.id2name
      retval = nil
      begin
        #do we have a callin registered?
        acallin = self.class.get_callin(methodname)
        if (!acallin.nil?)
          #simply call
          retval = acallin.call(self, *params, &block)
        else
          raise(NoMethodError, "undefined method `#{methodname}' for #{self.inspect}", caller) 
        end
      rescue Exception => ex
        if ($DEBUG)
          #throw original backtrace
          raise ex.class, ex.message, ex.backtrace
        else
          #set new backtrace, without annoying weaved - trace
          newtrace = Array.new
          ex.backtrace.each { |trace|
            if (!ObjectTeam::Caller_RE.match(trace))
              newtrace.push(trace)
            end
          }
          ex.set_backtrace(newtrace)
          raise ex.class, ex.message, newtrace
        end
      end
      return retval
    end
  end

  # This class represents an externalized Role.
  # All methods on an externalized role must be involved into the
  # lifting/lowering process.
  class ExternalizedRole
    
    # Instance of this class are created from the class Team, if an
    # externalized role is requested.
    def initialize(connector, role)
      @connector = connector
      @role = role
    end
    
    # This method handles all method to the role:
    # All attributes get lifted to their roles.
    # The result is lowered to the base object.
    def method_missing(method, *params, &block)
      newparms = @connector.lift(params) 
      result = @role.send(method.id2name, *newparms, &block)
      result = @connector.lower(result)
      return result
    end

    # Return the encapsulated connector.
    def __connector()
      return @connector
    end
    
    # Return the encapsulated role.
    def __role()
      return @role
    end

    # Return the encapsulated base.
    def __base()
      return @role.__baseobject
    end
  end

end

# Enrich each object for a method_missing functionality.
# If a method-call is not found, this method is called and forwarded
# to the dispatch method inside class. If no callin is registered, 
# an exceptionis thrown.
class Object
  include ObjectTeam::Missing
end

# Enrich each Class for the possibility to register and unregister 
# call-objects for a specific name. This mechanism emulates a dynamic
# method call under a certain name.
class Class
  include ObjectTeam::Compound
end

# Enrich Kernel for an assert method.
def ot_assert(bool, faildescription=nil)
  if (!bool)
    raise ObjectTeam::TeamException, (faildescription or "Assertion failed!"), caller
  end
end

# ObjectTeam.rb   April 2002
#
# Copyright (c) 2002 by Matthias Veit <matthias_veit@yahoo.de>
# 
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2, or (at your option) 
# any later version.
#  
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of 
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the  
# GNU General Public License for more details.
#  
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  
# 02111-1307, USA.
