;;;;
;;;; object.stk -- -- A variation of the Gregor Kickzales Tiny CLOS for STklos
;;;;
;;;; Copyright © 1993-2024 Erick Gallesio <eg@stklos.net>
;;;;
;;;;
;;;; 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 of the License, 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.
;;;;
;;;;           Author: Erick Gallesio [eg@unice.fr]
;;;;    Creation date: 20-Feb-1994 21:09


#|
//FIXMOI

* decoupage du source
* methods rapides
* Vérification que tous les points du mop sont implémentés

|#

(define-module STKLOS-OBJECT
  (import SCHEME)
  (export                                 ; Define the exported symbols of this file
    find-class is-a?
    ensure-metaclass ensure-metaclass-with-supers
    ensure-class
    ensure-generic-function
    ensure-method add-method!
    object-eqv? object-equal?
    write-object display-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
    compute-get-n-set
    allocate-instance initialize-instance initialize make-instance make
    no-next-method  no-applicable-method no-method ;; next-method-exists?
    change-class change-object-class
    shallow-clone deep-clone
    apply-generic apply-method apply-methods compute-applicable-methods
    method-more-specific? sort-applicable-methods
    method-procedure method-specializers method-generic-function
    method-specializers-equal?
    class-subclasses class-methods class-name class-direct-superclasses
    class-direct-subclasses class-precedence-list class-direct-methods
    class-direct-slots class-slots
    generic-function-name generic-function-methods
    generic-function-documentation
    slot-value

    ;; Syntaxes
    define-class
    define-generic
    method
    define-method
    )


(define extended-lambda->lambda         ;; Compiler function used here
  (in-module STKLOS-COMPILER extended-lambda->lambda))


(define class-redefinition (void))      ;; forward declaration

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

(define (%error-bad-class who obj)   (error who "bad class ~S" obj))
(define (%error-bad-generic who obj) (error who "bad generic function ~S" obj))
(define (%error-bad-method who obj)  (error who "bad method ~S" obj))

(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))


(define (declare-slots slots)
  ;; This is a *HUGE* *HACK*. Since STklos compiler displays the
  ;; unknown symbols, slots accessors are not known at compilation
  ;; time and yield a message. So, we pre-declare them here.
  ;; In the abolute this code is false, since the keywords :accessor
  ;; :getter and :setter can have a different meaning than the usual
  ;; one for a peculiar meta-class. Anyway, the only risk is to
  ;; trust that a symbol is defined whereas it is not.
  (for-each (lambda (s)
              (let ((getter   (slot-definition-getter   s))
                    (setter   (slot-definition-setter   s))
                    (accessor (slot-definition-accessor s)))
                (when getter   (register-new-global! getter))
                (when setter   (register-new-global! setter))
                (when accessor (register-new-global! accessor))))
            slots)
  slots)


