
class Team < Connector
  # some utility operations
  include TeamUtil
  # for logging purposes
  include Logging::NamedLog
  
  # all base proxies
  # "#{roleclass.name}=>#{baseclass.name}" -> proxy
  @@baseproxies = Hash.new

  
  # All deployment descriptors (array) this connector shall connect.
  attr :deployments
  # all role objects, created by this connector
  # role->bool
  attr :roles
  # all compound objects related to this connector
  # compund->role
  attr :compounds
  # remember all types, that play no role in this team => make lifting efficient
  attr :noroles
  # indicates activenes of this connector
  attr :active
  # indicates, if this connector is suspended
  attr :suspended
  # indicates, if this connector is weak => no role shall created implicitely
  attr_accessor :weak
  # id of connector - only used in to_s to identify different connector of same type
  attr :connectorid
  # a proc can be passed to the constructor, which will be used to create role
  # objects. This proc can be nil.
  attr :rolecreator

  # set alias to the question mark methods (bool)
  alias_method(:active?, :active)
  alias_method(:weak?, :weak)
  alias_method(:suspended?, :suspended)

  public #--------------------------------------------------------------------------------

  # Construct a dynamic connecor.
  # This connecor is usable during Team::while_active().
  # [deployments] all deployments, this connector should bind - nil will take
  #               the deployments, defined by the connector (default way).
  # [rolecreator] it is possible to pass a block to the constructor, which
  #               will be used to create role objects.
  def initialize(&rolecreator)
    init_log(self.class.name)
    debug("Connector created")
    @deployments = adjust_deployments(self.class.get_all_deployments())
    @compounds = ROTConfig::WEAKREFERENCE ? WeakHash.new : Hash.new
    @roles = ROTConfig::WEAKREFERENCE ? WeakHash.new : Hash.new
    @externalizedroles = ROTConfig::WEAKREFERENCE ? WeakHash.new : nil
    @noroles = Hash.new
    @active = false
    @suspended = false
    @weak = false
    @connectorid = Team.connectorID(self.class)
    @rolecreator = rolecreator
    #sort all deployments, so deployments of superclasses go first
    @deployments = @deployments.sort{ |a,b| (b.baseclass<=>a.baseclass or 0) }
  end

  def to_s()
    result = self.class.name+"-#{@connectorid}"
  end

  # Activates the connector, executes the given block and deactivates
  # afterwards.
  # [block] to execute. 
  def while_active()
    if (block_given?)
      begin
        activate()
        yield
      ensure
        deactivate()
      end
    end
  end

  # Make the connector active.
  # All deployments will be weaved into the specified classes.
  def activate()
    if (@deployments.nil?)
      if self.class.get_all_deployments().nil?
        raise ObjectTeam::TeamException, "No deployments given! - what to activate?", caller
      else
        raise ObjectTeam::TeamException, "Deployments not initialized! - Did you call super() during initialize??", caller
      end
    end
    @deployments.each { |deployment|
      info("deploy #{deployment.baseclass.name} -> #{deployment.roleclass.name}")
      #setup all weavings
      deploy_callins(deployment.baseclass, deployment.roleclass, deployment.callins)
    }
    #set activeness
    @active = true
  end

  # Make te connector inactive.
  # Remove all weavings, that are done during activate.
  def deactivate()
    @deployments.each { |deployment|
      deployment.baseclass.deactivate(self)
      deployment.baseclass.remove_role(self, deployment.roleclass)
    }
    #set activeness
    @active = false
  end

  # This method is used to obtain an externalized role of the given base object.
  # This method ensures the existence of the given role and the lifetime of
  # the role (the role will be garbage collected, if the given base object
  # gets finzlized. This can't happen, if there is at least one externalized
  # role of the given baseobject)
  # [baseobject] the base object the role is requested for
  # [return] the externalized role object
  def as_role(baseobject)
    role = lift(baseobject)
    raise(ObjectTeam::TeamException, "No role for #{baseobject}", caller) if role.nil?
    externrole = ObjectTeam::ExternalizedRole.new(self, role)
    @externalizedroles[externrole] = baseobject if (ROTConfig::WEAKREFERENCE)
    return externrole
  end

  # Lift the given parameter to its related role object, if this is a base
  # object. If not, the given paramter will be returned.
  # If an array is given, the whole array is lifted.
  def lift(param)
    result = nil
    if (param.instance_of?(Array))
      result = Array.new
      param.each { |p|
        result.push(lift_parameter(p))
      }
    elsif (param.instance_of?(ObjectTeam::ExternalizedRole))
      #CHECK: if this role is ours => unwrap
      result = (param.__connector==self ? param.__role : nil)
      #result = (param.__connector==self ? param.__role : lift_parameter(param.__base))
    else
      result = lift_parameter(param)
    end
    return result
  end

  # Lift exactly one parameter.
  # If the parameter's class is known by this connector it is
  # lifted to the known role-class. If the role class does not
  # exist, is is created.
  # [param] the paramter to lift.
  # [create] indicates, if a new role should be created, if param is player in this team and has no role
  # [return] the lifted paramter, if lifting is possible. Otherwise the parameter itself.
  def lift_parameter(param, create=true)
    result = nil
    #do we have a role for this baseobject?
    result = @compounds[param]
    if (result.nil?)
      #do we already searched for a role of this type?
      if (@noroles[param.class])
        result = param
      else
        #do we have a playedBy clause for the type of given object?
        roleclass = self.class.get_roleclass_of(param.class)
        if (!roleclass.nil?)
          #create role
          result = create_role(param, roleclass) if (create)
        else
          #remember for further lookups
          @noroles[param.class] = true
          #no lifting
          result = param
        end
      end
    end
    return result
  end

  # Lower the given parameter to its related base object, if this is a role
  # object. If not, the given paramter will be returned.
  # If an array is given, the whole array is lowered.
  def lower(param)
    result = nil
    if (param.instance_of?(Array))
      result = Array.new
      param.each { |p|
        result.push((@roles[p] ? p.__baseobject : p))
      }
    elsif (param.instance_of?(ObjectTeam::ExternalizedRole))
      result = (param.__connector==self ? param.__base : param)
    else
      result = ((@roles[param] ? param.__baseobject : param))
    end
    return result
  end

  # This method returns the roleobject of the given compound
  # related by this connector. This is useful for explicit lifting:
  #   myconnector[base].rolemethod(..)
  # The roleobject get's created, if it is not existent.
  # [compound] the base object
  # [return] the role object
  def [](compound)
    lift_parameter(compound, !@weak)
  end

  # This method suspends the team (Team freezing). All weaved action is
  # suspended until resume is called.
  def suspend()
    debug("Team suspended");
    @suspended = true;
  end

  # A suspended Team can be resumed. All weaved action takes place as any
  # ususal actiavted team.
  def resume()
    debug("Team resumed");
    @suspended = false;
  end

  protected #--------------------------------------------------------------------------------

  #Create role object.
  #Must match one deployment.
  # [compound] the compound the role wants to live in
  # [roleclass] the roleclass for the new role, nil if not known
  # [result] the role object or nil
  def create_role(compound, roleclass=nil)
    #initialize return value
    roleinstance = nil
    #lookup for roleclass, if not given
    roleclass = self.class.get_roleclass_of(compound.class) if (roleclass.nil?)
    #create an instance
    if (@rolecreator)
      #block is given
      roleinstance = @rolecreator.call(compound, roleclass)
    else
      #no block -> call default implementation
      roleinstance = create_roleobject(compound, roleclass)
    end
    if (roleinstance)
      #could be another class, than the roleclass above 
      roleclass = roleinstance.class
      debug("created role for #{compound.class.name} of type:  #{roleclass.name}")
      #is the Kernel.Role module included?
      if (!roleclass.included_modules.include?(ObjectTeam::Role))
        debug("include ObjectTeam::Role into #{roleclass.name}")
        roleclass.send("include", ObjectTeam::Role)
      end
      #init base call
      roleinstance.__basecall = nil
      #set a class-proxy as base
      proxy = get_base_proxy(roleclass, compound.class)
      roleinstance.__baseproxy = proxy
      #set base object
      roleinstance.__baseobject = compound
      #save the role object
      @roles[roleinstance] = true
      #remember relation compund -> roleinstance
      @compounds[compound] = roleinstance
      #call the user created constructor, if exists
      roleinstance.send("#{Interceptor::PREFIX}_initialize") if roleclass.private_instance_methods(false).include?("#{Interceptor::PREFIX}_initialize")
    end
    return roleinstance
  end

  # This method is called, whenever a role object must be created 
  # for the given compound. Override this method to fit custom behaviour. 
  # The default implementation simply return roleclass.new
  # (Perhaps your role objects don't have a default constructor, or it should
  #  be possible to manage different roles to the same type of base ...)
  # [compound] the base object, this role is created for.
  # [roleclass] the role class defined by the connector
  # [return] the role instance that matches the given base object.
  def create_roleobject(compound, roleclass)
    return roleclass.new
  end

  # Deploys all callins, given for a specific role.
  # [baseclass] the class all callins take place
  # [roleclass] the callins point to an instance of this class
  # [callins] array of callin-objects
  def deploy_callins(baseclass, roleclass, callins) 
    #iterate over all deployments
    callins.each { |callin|
      debug("setup call for #{baseclass.name}.#{callin.source} => #{roleclass.name}.#{callin.sink}")
      #honor callin renaming: 
      if (callin.mode==Mode::CALLIN and !baseclass.get_callin(callin.source).nil? )
        ot_assert(!baseclass.method_defined?(callin.source), "can't deploy callin #{callin.source} as normal callin - use replace!")
        callin = Callin.new(callin.source, callin.sink, Mode::REPLACE)
      end
      deploy_callin(baseclass, roleclass, callin)
    }

    #set roles as active in baseclass
    baseclass.add_role(self, roleclass)

    #shall we autoconnect??
    if (ROTConfig::AUTOCONNECT)
      #are there any methods left to connect? 
      while (roleclass != Object)
        expected_methods = Kernel.get_expected_methods(roleclass) #don't deploy expected methods!
        roleclass.public_instance_methods(false).each { |method|
          deplcallin = baseclass.get_callin(method)
          #is there an "unbound" rolemethod (which is not expected)?
          if ( (deplcallin.nil? or deplcallin.connector!=self) and 
               !expected_methods.include?(method) and 
               !baseclass.method_defined?(method)) 
            #mode of callin
            mode = nil
            #is this method a callin, defined by an other connector?
            if (!deplcallin.nil?)
              #the method is defined by an other connector!
              warn("Replace callin method #{baseclass.name}.#{method} from connector: #{baseclass.get_callin(method).connector.class.name}")
              mode = Mode::REPLACE
            else
              mode = Mode::CALLIN
            end
            #deploy the method 
            deploy_callin(baseclass, roleclass, Callin.new(method, method, mode), false)
          end
        }
        roleclass = roleclass.superclass
      end
    end
  end

  #Create a call object with the given callin descriptor.
  # register the call-object under the given name.
  def deploy_callin(baseclass, roleclass, callin, warnIfNoMethodFound=true)
    #is the calling role method valid?
    ot_assert(roleclass.method_defined?(callin.sink), "The rolemethod #{roleclass.name}.#{callin.sink} does not exist") # if (callin.sink!="initialize")
    #just a simple callin or a more complex call?
    if (callin.mode==Mode::CALLIN)
      #the methodname may not be used before!
      ot_assert(get_all_method_names(baseclass, callin.source).empty?, "Callin defined: >#{roleclass.name}.#{callin.source}<, but this method exists => use replace!")
      debug("Deploy callin CALLIN: #{baseclass.name}.#{callin.source} => #{callin.sink}")
      #create call object
      call = RoleCall.new(self, callin.sink, callin.source)
      #make the call available
      baseclass.set_callin(callin.source, call)
    else
      ###set the call-class##################################
      callclass = nil;
      case callin.mode
      when Mode::AFTER
        callclass = CallAfter
      when Mode::BEFORE
        callclass = CallBefore
      when Mode::REPLACE
        callclass = CallAround
      else
        error("Unknown Mode given in callin :#{callin.mode}")
        raise ObjectTeam::TeamException, "Unknown Mode given in callin :#{callin.mode}", caller
      end
      ###iterate over matching methods#######################
      methodnames = get_all_method_names(baseclass, callin.source)
      warn("No method matched by methodname: #{baseclass.name}.\'#{callin.source}\'") if (methodnames.empty? and warnIfNoMethodFound)
      methodnames.each { |methodname|
        debug("Deploy callin #{Mode.to_string(callin.mode)}: #{baseclass.name}.#{methodname} => #{callin.sink}")
        #the base call object
        base = baseclass.get_callin(methodname, true)
        call = callclass.new(self, callin.sink, callin.source, callin.parametermapping, base)
        #make the call available
        baseclass.set_callin(methodname, call)
      }
    end
  end

  
  # Return a baseproxy object, which will be called from roleclass-instances
  # that will callout to baseclass-instances. The baseproxy gets all the 
  # callout deployments necessary for these connection. Because the baseproxy
  # has no instance state, each connection is shared by the same proxy object.
  def get_base_proxy(roleclass, baseclass)
    debug("#{roleclass.name} => #{baseclass.name}")
    key = "#{roleclass.name}=>#{baseclass.name}"
    proxy = @@baseproxies[key]
    if (proxy.nil?) 
      debug("create BaseProxy for #{key}")
      #create proxy
      proxy = BaseProxy.new()
      klass = baseclass
      deployment = nil
      #climb until object
      while (klass != Object)
        #get deployment descriptor
        @deployments.each { |depl|
          if (depl.baseclass == klass)
            deployment = depl
            break;
          end
        }
        #set all callouts
        if (!deployment.nil?)
          deployment.callouts.each { |callout|
            proxy.add_callout_definition(callout)
          }
        end
        klass = klass.superclass
      end
      #give warning, if no deployment took place
      warn("#{baseclass.name}: no deployment found") if (deployment.nil?)
      #remember proxy
      @@baseproxies[key] = proxy
    end
    #given proxy is only a template
    proxy = proxy.clone
    proxy.connector = self
    return proxy
  end

  def adjust_deployments(deployments)
    result = Array.new
    deployments.each { |deployment|
      classname = deployment.roleclass.name.split(NamespaceRE)[-1]
      if (self.class.const_defined?(classname))
        roleclass = self.class.const_get(classname)
        if (roleclass != deployment.roleclass)
          #copy the deployment -> do not propagate changes
          info("deployment declared as #{deployment.roleclass.name} change to actual #{roleclass.name}")
          deployment = deployment.dup
          deployment.roleclass = roleclass
        end
      end
      result.push(deployment)
    }
    return result
  end

  # static stuff ==================================================================
  @@connectorids = Hash.new
  def Team.connectorID(classhandle)
    if (@@connectorids[classhandle].nil?)
      @@connectorids[classhandle] = 0
    else
      @@connectorids[classhandle] = @@connectorids[classhandle]+1
    end
    return @@connectorids[classhandle]
  end
  
  # Dumb help construct to support static connectors.
  # FIXME: better idea?
  def Team.activate_static(teamclass)
    #team = teamclass.new(teamclass.get_all_deployments)
    team = teamclass.new()
    team.activate
  end

  # realize automatic implicit inheritance =========================================
  def Team.inherited(teamklass)
    if(teamklass.superclass != Team)
      #Note: Since 27.09.2002 the ruby interpreter calls this method _AFTER_
      #the class has parsed. So the given classes are already defined!
      (teamklass.constants-Team.constants).each { |stringconst|
        if teamklass.const_get(stringconst).instance_of?(Class)
          raise(ObjectTeam::TeamException, "The implicit inheritance feature is not usable in this version of ruby!!\n Please use a patched version or a version prior 27.09.2002\n", caller) if teamklass.const_defined?(stringconst)
          teamklass.module_eval("class #{stringconst} < #{stringconst} \nend")
        end
      }
    end
  end
  
  # List all roles, defined in a subclass of this class.
  # [return] array of classes
  def Team.list_roles()
    result = []
    (self.constants-Team.constants).each { |stringconst|
      const = self.const_get(stringconst)
      result.push(const) if const.instance_of?(Class)
    }
    return result
  end
  
end


# Team.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.
