;;;;
;;;; s t k l o s . s t k    -- A variation of the Gregor Kickzales Tiny CLOS
;;;;			      for STk
;;;;
;;;; Copyright © 1993-1999 Erick Gallesio - I3S-CNRS/ESSI <eg@unice.fr>
;;;; 
;;;; Permission to use, copy, modify, distribute,and license this
;;;; software and its documentation for any purpose is hereby granted,
;;;; provided that existing copyright notices are retained in all
;;;; copies and that this notice is included verbatim in any
;;;; distributions.  No written agreement, license, or royalty fee is
;;;; required for any of the authorized uses.
;;;; This software is provided ``AS IS'' without express or implied
;;;; warranty.
;;;;
;;;;           Author: Erick Gallesio [eg@unice.fr]
;;;;    Creation date: 20-Feb-1994 21:09
;;;; Last file update: 27-Sep-1999 12:05 (eg)

(when (provided? "stklos") 
  (error "STklos already initialized."))

(%init-stklos)			  ; Initialize STklos
(with-module STk (import STklos)) ; Import the STklos module
(select-module STklos)		  ; Rest of file belongs to the STklos module
(export				  ; Define the exported symbols of this file
    find-class is-a?
    ensure-metaclass ensure-metaclass-with-supers
    define-class   make-class ensure-class
    define-generic make-generic-function ensure-generic-function
    define-method  ensure-method method add-method
    object-eqv? object-equal?
    write-object display-object Tk-write-object
    slot-unbound slot-missing 
    slot-definition-name  slot-definition-options slot-definition-allocation
    slot-definition-getter slot-definition-setter slot-definition-accessor
    slot-definition-init-form slot-definition-init-keyword 
    slot-init-function class-slot-definition
    method-body
    compute-get-n-set
    allocate-instance initialize make-instance make
    no-next-method  no-applicable-method no-method next-method-exists?
    change-class 
    shallow-clone deep-clone
    apply-generic apply-method apply-methods compute-applicable-methods 
    method-more-specific? sort-applicable-methods
    class-subclasses class-methods
    slot-value (setter slot-value)
)

;=============================================================================
;
;			      U t i l i t i e s
;
;=============================================================================