;--------------------------------------------------
(define (make class . l)        ;; A temporary version used for bootstrapping
  (cond
    ((eq? class <generic>) (%make class
                                  'generic
                                  (key-get l :name '???)))
    ((eq? class <method>) (%make class
                                  'method
                                  (list (key-get l :generic-function #f)
                                        (key-get l :specializers     '())
                                        (key-get l :procedure        '()))))
    (else                   (error 'basic-make "cannot make ~S with ~S" class l))))

;=============================================================================
;
;                             Access to Meta objects
;
;=============================================================================

;// ;;;
;// ;;; 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)))
;//

;;;
;;; Classes
;;;
#|
<doc EXT class-name
 * (class-name c)
 *
 * Returns the name of the class |c|,a s a symbol.
 *
 * @lisp
 * (define-class <A> () ())
 * (define x (make <A>))
 * (class-name (class-of x)) => <A>
 * @end lisp
 *
doc>
|#
(define (class-name C)
  (if (class? C) (slot-ref C 'name) (%error-bad-class 'class-name C)))

#|
<doc EXT class-direct-superclasses class-direct-subclasses class-subclasses class-precedence-list
 * (class-direct-superclasses c)
 * (class-direct-subclasses c)
 * (class-subclasses c)
 * (class-precedence-list c)
 *
 * |class-direct-superclasses| and |class-direct-subclasses| returns a list with
 * all direct superclasses or subclasses of class |c|.
 *
 * |class-subclasses| returns a list with all subclasses of class |c|, even if
 * not direct.
 *
 * |class-precedence-list| returns the precedence list for class |c|. If
 * |c| is a superclass of several classes, this list shows what precedence
 * each superlclass has when resolving names of attributes and methods in
 * |c|.
 *
 * The items returned in the list are classes. Use |class-name| to obtain
 * their names.
 *
 * @lisp
 * (define-class <A> () (slot-one slot-two))
 * (define-class <B> () ())
 * (define-class <C> (<A> <B>) (slot-one))
 * (define-class <D> (<A>) ())
 *
 * (map class-name (class-direct-superclasses <C>)) => (<A> <B>)
 * (map class-name (class-direct-superclasses <D>)) => (<A>)
 * (map class-name (class-direct-subclasses <A>))   => (<D> <C>)
 * (map class-name (class-precedence-list <C>))     => (<C> <A> <B> <object> <top>)
 * @end lisp
 *
 * |Slot-one| in class |<C>| refers to the slot defined in |<C>| (because
 * it is present in both |<A>| and |<C>|, but |<C>| has precedence over
 * |<A>|). |Slot-two| in |<C>| can only be the one defined in |<A>|.
 *
 * The native STklos types are also subclasses of one single class, |<top>|.
 *
 * @lisp
 *
 * (map class-name  (class-subclasses <top>))
 *         => (<entity-class> <procedure-class> <class> <accessor-method>
 *             <simple-method> <method> <generic> <object> <boolean> <char>
 *             ...
 *             <number>
 *             ...
 *             <output-port> <port> <hash-table>)
 * @end lisp
 * One of the listed classes above is |<number>| (its subclasses are also
 * listed in that output). Calling |class-subclasses| on |<number>| returns
 * its six subclasses:
 * @lisp
 * (map class-name (class-subclasses <number>))
 *         => (map class-name (class-subclasses <number>))
 * @end lisp
doc>
|#




#|
<doc EXT class-slots class-direct-slots
 * (class-slots c)
 * (class-direct-slots c)
 *
 * |Class-slots| returns a list of the slots in class |c|,  including
 * those inherited from other classes.
 *
 * @lisp
 * (class-slots <A>) => ((a #:init-form 1) (b #:init-form 2))
 * (class-slots <C>) => ((b #:init-form 2) (a #:init-form 3))
 * @end lisp
 *
 * |Class-direct-slots| returns a list with the names of the slots that
 * were defined directly in the class (excluding the inherited slots).
 *
 * @lisp
 * (class-direct-slots <B>) => ((a #:init-form 3))
 * @end lisp
doc>
<doc class-methods class-direct-methods
 * (class-methods c)
 * (class-direct-methods c)
 *
 * |Class-direct-methods| returns a list of all direct methods of
 * a class.
 *
 * |Class-methods| returns a list of all methods of a class and also
 * of its subclasses.
 *
 * @lisp
 * (define-class <A> () ())
 *
 * (define-method method-A ((a <A>)) (print 'A))
 * (define-class <C> (<A>) ())
 * (define-method method-C ((c <C>)) (print 'C))
 * (class-methods <C>)
 *
 * (map method-generic-function (class-methods <A>))
 *     => (#[<generic> method-C (1)]
 *         #[<generic> method-A (1)])
 * (map method-generic-function (class-direct-methods <A>))
 *     => (#[<generic> method-A (1)])
 * @end lisp
doc>
|#
(define (class-direct-superclasses C)
  (if (class? C)
      (slot-ref C 'direct-supers)
      (%error-bad-class 'class-direct-superclasses C)))

(define (class-direct-slots C)
  (if (class? C)
      (slot-ref C 'direct-slots)
      (%error-bad-class 'class-direct-slots C)))

(define (class-direct-subclasses C)
  (if (class? C)
      (slot-ref C 'direct-subclasses)
      (%error-bad-class 'class-direct-subclasses C)))

(define (class-direct-methods C)
  (if (class? C)
      (slot-ref C 'direct-methods)
      (%error-bad-class 'class-direct-methods C)))

(define (class-precedence-list C)
  (if (class? C)
      (slot-ref C 'cpl)
      (%error-bad-class 'class-precedence-list C)))

(define (class-slots C)
  (if (class? C)
      (slot-ref C 'slots)
      (%error-bad-class 'class-slots C)))

;;;
;;; Slots
;;;
#|
<doc EXT 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-definition-name s)
 * (slot-definition-options s)
 * (slot-definition-allocation s)
 * (slot-definition-getter s)
 * (slot-definition-setter s)
 * (slot-definition-accessor s)
 * (slot-definition-init-form s)
 * (slot-definition-init-keyword s)
 *
 * These procedures operate on slot definitions (the lists included in class
 * definitions). They return the slot name, the list of options, the slot
 * allocation, and the getter, setter, accessor, init-form and init-keyword
 * of a slot, as returned by |class-slots| or by |class-slot-definition|.
 *
 * @lisp
 * (slot-definition-name '(x #:init-form 0  #:accessor x))       => x
 * (slot-definition-accessor '(x #:init-form 0  #:accessor x))   => x
 * (slot-definition-allocation '(x #:init-form 0  #:accessor x)) => instance
 * (slot-definition-options '(x #:init-form 0 #:accessor x))
 *  => (#:init-form 0 #:accessor x)
 * @end lisp
doc>
|#
(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
      (key-get (cdr s) :allocation :instance)))

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

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

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

(define (slot-definition-init-form s)
  (if (pair? s)
      (key-get (cdr s) :init-form (void))
      (void)))

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


;; TODO: Document slot-init-function.
(define (slot-init-function c s)
  (let ((s (slot-definition-name s)))
    (cadr (assq s (slot-ref c 'getters-n-setters)))))

#|
<doc EXT class-slot-definition
 * (class-slot-definition c s)
 *
 * |Class-slot-definition| returns the definition of slot |s|
 * in class |c|.
 *
 * @lisp
 * (define-class <A> ()
 *   ((slot-one :init-form 'none :getter get-one)
 *    (slot-two :init-form 'nada :init-keyword :two)))
 *
 * (class-slot-definition <A> 'slot-two)
 *   => (slot-two #:init-form 'nada #:init-keyword #:two)
 *
 * (class-slots <A>)
 *   => ((slot-one #:init-form 'none #:getter get-one)
 *       (slot-two #:init-form 'nada #:init-keyword #:two))
 * @end lisp
doc>
|#
(define (class-slot-definition class slot-name)
  (assq slot-name (class-slots class)))

;;;
;;; Generic functions
;;;
#|
<doc EXT generic-function-name generic-function-methods generic-function-documentation
 * (generic-function-name gf)
 * (generic-function-methods gf)
 * (generic-function-documentation gf)
 *
 * Given a generic function object, these procedures return the name,
 * the list of methods and the documentation of the generic function.
 *
 * For example,
 *
 * @lisp
 * (define-generic compute :documentation "This function does computations")
 *
 * (define-method compute ( (a <real>) (b <real>) )
 *   (* a b))
 *
 * (define-method compute ( (a <real>) (b <complex>) )
 *   (* a (imag-part b)))
 *
 * (generic-function-name compute)          => compute
 * (generic-function-methods compute)       => (#[<method> 7f9901743db0]
 *                                              #[<method> 7f990155af60])
 * (generic-function-documentation compute) => "This function does computations"
 * @end lisp
 *
 * It is not necessary that the generic function be explicitly defined. If it is
 * not explicitly created, however, the documentation will be set to `#f`.
doc>
|#
(define (generic-function-name gf)
  (if (generic? gf)
      (slot-ref gf 'name)
      (%error-bad-generic 'generic-function-name gf)))

(define (generic-function-methods gf)
  (if (generic? gf)
      (slot-ref gf 'methods)
      (%error-bad-generic 'generic-function-methods gf)))

(define (generic-function-documentation gf)
  (if (generic? gf)
      (slot-ref gf 'documentation)
      (%error-bad-generic 'generic-function-documentation gf)))

;;;
;;; Methods
;;;
#|
<doc EXT method-generic-function method-specializers method-procedure
 * (method-generic-function m)
 * (method-specializers m)
 * (method-procedure m)
 *
 * Given a method |m|,
 * * |method-generic-function| returns the generic function that this
 *   object is a specialization of;
 * * |method-specializers| returns a list of specializers (this and other
 *   methods) of its generic functions;
 * * |method-procedure| returns the procedure that implements this method.
 *
 * @lisp
 * (define-method compute ( (a <real>) (b <real>) )
 *  (* a b))
 *
 * (define-method compute ( (a <real>) (b <complex>) )
 *   (* a (imag-part b)))
 *
 * (define m (car (generic-function-methods compute)))
 *
 * (method-generic-function m)
 *    => #[<generic> compute (2)]
 *
 * (method-specializers m)
 *    => (#[<class> <real> 7f3e3a157ba0]
 *        #[<class> <complex> 7f3e3a157bd0])
 *
 * (method-procedure m)
 *    => #[closure 7f9901d6e680]
 * @end lisp
doc>
|#
(define (method-generic-function m)
  (if (method? m)
      (slot-ref m 'generic-function)
      (%error-bad-method 'method-generic-function m)))

(define (method-specializers m)
  (if (method? m)
      (slot-ref m 'specializers)
      (%error-bad-method 'method-specializers m)))

(define (method-procedure m)
  (if (method? m)
      (slot-ref m 'procedure)
      (%error-bad-method 'method-procedure m)))

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

;;
;; is-a?
;;
#|
<doc EXT is-a?
 * (is-a? obj class)
 *
 * Returns `#t` if |obj| is an instance of |class|, and `#f` otherwise.
doc>
|#
(define (is-a? obj class)
  (and (memq class (class-precedence-list (class-of obj))) #t))

;;
;; Find-class
;;
#|
<doc EXT find-class
 * (find-class name)
 * (find-class name default)
 *
 * Returns the class whose name is equal to symbol |name|. If |name| is not
 * a class instance, the |default| value is returned, if present.
doc>
|#
(define (find-class name :optional (default #f default?))
  (let ((cls (if (symbol? name)
                 (symbol-value* name (current-module) #f)
                 name)))
    (if (is-a? cls <class>)
        cls
        (if default?
            default
            (error 'find-class "bad class ~S" name)))))

;;
;; %compute-slots
;;
(define (%compute-slots C)
  (define (remove-duplicate-slots l res)
    (if (null? l)
        res
        (let ((s (slot-definition-name (car l))))
          (unless (symbol? s)
            (error 'compute-slots "bad slot name ~S" s))
          (remove-duplicate-slots (cdr l)
                                  (if (assq s res) res (cons (car l) res))))))

  (define (build-slots-list cpl res)
    (if (null? cpl)
        (remove-duplicate-slots (reverse res) '())
        (build-slots-list (cdr cpl)
                          (append (class-direct-slots (car cpl)) res))))
  (define (sort-slots l)
    ;; Slots are sorted such that instance slots are always before other slots.
    ;; So, virtual slots are placed fist and, for instance, virtual slots can have
    ;; an initial value which depends of real slots.
    ;; Note that the following "sort" just displace non-instance slots at the
    ;; end keeping initial order (i.e. we don't use the STklos sort procedure
    ;; since it is not stable.
    (let Loop ((l l) (instance '()) (other '()))
      (cond
        ((null? l)
           (append (reverse! instance) (reverse! other)))
        ((eq? (slot-definition-allocation (car l)) :instance)
           (Loop (cdr l) (cons (car l) instance) other))
        (else
           (Loop (cdr l) instance (cons (car l) other))))))

  (sort-slots (build-slots-list (class-precedence-list C) '())))


;=============================================================================
;
;                       M e t a c l a s s e s   s t u f f
;
;=============================================================================
#| FIXME: insert this documentation when the concept of metaclass is explained
   in define-class

<doc EXT ensure-metaclass
 * (ensure-metaclass class-list)
 *
 * Given a list of classes, returns a class which is a superclass of all
 * the classes given. This superclass will be built so that it is a subclass
 * of the most specific classes posible.
 *
 * @lisp
 * (define-class <A> () ())
 * (define-class <B> (<A>) ())
 * (define-class <C> () ())
 *
 * (ensure-metaclass (list <B> <A>)) => #[<class> <B> 7fea2b8a3c30]
 * (define m (ensure-metaclass (list <B> <A> <C>)))
 * (class-direct-superclasses m)
 *   => (#[<class> <B> 7fea2b8a3c30] #[<class> <C> 7fea2b8afb40])
 * @end lisp
doc>
|#
(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)
  (if (null? supers)
      (find-class <class>)
      (let* ((all-metas (map (lambda (x)
                               (if (is-a? x <class>)
                                   x
                                   (class-of (find-class x))))
                             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
#|
<doc EXT-SYNTAX define-class
 * (define-class name supers slots . options)
 *
 * Creates a class whose name is |name|, and whose superclasses are in the
 * list |supers|, with the slots specified by the list |slots|.
 *
 * As an example, this is the definition of a point:
 * @lisp
 * (define-class <point> ()
 *   (x y))
 * @end lisp
 *
 * In another example, a class |<circle>| that inherits |<point>|.
 * @lisp
 * (define-class <circle> (<point>)
 *   (radius))
 * @end lisp
 *
 * The following options can be passed to slots:
 *
 * - |:init-form| is the default value for the slot.
 * - |:init-keyword| is the keyword for initializing the slot.
 * - |:getter| is the name of the getter method.
 * - |:setter| is the name of the setter method.
 * - |:accessor| is the name of the accessor (setter and getter) method.
 *
 *
 * For example,
 * @lisp
 * (define-class <point> ()
 *   (x :init-form 0 :getter get-x :setter set-x! :init-keyword :x)
 *   (y :init-form 0 :getter get-y :setter set-y! :init-keyword :y))
 * @end lisp
 *
 * {{stklos}} also defines setters for the specified getters, so the following
 * will work with the definition of |<point>| given above:
 *
 * @lisp
 * (set! (slot-ref my-point 'x) 50)
 * @end lisp
 *
 * Accessors, are methods which can be used as getter and setter, as shown bellow
 *
 * @lisp
 * (define-class <circle> (<point>)
 *   ((radius :accessor radius :init-keyword :radius)))
 *
 * (define x (make <circle> :radius 100))
 * (radius x)                             => 100
 * (set! (radius x) 200)
 * (radius x)                             => 200
 * @end lisp
doc>
|#
(define-macro (define-class name supers slots . options)
  `(define ,name
      (ensure-class
        ',name                                  ; name
        ',supers                                ; supers
        ',(declare-slots slots)                 ; slots
        ,(or (key-get options :metaclass #f)    ; metaclass
             `(ensure-metaclass ',supers))
        ,@options)))

;==== Ensure-class
(define (ensure-class name supers slots metaclass . options)

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

  (let ((supers (if (null? supers)
                    (list <object>)
                    (map find-class supers)))
        (slots  (map (lambda (x)
                       ;; ensure all slot description is a list
                       (if (pair? x) x (list x)))
                     slots)))
    ;; Verify that all direct slots are different and that we don't inherit
    ;; several time from the same class
    (let ((tmp (find-duplicate supers)))
      (when tmp
        (error 'define-class "super class ~S is duplicated in class ~S" tmp name)))
    (let ((tmp (find-duplicate (map slot-definition-name slots))))
      (when tmp
        (error 'define-class "slot ~S is duplicated in class ~S" tmp name)))

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

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


#|
<doc EXT define-generic
 * (define-generic gf)
 * (define-generic gf metaclass)
 * (define-generic gf :documentation str)
 * (define-generic gf metaclass :documentation str)
 *
 * This function creates a new generic function named |gf|. By default, the created
 * function is an instance of the class |<generic>|, except if |metaclass| is used.
 * Using a different metaclass permits to fine tune the way methods are applied.
 *
 * NOTE:  Generally, it is not necessary to use |define-generic| to declare a generic
 * function, since it is created the first time a method  with the name of that function
 * is created. However, for the sake of documentation, or to export a generic function
 * from a module which do not define methods, it is possible also to explicitly
 * define the generic function before its methods.
doc>
|#
; ==== Define-generic
(define-macro (define-generic gf :optional (meta '<generic>)
                                 :key      (documentation #f))
  `(define ,gf (ensure-generic-function ',gf ,meta ,documentation)))


;==== Ensure-generic-function
(define (ensure-generic-function name :optional (metaclass <generic>)
                                                (documentation #f))
  (let ((old (symbol-value* name (current-module) #f)))
    (if (generic? old)
        old
        (%symbol-define name
                        (make metaclass :name name
                                        :default (and (procedure? old) old)
                                        :documentation documentation)
                        (current-module)))))

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

#|
<doc EXT-SYNTAX  define-method
 * (define-method gf args body)
 *
 * The primitive |define-method| creates a generic method that will be added to a
 * generic function |gf|, having arguments |args| and body |body|.
 * |Args| is a list of arguments, and each argument can be either a single
 * symbol (the argument name) or an argument specification.
 * In its simplest form, a generic method behaves a lot like an ordinary
 * procedure:
 * @lisp
 * (define-method square (x)
 *   (* x x))
 *
 * (square 10)  => 100
 * @end lisp
 * In the above example, |x| is the only argument to the method, and it is
 * specified as a single symbol, which means that *_any_* type of argument will
 * be matched and this method will be used.
 *
 * An argument may also be specified in the form of a list with two elements:
 * the argument name and its class. The method will only be applicable when its
 * arguments are of the specified classes (or types).
 * @lisp
 * (define-method ++ ( (x <string>) (y <string>) )
 *   (string-append x y))
 *
 * (define-method ++ ( (x <number>) (y <number>) )
 *   (+ x y))
 *
 * (define-method ++ ( (x <string>) (y <number>) )
 *   (format #f (string-append x "~a") y))
 *
 * (++ "abc" "def")   => "abcdef"
 * (++ 2 3.5)         => 5.5
 * (++ 2 2-3i)        => 4-3i
 * (++ "number: " 10) => "number: 10"
 * (++ 3 "wrong")     => error
 * @end lisp
 * In this example, three methods are added to the generic function |++|,
 * and when the generic function is called, the appropriate method is chosen.
 * When no method is applicable, an error is signaled.
 *
 * The classes matched in the example are |<string>| and |<number>|, but any
 * class can be used. All Scheme types are built-in classes in STklos:
 *
 * ```
 * <boolean>  <null>
 * <char>     <object>
 * <class>    <pair>
 * <complex>  <procedure>
 * <eof>      <rational>
 * <integer>  <real>
 * <fixnum>   <bignum>
 * <list>     <symbol>
 * <vector>  ...
 * ```
 *
 * User-defined classes can also be used (and this is the main original use case
 * for generic functions). A very simple example follows.
 * @lisp
 * (define-class <figure> ()
 *   ((pos-x :getter pos-x :init-keyword :pos-x)
 *    (pos-y :getter pos-y :init-keyword :pos-y)))
 *
 * (define-class <circle> (<figure>)
 *   ((radius :getter radius :init-keyword :radius)))
 *
 * (define-class <square> (<figure>)
 *   ((side :getter side :init-keyword :side)))
 *
 * (define-method describe-figure ( (c <circle>) )
 *   (format #f "A circle at (~a, ~a), with radius ~a.~%"
 *           (pos-x c)
 *           (pos-y c)
 *           (radius c)))
 *
 * (define-method describe-figure ( (s <square>) )
 *   (format #f "A square centered at (~a, ~a), with side ~a.~%"
 *           (pos-x s)
 *           (pos-y s)
 *           (side s)))
 *
 * (define sq (make <square> :pos-x 1 :pos-y 2 :side 10))
 * (define ci (make <circle> :pos-x 2 :pos-y 2 :radius 5))
 *
 * (describe-figure sq)
 *   => "A square centered at (1, 2), with side 10.\n"
 *
 * (describe-figure ci)
 *   => "A circle at (2, 2), with radius 5.\n"
 * @end lisp
 * Generic methods can be created with different number of arguments, and that
 * will also be one criterion to match the method.
 *
 * @lisp
 * (define-method process (a b c) (+ a b c))
 * (define-method process (a b) (* a b))
 *
 * (process 2 3)   => 6
 * (process 2 3 4) => 9
 * @end lisp
doc>
|#

#|
<doc EXT next-method
 * (next-method)
 *
 * Inside a method, one can use the |(next-method)|  to call the next
 * applicable method.
 * @lisp
 * (define-method class-list ((obj <integer>))  (cons 'integer  (next-method)))
 * (define-method class-list ((obj <rational>)) (cons 'rational (next-method)))
 * (define-method class-list ((obj <real>))     (cons 'real     (next-method)))
 * (define-method class-list ((obj <complex>))  (cons 'complex  (next-method)))
 * (define-method class-list ((obj <number>))   (cons 'number   (next-method)))
 * (define-method class-list ((obj <top>))      (list 'top))
 *
 * (class-list 2.3)  => (real complex number top)
 * (class-list 2)    => (integer rational real complex number top)
 * (class-list 1-2i) => (complex number top)
 * @end lisp
doc>
|#

;==== Add-method!
(define (%method-specializers-equal? gf m1 m2)
  (equal? (method-specializers m1)
          (method-specializers m2)))

(define method-specializers-equal? ; will be redefined later as a method.
    %method-specializers-equal?)


(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 (memq 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 (delete! m (class-direct-methods x)
                                                     eq?)))
              (method-specializers m)))

(define (compute-new-list-of-methods gf new)
  (let ((methods  (generic-function-methods gf)))
    (let Loop ((l methods))
      (if (null? l)
          (cons new methods)
          (if (method-specializers-equal? gf (car l) new)
              (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! gf 'methods (compute-new-list-of-methods gf m))
  (add-method-in-classes! m)
  m)


(define (ensure-method gf args body :optional kind-of-method)
  (let* ((new  (extended-lambda->lambda `(method ,args ,@body)))
         (args (cadr new))
         (body (cddr new)))
    `(make ,(or kind-of-method '<method>)
          :generic-function ,gf
          :specializers     (map* find-class ',(specializers args))
          :procedure        (let ((next-method (void)))
                              (lambda ,(formals args)
                                ,@body)))))


;====  Method
(define-macro (method args . body)
  (ensure-method #f args body))


; ==== Define-method
(define-macro (define-method name args . body)
  (let ((gf (gensym "gf")))
    `(let ((,gf (ensure-generic-function ',name)))
       (add-method! ,gf ,(ensure-method gf args body))
       (values (void) ',name))))


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

;==== Methods to compare objects
(define-method object-eqv?   (x y) #f)         ; by default
(define-method object-equal? (x y) (eqv? x y)) ; if they are eqv? they are equal?


;==== 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]" (number->string (address-of o) 16)))

(define-method write-object ((o <object>) file)
  (let ((class (class-of o)))
    (if (slot-bound? class 'name)
        (format file "#[~A ~A]" (class-name class)
                (number->string (address-of o) 16))
        (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)
                (number->string (address-of class) 16))
        (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))

;==== Slot access

;; Avoid printing object itself in the error message, because it might
;; cause an infinite loop (via write-object method). Problem signaled
;; by Shiro Kawai <shiro@acm.org>.

(define-method slot-unbound ((c <class>) (o <object>) s)
  (error "slot ~S is unbound in #p~A (an object of class ~S)"
         s (number->string (address-of o) 16) c))

(define-method slot-missing ((c <class>) (o <object>) name :optional value)
  (error "no slot with name `~S' in #p~A (an object of class ~S)"
         name (number->string (address-of o) 16) c))

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

(define-method no-next-method ((gf <generic>) method args)
  (error "no next method for ~S in call ~S"
         method (cons (generic-function-name 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 Rob Deline <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
;;
;=============================================================================
;; Define generics to avoid compiler warnings about undefined symbols
(define-generic remove-class-accessors)
(define-generic update-direct-method)
(define-generic update-direct-subclass)


;==== 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 (eq? (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 (eq? cls old) new cls))
                         (class-direct-superclasses 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))))

;==== 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
                                   (delete! old (class-direct-subclasses c) eq?)))
            (class-direct-superclasses 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))


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

(define (%find-inherited-get-n-set slot-name class)
  (let Loop ((l (cdr (class-precedence-list class))))
    (let ((r (assq slot-name (slot-ref (car l) 'getters-n-setters))))
      (if r
          (cddr r)
          (Loop (cdr l))))))

(define (%direct-slot? slot-name class)
  (memq slot-name (map slot-definition-name
                       (class-direct-slots class))))

(define (%make-active-getter-n-setter index keys)
  (let ((before-ref  (make-closure (key-get keys :before-slot-ref #f)))
        (after-ref   (make-closure (key-get keys :after-slot-ref #f)))
        (before-set! (make-closure (key-get keys :before-slot-set! #f)))
        (after-set!  (make-closure (key-get keys :after-slot-set! #f))))
    ;; Compute the getter and setter of an active slot at given index
    ;; The result is of the form '(getter setter (:before-ref ... :after-ref ...))
    (list (lambda (o)
            (if before-ref
                (if (before-ref o)
                    (let ((res (%fast-slot-ref o index)))
                      (and after-ref
                           (not (eqv? res (void)))
                           (after-ref o))
                      res)
                    (void))
                (let ((res (%fast-slot-ref o index)))
                  (and after-ref
                       (not (eqv? res (void)))
                       (after-ref o))
                  res)))
          (lambda (o v)
            (if before-set!
                (when (before-set! o v)
                  (%fast-slot-set! o index v)
                  (and after-set! (after-set! o v)))
                (begin
                  (%fast-slot-set! o index v)
                  (and after-set! (after-set! o v)))))
          (list
             :before-slot-ref  before-ref
             :after-slot-ref   after-ref
             :before-slot-set! before-set!
             :after-slot-set!  after-set!))))

;;;
;;; Compute-get-n-set
;;;
(define-method compute-get-n-set ((class <class>) s)
  (let ((name (slot-definition-name 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.
                 (if (%direct-slot? name class)
                     ;; This slot is direct; create a new shared cell
                     (let ((shared-cell (void)))
                       (list (lambda (o)   shared-cell)
                             (lambda (o v) (set! shared-cell v))))
                     ;; Slot is inherited. Find its definition in superclass
                     (%find-inherited-get-n-set name class)))

      ((:each-subclass) ;; slot shared by instances of direct subclass only.
                        ;; (Thomas Buerger, April 1998)
                        (let ((shared-cell (void)))
                          (list (lambda (o)   shared-cell)
                                (lambda (o v) (set! shared-cell v)))))

      ((:virtual) ;; No allocation
                  ;; slot-ref and slot-set! function must be given by the user
                  (if (%direct-slot? name class)
                      (let* ((s   (slot-definition-options s))
                             (get (key-get s :slot-ref  #f))
                             (set (key-get s :slot-set! #f)))
                        (unless (and get set)
                          (error
                              "a :slot-ref and a :slot-set! must be supplied in ~S"
                              s))
                        (list (make-closure get)
                              (make-closure set)))
                      (%find-inherited-get-n-set name class)))
      ((:active) ;; Active slot
                 ;; active slots admit a daemon before or after slot access
              (let ((index      (slot-ref class 'nfields)))
                (slot-set! class 'nfields (+ index 1))
                (if (%direct-slot? name class)
                    (%make-active-getter-n-setter index (slot-definition-options s))
                    (let ((old (%find-inherited-get-n-set name class)))
                      ;; Old is of the form
                      ;;   (getter setter XXXX)
                      ;; where XXX is (:before-slot-ref closure ...)
                      ;; We must re-create the getter and setter since the offset
                      ;; of the slot can be different. but we MUST NOT re-evaluate
                      ;; the 4 daemons (they can be not exported closures).
                      (%make-active-getter-n-setter index (list-ref old 2))))))
      (else    (next-method)))))

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


(define (compute-slot-accessors class slots)
  ;; accessors are made here in-line using a light version of ensure-method
  ;; in particular, the class of accessors is <accessor-method> and there
  ;; is no next method defined for them, since they are terminal.

  (define (make-reader name getter)
    (let ((gf (ensure-generic-function getter)))
      (add-method! gf
                   (make <accessor-method>
                         :generic-function gf
                         :specializers (list class)
                         :procedure    (lambda (o) (slot-ref o name))))))
  (define (make-writer name setter)
    (let ((gf (ensure-generic-function setter)))
      (add-method! gf
                   (make <accessor-method>
                         :generic-function gf
                         :specializers (list class <top>)
                         :procedure    (lambda (o v) (slot-set! o name v))))))
  ;;
  ;;
  (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
            (make-reader name getter))
          (when setter
            (make-writer name setter))
          (when accessor
            (make-reader name accessor)
            (make-writer name accessor))))
      slots))


;;;
;;; compute-getters-n-setters
;;;

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

  (define (compute-slot-init-function s)
    (let ((init (slot-definition-init-form s)))
      (cond
        ((eq? init (void))
           #f)
        ((eq? (slot-definition-allocation s) :class)
           (let ((sn  (slot-definition-name s))
                 (o   (gensym))
                 (val (gensym)))
             (make-closure `(lambda (,o)
                              (let ((,val (%slot-ref ,o ',sn)))
                                (if (eq? ,val (void))
                                    ,init
                                    ,val))))))
        (else
           (make-closure `(lambda (,(gensym)) ,init))))))


  (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 <optional infos>)
           ;; If slot is an instance 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
;;;
;;; The current implementation was provided by Anthony Beurivé
;;; <beurive@labri.u-bordeaux.fr>. This implementation is monotonic and
;;; works "by level". For more information on monotonic superclass
;;; linearization  algorithm look at the paper
;;;     A Monotonic Superclass Linearization for Dylan,
;;;     K. Barrett, B. Cassels, P. Haahr, D. A. Moon, K. Playford,
;;;     P. Tucker Withington,
;;;     OOPSLA 96.
;;; also available at
;;;     http://www.webcom.com/~haahr/dylan/linearization-oopsla96.html
;;;
(define (compute-cpl class)

  (define (reduce sequences cpl)
    (define (aux sequence)
      (if (and (pair? sequence) (memq (car sequence) cpl))
          (aux (cdr sequence))
          sequence))
    (map aux sequences))

  (define (delete-null-lists sequences)
    (if (pair? sequences)
        (let ((first (car sequences)))
          (if (pair? first)
              (cons first (delete-null-lists (cdr sequences)))
              (delete-null-lists (cdr sequences))))
        '()))

  (define (select-candidate candidate sequences)
    (if (pair? sequences)
        (and (not (memq candidate (cdr (car sequences))))
             (select-candidate candidate (cdr sequences)))
        candidate))

  (define (filter-candidates candidates sequences)
    (if (pair? candidates)
        (or (select-candidate (car candidates) sequences)
            (filter-candidates (cdr candidates) sequences))
        #f)) ; No valid candidate (inconsistency).

  (define (next-class sequences)
    (let ((candidates (map car sequences)))
      (or (filter-candidates candidates sequences)
          (car candidates)))) ; Arbitrarily brake the inconsistency.

  (define (step cpl sequences)
    (if (pair? sequences)
        (let ((new-cpl (cons (next-class sequences) cpl)))
          (step new-cpl (delete-null-lists (reduce sequences new-cpl))))
        (reverse cpl)))

  (step '() (let ((supers (slot-ref class 'direct-supers)))
              (cons (cons class supers)
                    (map (lambda (super)
                           (slot-ref super 'cpl))
                         supers)))))


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

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

(define-method initialize-instance ((class <class>) initargs)
  (next-method)
  (let ((dslots (key-get initargs :slots   '()))
        (supers (key-get initargs :dsupers '())))

    (slot-set! class 'name              (key-get initargs :name '???))
    (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)
    (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)))
    ;; 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)))


(define-method initialize-instance ((generic <generic>) initargs)
  (let ((previous-definition (key-get initargs :default #f))
        (doc                 (key-get initargs :documentation #f)))
    (next-method)
    (slot-set! generic 'name    (key-get initargs :name '???))
    (slot-set! generic 'methods
               (if (procedure? previous-definition)
                   (list (make <method> ; /// FAST-METHOD
                           :specializers <top>
                           :generic-function generic
                           :procedure (let ((next-methhod #f))
                                        (lambda l
                                          (apply previous-definition l)))))
                   '()))
    (slot-set! generic 'documentation doc)))

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


(define initialize initialize-instance)

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

#|
<doc EXT allocate-instance initialize initialize-instance make make-instance
 * (allocate-instance c init-args)
 * (initialize-instance c init-args)
 * (make-instance c)
 * (initialize c init-args)
 * (make c)
 *
 * |Make-instance| creates and initializes an instance of class |c|.
 * |Allocate-instance| is similar, but only allocates the instance,
 * without initializing it.
 *
 * @lisp
 * (define-class <X> () ((xx :init-form 10)))
 * (define my-x (make-instance <X>))
 * (describe my-x)
 * #[<X> 7fbd2e3075d0] is an an instance of class <X>.
 * The only slot is:
 *   xx = 10
 *
 * (define my-other-x (allocate-instance <X> 20))
 * (describe y)
 * #[<X> 7fbd2e2bdab0] is an an instance of class <X>.
 * The only slot is:
 *   xx = #[unbound]
 * @end lisp
 * NOTE: |make| is an alias for |make-instance|.
 *
 * |Initialize| is a generic function that is called by |make-instance|
 * (or |make|) in order to initialize instances of a class.
 *
 * @lisp
 * (define-class <x> (<object>)
 *   ((a :init-keyword :a)
 *    (b :accessor x-b)
 *    c))
 *
 * (describe (make <x> :a 10))
 *
 * #[<x> 7f8a49e96bd0] is an instance of class <x>.
 * Slots are:
 *      a = 10
 *      b = #[unbound]
 *      c = #[unbound]
 * @end lisp
 * If an |initialize| method is implemented, it overrides the
 * default, and the initializing arguments are not automatically
 * processed:
 * @lisp
 * (define-method initialize-instance ((x <x>) args)
 *   (print '== " " args)
 *   (set! (bb x) 1000))
 *
 * (describe (make <x> :a 10))
 *
 * == (a 10)
 * #[<x> 7f8a49e96bd0] is an instance of class <x>.
 * Slots are:
 *      a = #[unbound]
 *      b = 1000
 *      c = #[unbound]
 * @end lisp
 * Note that |a| was *not* initialized! This is because the new
 * |initialize| method does not set it, and the keyword initializer
 * was ignored (the method actually printed the arguments, but
 * ignored them).
 *
 * But if |next-method| is called at the end of our |initialize|
 * method, the result will be as expected:
 *
 * @lisp
 * (define-method initialize-instance ((x <x>) args)
 *   (print '== " " args)
 *   (set! (bb x) 1000)
 *   (next-method))
 *
 * (describe (make <x> :a 10))
 *
 * == (a 10)
 * #[<x> 7f8a49e96bd0] is an instance of class <x>.
 * Slots are:
 *      a = 10
 *      b = 1000
 *      c = #[unbound]
 * @end lisp
 * Now |a| has also been initialized.
 *
 * NOTE: |initialize| is an alias for |initialize-instance|.
 *
 * Finally, the standard |make-instance| is simple to write: it just
 * calls |allocate-instance| and initializes it with |initialize-instance|:
 * @lisp
 * (define-method make-instance ((class <class>) . initargs)
 *   (let ((instance (allocate-instance class initargs)))
 *     (initialize-instance instance initargs)
 *     instance))
 * @end lisp
doc>
|#
(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 instance initargs)
    instance))

(define make make-instance)

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

#|
<doc EXT change-class
 * (change-class object new-class)
 *
 * |Change-class| will change the class of |object| to |new-class|.
 * The values of slots that do exist in both the old and new classes
 * are carried out to the new object. The slots that do not exist in
 * the old instance are initialized as if the object was new.
 *
 * @lisp
 * (define-class <A> ()        ((a-slot #:init-form 'a-value)))
 * (define-class <B> ()        ((b-slot #:init-form 'b-value)))
 * (define-class <C> (<A> <B>) ((c-slot #:init-form 'c-value)))
 *
 * (define a (make <A>))
 * (define c (make <C>))
 *
 * (describe a)
 * #[<A> 7f91fe79c660] is an instance of class <A>.
 * The only slot is:
 *      a-slot = a-value
 *
 * (describe c)
 * #[<C> 7f91fe7a0510] is an instance of class <C>.
 * Slots are:
 *      a-slot = a-value
 *      b-slot = b-value
 *      c-slot = c-value
 *
 * (change-class a <C>)
 * (change-class c <A>)
 *
 * (describe a)
 * #[<C> 7f91fe79c660] is an instance of class <C>.
 * Slots are:
 *      a-slot = a-value
 *      b-slot = b-value
 *      c-slot = c-value
 *
 * (describe c)
 * #[<A> 7f91fe7a0510] is an instance of class <A>.
 * The only slot is:
 *      a-slot = a-value
 *
 * (slot-value c 'c-slot) => error
 * @end lisp
 * The last line shows that |c| really changed into an object of
 * class |<A>|: it does not have the slot |c-slot| anymore.
doc>
|#
(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
                    (when (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)))
                      (when init
                          (slot-set-using-class!
                               new-class
                               new-instance
                               slot
                               (init new-instance))))))
              (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))

;=============================================================================
;
;                               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 3 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.
;   - nobody (except persons really interested with playing with a MOP) will
;     probably use the following code
;=============================================================================

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

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

(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 apply-method ((gf <generic>) methods build-next args)
  (let ((proc (method-procedure (car methods))))
    (%set-next-method! proc (build-next methods args))
    (apply proc 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? (cdr procs))
                           (no-next-method gf (car procs) a)
                           (apply-method gf (cdr procs) next a)))))))
    (apply-method gf l next args)))


;; ==== apply-generic

(define-method apply-generic ((gf <generic>) args)
  (if (null? (generic-function-methods gf))
      (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))))


; ======================================================================
;       Functions redefined as methods ...
; ======================================================================
(define-method method-specializers-equal? ((gf <generic>) m1 m2)
  (%method-specializers-equal? gf m1 m2))


;=============================================================================
;
;                               T o o l s
;
;=============================================================================
(define class-subclasses #f)
(define class-methods    #f)

(let ()
  (define (list2set l)
    (let Loop ((l l) (res '()))
      (cond
       ((null? l)          res)
       ((memq (car l) res) (Loop (cdr l) res))
       (else               (Loop (cdr l) (cons (car l) res))))))

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

  ;; ==== class-subclasses
  (set! class-subclasses
        (lambda (c)
          (letrec ((allsubs (lambda (c)
                              (cons c (mapappend allsubs
                                                 (class-direct-subclasses c))))))
            (list2set (cdr (allsubs c))))))
  ;; ==== class-methods
  (set! class-methods
        (lambda (c)
          (list2set (mapappend class-direct-methods
                               (cons c (class-subclasses c)))))))

;==== CLOS like slot-value (but as a gf instead of proc as slot-ref or slot-set!)
(define-method slot-value ((o <object>) s)              ;; The getter
  (slot-ref o s))

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


;; Just tell C that the object system is now fully initialized
(%object-system-initialized)

) ;; end of STKLOS-OBJECT module

;; ======================================================================
(select-module STklos)
(import STKLOS-OBJECT)


;; LocalWords:  supers metaclass OOPSLA linearization beurive labri fr Cassels
;; LocalWords:  Beuriv Playford Withington Haahr Gregor Kickzales init
