(library (yuni miniobj minitype)
         (export miniobj-minitype-ref
                 miniobj-minitype-set!
                 miniobj-minitype-typeof
                 miniobj-minitype-typeof-error
                 define-minitype
                 make-minitype
                 make-minitype-obj
                 minitype-predicate
                 minitype-typeof
                 make-minitype-refslot
                 make-minitype-setslot!)
         (import (rnrs)
                 (yuni compat simple-struct))

(define (check? obj sym)
  (and
    (simple-struct? obj)
    (eq? sym
         (simple-struct-name obj))))
(define (minitype? obj)
  (check? obj '*yuni-minitype*))
(define (minitype-obj? obj)
  (check? obj '*yuni-minitype-obj*))

(define (check-minitype obj)
  (or (minitype? obj)
      (assertion-violation 'check-minitype "minitype needed" obj)))

(define (check-minitype-obj obj)
  (or (minitype-obj? obj)
      (assertion-violation 'check-minitype-obj "minitype-obj needed" obj)))

(define (check-minitype-obj-type obj minitype)
  (or (and (minitype-obj? obj)
           (minitype? minitype)
           (or (eq? (minitype-type (minitype-obj-type obj))
                    (minitype-type minitype))
               (assertion-violation 'check-minitype-obj "type unmatched"
                                    (list minitype obj))))
      (assertion-violation 'check-minitype-obj "minitype-obj needed" obj)))
(define (minitype-slot obj)
  (check-minitype obj)
  (simple-struct-ref obj 1))
(define (minitype-type obj)
  (check-minitype obj)
  (simple-struct-ref obj 0))
(define (minitype-obj-slot obj)
  (check-minitype-obj obj)
  (simple-struct-ref obj 1))
(define (minitype-obj-type obj)
  (check-minitype-obj obj)
  (simple-struct-ref obj 0))

(define (make-minitype name slots)
  (make-simple-struct 
    '*yuni-minitype* 
    2 (list name slots)))

(define (make-minitype-obj minitype)
  (check-minitype minitype)
  (lambda ()
    (make-simple-struct 
      '*yuni-minitype-obj* 
      2 (list 
          minitype
          (make-vector (length (minitype-slot minitype)))))))

(define (minitype-predicate obj minitype)
  (check-minitype minitype)
  (and
    (minitype-obj? obj)
    (eq? minitype
         (minitype-obj-type obj))))

(define (minitype-typeof obj) ;; FIXME: should allow RnRS types(like lists, vectors, ...)..
  (and
    (minitype-obj? obj)
    (minitype-obj-type obj)))


(define (scan-slot minitype slot)
  (define (number cur rest)
    (if (pair? rest)
      (if (eq? (car rest) slot)
        cur
        (number (+ 1 cur) (cdr rest)))
      #f))
  (let ((slots (minitype-slot minitype)))
    (number 0 slots)))

(define (make-minitype-refslot minitype slot)
  (define slotno (scan-slot minitype-slot))
  (check-minitype minitype)
  (lambda (obj)
    (check-minitype-obj obj minitype)
    (vector-ref (minitype-obj-slot obj) slotno)))

(define (make-minitype-setslot! minitype slot)
  (define slotno (scan-slot minitype-slot))
  (check-minitype minitype)
  (lambda (obj value)
    (check-minitype-obj obj minitype)
    (vector-set! (minitype-obj-slot obj) slotno value)))

(define (miniobj-minitype-typeof obj k)
  (if (minitype-obj? obj)
    (minitype-obj-type obj)
    (k obj)))

(define (miniobj-minitype-typeof-error obj)
  (assertion-violation 'miniobj-typeof "unsupported object" obj))

(define (miniobj-minitype-ref obj slot k)
  (if (minitype-obj? obj)
    (vector-ref (minitype-obj-slot obj) (scan-slot (minitype-obj-type obj) slot))
    (k obj slot)))

(define (miniobj-minitype-set! obj slot value k)
  (if (minitype-obj? obj)
    (vector-set! (minitype-obj-slot obj) (scan-slot (minitype-obj-type obj) slot) value)
    (k obj slot value)))

(define-syntax define-minitype
  (syntax-rules ()
    ((_ name spec)
     (define name (make-minitype 'name 'spec)))))

)