(define (mapappend func . args)
  (if (memv '()  args)
      '()
      (append (apply func (map car args))
	      (apply mapappend func (map cdr args)))))

(define (find-duplicate l)	; find a duplicate in a list; #f otherwise
  (cond 
    ((null? l)			#f)
    ((memv (car l) (cdr l))	(car l))
    (else 			(find-duplicate (cdr l)))))

;--------------------------------------------------
(define (set-symbol! symbol value env)
  (let ((module (%get-module env)))
    (eval `(define ,symbol ,value) (module-environment module))))

(define (lookup-symbol symbol env)
  (let ((module (%get-module env)))
    (eval symbol (module-environment module))))

(define make-closure eval)

;--------------------------------------------------
(define (specializers l)
  (cond
   ((null? l) '())
   ((pair? l) (cons (if (pair? (car l)) (cadar l) '<top>)
		    (specializers (cdr l))))
   (else      '<top>)))

(define (formals l)
  (if (pair? l)
      (cons (if (pair? (car l)) (caar l) (car l)) (formals (cdr l)))
      l))

;--------------------------------------------------
; 						[FIXME] This is too complicate
(define (%find-class name env . default)
  (if (symbol? name)
      ;; Search symbol in the globals of given env. (it must denote a class)
      (or (and (symbol-bound? name env)
	       (let ((cls (lookup-symbol name env)))
		 (and (is-a? cls <class>) cls)))
	  (if (null? default)
	      (error "find-class: class ~S not found" name)
	      (car default)))
      (if (is-a? name <class>) 
	  name
	  (error "find-class: bad-class ~S" name))))

;=============================================================================

;;
;; Find-class
;;
(define-macro (find-class name . default)
  (let ((%find-class (with-module STklos %find-class)))
    `(apply ,%find-class ,name ,(the-environment) ',default)))

;;
;; is-a?
;;
(define (is-a? obj class)
  (and (memv class (class-precedence-list (class-of obj))) #t))


;=============================================================================
;
; 			M e t a c l a s s e s   s t u f f
;
;=============================================================================

(define ensure-metaclass-with-supers
  (let ((table-of-metas '()))
    (lambda (meta-supers)
      (let ((entry (assoc meta-supers table-of-metas)))
	(if entry
	    ;; Found a previously created metaclass
	    (cdr entry)
	    ;; Create a new meta-class which inherit from "meta-supers"
	    (let ((new (make <class> :dsupers meta-supers
			             :slots   '()
				     :name   (gensym "metaclass"))))
	      (set! table-of-metas (cons (cons meta-supers new) table-of-metas))
	      new))))))

(define (ensure-metaclass supers env)
  (if (null? supers)
      (%find-class '<class> env)
      (let* ((all-metas (map (lambda (x)
			       (if (is-a? x <class>)
				   x
				   (class-of (%find-class x env))))
			     supers))
	     (all-cpls  (apply append
			       (map (lambda (m) (cdr (class-precedence-list m))) 
				    all-metas)))
	     (needed-metas '()))
	;; Find the most specific metaclasses.  The new metaclass will be
	;; a subclass of these.
	(for-each
	 (lambda (meta)
	   (when (and (not (member meta all-cpls)) (not (member meta needed-metas)))
	     (set! needed-metas (append needed-metas (list meta)))))
	 all-metas)
	;; Now return a subclass of the metaclasses we found.
	(if (null? (cdr needed-metas))
	    (car needed-metas)  ; If there's only one, just use it.
	    (ensure-metaclass-with-supers needed-metas)))))

;=============================================================================
;
; 			D e f i n e - c l a s s
;
;=============================================================================

;==== Define-class
(define-macro (define-class name supers slots . options)
  `(begin 
     (make-class ,name ,supers ,slots ,@options) 
     (make-undefined)))

;==== Make-class
(define-macro (make-class name supers slots . options)
  `(ensure-class
       ',name							; name
       ',supers							; supers
       ',slots							; slots 
       ,(or (get-keyword :metaclass options #f)			; metaclass
	    `(ensure-metaclass ',supers (the-environment)))
       (the-environment)					; environment
       ,@options))

;==== Ensure-class
(define (ensure-class name supers slots metaclass env . options)
  (let ((supers (if (null? supers) 
		    (list (%find-class '<object> env))
		    (map (lambda (x) (%find-class x env)) supers))))
    ;; Verify that all direct slots are different and that we don't inherit
    ;; several time from the same class
    (let ((tmp1 (find-duplicate supers))
	  (tmp2 (find-duplicate (map slot-definition-name slots))))
      (when tmp1
	(error "define-class: super class ~S is duplicate in class ~S" tmp1 name))
      (when tmp2
	(error "define-class: slot ~S is duplicate in class ~S" tmp2 name)))

    ;; Everything seems correct, build the class
    (let ((old (%find-class name env #f))
	  (cls (apply make metaclass :dsupers supers :slots slots 
		      :name name :environment env options)))
      (when old (class-redefinition old cls))
      (set-symbol! name cls env)
      cls)))

;=============================================================================
;
; 			D e f i n e - g e n e r i c
;
;=============================================================================

; ==== Define-generic
(define-macro (define-generic gf)
  `(begin
     (make-generic-function ,gf)
     (make-undefined)))
       

;==== Make-generic-function
(define-macro (make-generic-function gf)
  `(ensure-generic-function ',gf (the-environment)))

;==== Ensure-generic-function
(define (ensure-generic-function name env)
  (let* ((name      (extended-name->scheme-name name))
	 (old-value (and (symbol-bound? name env) 
;FIXME:			 (not (autoload? name module))
			 (lookup-symbol name env))))
    (set-symbol! name 
		 (make <generic> 
		       :name    name 
		       :default (and (procedure? old-value) old-value))
		 env)))
		 

;=============================================================================
;
; 			D e f i n e - m e t h o d
;
;=============================================================================

(define-macro (define-method name args . body)
  `(begin
     (ensure-method ',name
		    ',(formals args)
		    ',(specializers args)
		    ',body
		    (the-environment))
     (make-undefined)))

(define (ensure-method name formals specializers body env . kind-of-method)
  (let* ((name      (and name (extended-name->scheme-name name)))
	 (old-value (and name 
			 (symbol-bound? name env) 
;FIXME:			 (not (autoload? name module))
			 (lookup-symbol name env))))

    ;; See if a generic function must be defined for this method.
    (when (and name (not (is-a? old-value <generic>)))
      (ensure-generic-function name env))
    
    ;; Define the method and, eventually, add it to its generic
    (let ((method (make (if (null? kind-of-method) <method> (car kind-of-method))
			:specializers (map* (lambda (x) 
					      (if (symbol? x) 
						  (%find-class x env)
						  x))
					    specializers)
			:procedure (make-closure
				    `(lambda (next-method ,@formals) ,@body)
				    env))))
      (when name (add-method (lookup-symbol name env) method))
      ; Return the newly created method as result
      method)))

;====  Method

(define-macro (method args . body)
  `(begin
     (ensure-method #f
		    ',(formals args)
		    ',(specializers args)
		    ',body
		    (the-environment))))

;==== Add-method

(define (add-method-in-classes m)
  ;; Add method in all the classes which appears in its specializers list
  (for-each* (lambda (x)
	       (let ((dm (class-direct-methods x)))
		 (unless (memv m dm)
		   (slot-set! x 'direct-methods (cons m dm)))))
	     (method-specializers m)))

(define (remove-method-in-classes m)
  ;; Remove method in all the classes which appears in its specializers list
  (for-each* (lambda (x)
	       (slot-set! x 'direct-methods (remv m (class-direct-methods x))))
	      (method-specializers m)))

(define (compute-new-list-of-methods gf new)
  (let ((new-spec (method-specializers new))
	(methods  (generic-function-methods gf)))
    (let Loop ((l methods))
      (if (null? l)
	  (cons new methods)
	  (if (equal? (method-specializers (car l)) new-spec)
	      (begin 
		;; This spec. list already exists. Remove old method from dependents
		(remove-method-in-classes (car l))
		(set-car! l new) 
		methods)
	      (Loop (cdr l)))))))

;;
;; Add-method
;;
(define (add-method gf m)
  (slot-set! m  'generic-function gf)
  (slot-set! gf 'methods (compute-new-list-of-methods gf m))
  (add-method-in-classes m)
  m)

;=============================================================================
;
; 			      Access to Meta objects
;
; A lot of them are in C
;=============================================================================

;;;
;;; Methods
;;;
(define-method method-body ((m <method>))
  (let* ((spec (map class-name (slot-ref m 'specializers)))
	 (proc (procedure-body (slot-ref m 'procedure)))
	 (args (cdadr proc))
	 (body (cddr proc)))
    (list* 'method (map list args spec) body)))

;;;
;;; Slots
;;;
(define (slot-definition-name s)
  (if (pair? s) (car s) s))

(define (slot-definition-options s)
  (and (pair? s) (cdr s)))

(define (slot-definition-allocation s)
  (if (symbol? s)
      :instance
      (get-keyword :allocation (cdr s) :instance)))

(define (slot-definition-getter s)
  (and (pair? s) (get-keyword :getter (cdr s) #f)))

(define (slot-definition-setter s)
  (and (pair? s) (get-keyword :setter (cdr s) #f)))

(define (slot-definition-accessor s)
  (and (pair? s) (get-keyword :accessor (cdr s) #f)))

(define (slot-definition-init-form s)
  (if (pair? s) 
      (let* ((none (list '**none**))
	     (v1   (get-keyword :init-form (cdr s) none))
	     (v2   (get-keyword :initform  (cdr s) none))) ; Backward compatibility
	(if (eq? v1 none)
	    (if (eq? v2 none)
		(make-unbound)
		v2)
	    v1))
      (make-unbound)))

(define (slot-definition-init-keyword s)
  (and (pair? s) (get-keyword :init-keyword (cdr s) #f)))

(define (slot-init-function c s)
  (let ((s (slot-definition-name s)))
    (cadr (assoc s (slot-ref c 'getters-n-setters)))))

(define (class-slot-definition class slot-name)
  (assoc slot-name (class-slots class)))


;=============================================================================
;
; 			    Standard methods   
; 			used by the C runtime
;
;=============================================================================

;==== Methods to compare objects

(define-method object-eqv? (x y)    #f)
(define-method object-equal? (x y)  (eqv? x y))

;==== Methods to display/write an object

;     Code for writing objects must test that the slots they use are
;     bound. Otherwise a slot-unbound method will be called and will 
;     conduct to an infinite loop.

;; Write
(define-method write-object (o file)
  (format file "#[instance ~A]" (address-of o)))

(define-method write-object ((o <object>) file)
  (let ((class (class-of o)))
    (if (slot-bound? class 'name) 
	(format file "#[~A ~A]" (class-name class) (address-of o))
	(next-method))))

(define-method write-object((class <class>) file)
  (let ((meta (class-of class)))
    (if (and (slot-bound? class 'name) (slot-bound? meta 'name)) 
	(format file "#[~A ~A ~A]" (class-name meta) (class-name class)
				   (address-of class))
	(next-method))))

(define-method write-object((gf <generic>) file)
  (let ((meta (class-of gf)))
    (if (and (slot-bound? gf 'name) (slot-bound? meta 'name)
	     (slot-bound? gf 'methods))
	(format file "#[~A ~A (~A)]" (class-name meta)
				     (generic-function-name gf)
				     (length (generic-function-methods gf)))
	(next-method))))

;; Display (do the same thing as write by default)
(define-method display-object (o file) 
  (write-object o file))

;; Tk-write-object is called when a STklos object is passed to a Tk-command.
;; By default, we do the same job as write; but if an object is a <Tk-widget>
;; we will pass it its Eid. The method for <Tk-widget> is defined elsewhere.
(define-method Tk-write-object (o file)
  (write-object o file))

;==== Slot access

(define-method slot-unbound ((c <class>) (o <object>) s)
  (error "Slot `~S' is unbound in object ~S" s o))

(define-method slot-missing ((c <class>) (o <object>) s)
  (error "No slot with name `~S' in object ~S" s o))
  

(define-method slot-missing ((c <class>) (o <object>) s value)
  (slot-missing c o s))

; ==== Methods for the possible error we can encounter when calling a gf

(define-method no-next-method ((gf <generic>) args)
  (error "No next method when calling ~S\nwith ~S as argument" gf args))

(define-method no-applicable-method ((gf <generic>) args)
  (error "No applicable method for ~S\nin call ~S"
	 gf (cons (generic-function-name gf) args)))

(define-method no-method ((gf <generic>) args)
  (error "No method defined for ~S"  gf))


(define-macro (next-method-exists?)
  `((with-module STklos %next-method-exists?) next-method))

;=============================================================================
;
;		    Cloning functions (from rdeline@CS.CMU.EDU)
;
;=============================================================================

(define-method shallow-clone ((self <object>))
  (let ((clone (%allocate-instance (class-of self)))
	(slots (map slot-definition-name
		    (class-slots (class-of self)))))
    (for-each (lambda (slot)
		(if (slot-bound? self slot)
		    (slot-set! clone slot (slot-ref self slot))))
	      slots)
    clone))

(define-method deep-clone  ((self <object>))
  (let ((clone (%allocate-instance (class-of self)))
	(slots (map slot-definition-name
		    (class-slots (class-of self)))))
    (for-each (lambda (slot)
		(if (slot-bound? self slot)
		    (slot-set! clone slot
			       (let ((value (slot-ref self slot)))
				 (if (instance? value)
				     (deep-clone value)
				     value)))))
	      slots)
    clone))

;=============================================================================
;
; 		     	Class redefinition utilities
;;
;=============================================================================

;==== Class-redefinition

(define-method class-redefinition ((old <Class>) (new <Class>))
  ;; Work on direct methods:
  ;;		1. Remove accessor methods from the old class 
  ;;		2. Patch the occurences of old in the specializers by new
  ;;		3. Displace the methods from old to new
  (remove-class-accessors old)					;; -1-
  (let ((methods (class-direct-methods old)))
    (for-each (lambda (m) (update-direct-method m old new))	;; -2-
	      methods)
    (slot-set! new 'direct-methods methods))			;; -3-

  ;; Remove the old class from the direct-subclasses list of its super classes
  (for-each (lambda (c) (slot-set! c 'direct-subclasses 
				   (remv old (class-direct-subclasses c))))
	    (class-direct-supers old))

  ;; Redefine all the subclasses of old to take into account modification
  (for-each 
       (lambda (c) (update-direct-subclass c old new))
       (class-direct-subclasses old))

  ;; Invalidate class so that subsequent instances slot accesses invoke
  ;; change-object-class
  (slot-set! old 'redefined new))


;==== Remove-class-accessors

(define-method remove-class-accessors ((c <class>))
  (for-each 
     (lambda (m) (if (is-a? m <accessor-method>) (remove-method-in-classes m)))
     (class-direct-methods c)))


;==== Update-direct-method

(define-method update-direct-method ((m  <method>) (old <class>) (new <class>))
  (let Loop ((l (method-specializers m)))
    (when (pair? l)       	; Note: the <top> in dotted list is never used. 
      (if (eqv? (car l) old)  ; So we can work if we had only proper lists.
	(set-car! l new))
      (Loop (cdr l)))))

;==== Update-direct-subclass

(define-method update-direct-subclass ((c <class>) (old <Class>) (new <Class>))
  (let ((new-supers (map (lambda (cls) (if (eqv? cls old) new cls))
			 (class-direct-supers c))))
    ;; Create a new class with same name as c. This will automagically call 
    ;; class-redefinition on this subclass and redefine all its descent
    (ensure-class (class-name c)
		  new-supers
		  (class-direct-slots c)
		  (class-of c)
		  (slot-ref c 'environment))))

;=============================================================================
;
; 			Utilities for INITIALIZE methods
;
;=============================================================================

;;;
;;; compute-slot-accessors
;;;
(define (compute-slot-accessors class slots env)
  (for-each
      (lambda (s)
	(let ((name     (slot-definition-name     s))
	      (getter   (slot-definition-getter   s))
	      (setter   (slot-definition-setter   s))
	      (accessor (slot-definition-accessor s)))
	  (when getter
	    (ensure-method getter (list 'o) (list class)
			   `((slot-ref o ',name)) 
			   env <accessor-method>))
	  (when setter
	    (ensure-method setter (list 'o 'v) (list class <top>)
			   `((slot-set! o ',name v)) 
			   env <accessor-method>))
	  (when accessor
	    (ensure-method accessor (list 'o) (list class)
			   `((slot-ref o ',name)) 
			   env <accessor-method>)
	    (ensure-method `(setter ,accessor) (list 'o 'v) (list class <top>)
			   `((slot-set! o ',name v))
			   env <accessor-method>))))
      slots))

;;;
;;; compute-getters-n-setters
;;; 
(define (compute-getters-n-setters class slots env)

  (define (compute-slot-init-function s)
    (let ((init (slot-definition-init-form s)))
      (and (not (unbound? init)) (make-closure `(lambda () ,init) env))))

  (define (verify-accessors slot l)
    (if (pair? l)
	(let ((get (car l)) 
	      (set (cadr l)))
	  (unless (and (closure? get) (= (%procedure-arity get) 1))
	    (error "Bad getter closure for slot `~S' in ~S: ~S" slot class get))
	  (unless (and (closure? set) (= (%procedure-arity set) 2))
	    (error "Bad setter closure for slot `~S' in ~S: ~S" slot class set)))))

  (map (lambda (s)
	 (let* ((s     (if (pair? s) s (list s)))
		(g-n-s (compute-get-n-set class s))
		(name  (slot-definition-name s)))
	   ; For each slot we have '(name init-function getter setter)
	   ; If slot, we have the simplest form '(name init-function . index)
	   (verify-accessors name g-n-s)
	   (list* name (compute-slot-init-function s) g-n-s)))
       slots))

;;;
;;; compute-cpl
;;;
(define (compute-cpl class)
  
  (define (filter-cpl class)
    (let ((res  '()))
      (for-each (lambda (item)
		  (unless (or (eq? item <object>) 
			      (eq? item <top>) 
			      (member item res))
		   (set! res (cons item res))))
	      class)
      res))

  (let* ((supers   (slot-ref class 'direct-supers))
	 (big-list (apply append (cons class supers) (map compute-cpl supers))))
    (reverse (list* <top> <object> (filter-cpl big-list)))))

;;;
;;; Compute-get-n-set
;;;
(define-method compute-get-n-set ((class <class>) s)
  (case (slot-definition-allocation s)
    (:instance ;; Instance slot
               ;; get-n-set is just its offset
     	       (let ((already-allocated (slot-ref class 'nfields)))
		 (slot-set! class 'nfields (+ already-allocated 1))
		 already-allocated))

    (:class  ;; Class slot
             ;; Class-slots accessors are implemented as 2 closures around 
     	     ;; a Scheme variable. As instance slots, class slots must be
	     ;; unbound at init time. Since assignement to an unbound variable 
  	     ;; is not possible with our set! (in this case set! thinks that
	     ;; the variable has not been defined), our variable is in fact 
	     ;; a vector of length 1. This permits to circumvent this problem, 
             ;; without introducing a "set-environment" primitive.
             (let ((name (slot-definition-name s)))
	       (if (memq name (map slot-definition-name (class-direct-slots class)))
		   ;; This slot is direct; create a new shared cell
		   (let ((shared-cell (make-vector 1)))
		     (list (lambda (o)   (vector-ref shared-cell 0))
			   (lambda (o v) (vector-set! shared-cell 0 v))))
		   ;; Slot is inherited. Find its definition in superclass
		   (let Loop ((l (cdr (class-precedence-list class))))
		     (let ((r (assoc name (slot-ref (car l) 'getters-n-setters))))
		       (if r
			   (cddr r)
			   (Loop (cdr l))))))))

    (:each-subclass ;; slot shared by instances of direct subclass.
		    ;; (Thomas Buerger, April 1998)
     		    (let ((shared-cell (make-vector 1)))
		      (list (lambda (o)   (vector-ref  shared-cell 0))
			    (lambda (o v) (vector-set! shared-cell 0 v)))))

    (:virtual;; No allocation
     	     ;; slot-ref and slot-set! function must be given by the user
     	     (let ((get (get-keyword :slot-ref  (slot-definition-options s) #f))
		   (set (get-keyword :slot-set! (slot-definition-options s) #f))
		   (env (class-environment class)))
	       (unless (and get set)
		  (error "You must supply a :slot-ref and a :slot-set! in ~A" s))
	       (list (make-closure get env)
		     (make-closure set env))))
    (else    (next-method))))

(define-method compute-get-n-set ((o <object>) s)
  (error "Allocation \"~S\" is unknown" (slot-definition-allocation s)))

;=============================================================================
;
; 			    I n i t i a l i z e
;
;=============================================================================

(define-method initialize ((object <object>) initargs)
  (%initialize-object object initargs))

(define-method initialize ((class <class>) initargs)
  (next-method)
  (let ((dslots (get-keyword :slots	  initargs '()))
	(supers (get-keyword :dsupers	  initargs '()))
	(env    (get-keyword :environment initargs (global-environment))))

    (slot-set! class 'name	  	(get-keyword :name initargs '???))
    (slot-set! class 'direct-supers 	supers)
    (slot-set! class 'direct-slots  	dslots)
    (slot-set! class 'direct-subclasses '())
    (slot-set! class 'direct-methods    '())
    (slot-set! class 'cpl		(compute-cpl class))
    (slot-set! class 'redefined		#f)
    (slot-set! class 'environment	env)
    (let ((slots (%compute-slots class)))
      (slot-set! class 'slots	  	  slots)
      (slot-set! class 'nfields	  	  0)
      (slot-set! class 'getters-n-setters (compute-getters-n-setters class 
								     slots 
								     env)))
    ;; Update the "direct-subclasses" of each inherited classes
    (for-each (lambda (x)
		(slot-set! x 'direct-subclasses 
			   (cons class (slot-ref x 'direct-subclasses))))
	      supers)

    ;; Build getters - setters - accessors
    (compute-slot-accessors class dslots env)))


(define-method initialize ((generic <generic>) initargs)
  (let ((previous-definition (get-keyword :default initargs #f)))
    (next-method)
    (slot-set! generic 'name    (get-keyword :name initargs '???))
    (slot-set! generic 'methods (if (is-a? previous-definition <procedure>)
				    (list (make <method>
						:specializers <top>
						:procedure
						    (lambda (nm . l)
						      (apply previous-definition 
							     l))))
				    '()))))

(define-method initialize ((method <method>) initargs)
  (next-method)
  (slot-set! method 'generic-function (get-keyword :generic-function initargs #f))
  (slot-set! method 'specializers (get-keyword :specializers initargs '()))
  (slot-set! method 'procedure (get-keyword :procedure initargs (lambda l '()))))


;=============================================================================
;
; 			C h a n g e - c l a s s
;
;=============================================================================

(define (change-object-class old-instance old-class new-class)
  (let ((new-instance (allocate-instance new-class ())))
    ;; Initalize the slot of the new instance
    (for-each (lambda (slot)
		(if (slot-exists-using-class? old-class old-instance slot)
		    ;; Slot was present in old instance; set it 
		    (if (slot-bound-using-class? old-class old-instance slot)
			(slot-set-using-class!
			     new-class 
			     new-instance 
			     slot 
			     (slot-ref-using-class old-class old-instance slot)))
		    ;; slot was absent; initialize it with its default value
		    (let ((init (slot-init-function new-class slot)))
		      (if init
			  (slot-set-using-class!
			       new-class 
			       new-instance 
			       slot
			       (apply init '()))))))
	      (map slot-definition-name (class-slots new-class)))
    ;; Exchange old an new instance in place to keep pointers valids
    (%modify-instance old-instance new-instance)
    old-instance))


(define-method change-class ((old-instance <object>) (new-class <class>))
  (change-object-class old-instance (class-of old-instance) new-class))

;=============================================================================
;
; 				     M a k e 
;
;     A new definition which overwrite the previous one which was built-in
;
;=============================================================================

(define-method allocate-instance ((class <class>) initargs)
  (%allocate-instance class))

(define-method make-instance ((class <class>) . initargs)
  (let ((instance (allocate-instance class initargs)))
    (initialize instance initargs)
    instance))

(define make make-instance)

;=============================================================================
;
;				a p p l y - g e n e r i c
;
; Protocol for calling standard generic functions.
; This protocol is  not used for real <generic> functions (in this case we use
; a completely C hard-coded protocol). 
; Apply-generic is used by STklos for calls to subclasses of <generic>.
;
; The code below is similar to the first MOP described in AMOP. In particular,
; it doesn't used the currified approach to gf call. There are 2 reasons for 
; that: 
;   - the protocol below is exposed to mimic completely the one written in C
;   - the currified protocol would be imho inefficient in C.
;=============================================================================

(define-method apply-generic ((gf <generic>) args)
  (if (null? (slot-ref gf 'methods))
      (no-method gf args))
  (let ((methods (compute-applicable-methods gf args)))
    (if methods
	(apply-methods gf (sort-applicable-methods gf methods args) args)
	(no-applicable-method gf args))))

(define-method compute-applicable-methods ((gf <generic>) args)
  (apply find-method gf args))

(define-method sort-applicable-methods ((gf <generic>) methods args)
  (let ((targs (map class-of args)))
    (sort methods (lambda (m1 m2) (method-more-specific? m1 m2 targs)))))

(define-method method-more-specific? ((m1 <method>) (m2 <method>) targs)
  (%method-more-specific? m1 m2 targs))

(define-method apply-method ((gf <generic>) methods build-next args)
  (apply (method-procedure (car methods))
	 (build-next (cdr methods) args)
	 args))

(define-method apply-methods ((gf <generic>) (l <list>) args)
  (letrec ((next (lambda (procs args)
		   (lambda new-args
		     (let ((a (if (null? new-args) args new-args)))
		       (if (null? procs)
			   (no-next-method gf a)
			   (apply-method gf procs next a)))))))
    (apply-method gf l next args)))

;=============================================================================
;
;		     <Composite-metaclass> and <Active-metaclass>
; 
;=============================================================================

(autoload "active-slot"    <Active-metaclass>)
(autoload "composite-slot" <Composite-metaclass>)
(export <Composite-metaclass> <Active-metaclass>)

;=============================================================================
;
; 				T o o l s
;
;=============================================================================

(define (list2set l)			;; duplicate the standard list->set
  (let Loop ((l l) (res '()))		;; function but using eq instead of eqv
    (cond				;; which really sucks a lot, uselessly here
     ((null? l) 	 res)
     ((memq (car l) res) (Loop (cdr l) res))
     (else		 (Loop (cdr l) (cons (car l) res))))))


(define (class-subclasses c)
  (letrec ((allsubs (lambda (c)
		      (cons c (mapappend allsubs (class-direct-subclasses c))))))
    (list2set (cdr (allsubs c)))))

(define (class-methods c)
  (list2set (mapappend class-direct-methods (cons c (class-subclasses c)))))

;;
;; Clos like SLOT-VALUE 
;; Note: SLOT-VALUE is a gf whereas SLOT-REF and SLOT-SET! are functions.
;;
(define-method slot-value ((o <object>) s)
  (slot-ref o s))

(define-method (setter slot-value) ((o <object>) s v)
  (slot-set! o s v))

;=============================================================================
;
; 			     Backward compatibility
;
;=============================================================================
(define class-cpl 		 class-precedence-list)		; Don' use these
(define get-slot-allocation	 slot-definition-allocation)	; obolete defs
(define slot-definition-initform slot-definition-init-form)	; anymore

(export class-cpl get-slot-allocation slot-definition-initform)

(provide "stklos")

