object.rkt
#lang racket/base

(require "contract.rkt")
(provide (contract-out (build-object ffi-builder?)) build-object-ptr _gobject pointer field set-field gtype->ffi)

(require "loadlib.rkt" "base.rkt" ffi/unsafe ffi/unsafe/alloc "function.rkt" "translator.rkt" "gtype.rkt"
         racket/match (prefix-in f: "field.rkt"))

(define-gi* g-object-info-find-method (_fun _pointer _string -> _info))
(define-gi* g-object-info-get-parent (_fun _pointer -> _info))
(define-gi* g-object-info-get-n-fields (_fun _pointer -> _int))
(define-gi* g-object-info-get-field (_fun _pointer _int -> _info))

(define (find-method info name)
  (and info
       (or (g-object-info-find-method info name)
           (find-method (g-object-info-get-parent info) name))))

(define-gobject* g-object-unref (_fun _pointer -> _void) #:wrap (deallocator))
(define-gobject* g-object-ref-sink (_fun _pointer -> _pointer) #:wrap (allocator g-object-unref))



(define (closures info)
  (define (call name args)
    (define function-info (find-method info (c-name name)))
    (if function-info
        (apply (build-function function-info) args)
        (raise-argument-error 'build-object "FFI method name" name)))
  (define fields-dict
    (for/list ([i (in-range (g-object-info-get-n-fields info))])
      (define field-info (g-object-info-get-field info i))
      (cons (g-base-info-get-name field-info) field-info)))
  (define (find-field name)
    (cdr (or (assoc (c-name name) fields-dict)
             (raise-argument-error 'build-object "FFI field name" name))))
  (define (closure this)
    (define signals (box null))
    (λ (name . args)
      (case name
        [(:this) this]
        [(:signals) signals]
        [(:field)
         (match args
           [(list name) (f:get this (find-field name))])]
        [(:set-field) 
         (match args
           [(list name value) (f:set (find-field name) value)])]
        [else (call name (cons this args))])))
  (values call closure))

(define (build-object info)
  (define-values (call closure) (closures info))
  (λ (name . args)
    (define this (g-object-ref-sink (call name args)))
    (closure this)))

(define (build-object-ptr info ptr)
  (define-values (call closure) (closures info))
  (closure ptr))

(define (pointer obj) (obj ':this))

(define (field obj name) (obj ':field name))

(define (set-field obj name value) (obj ':set-field name value))

(define-gi* g-irepository-find-by-gtype (_fun (_pointer = #f) _long -> _pointer))

(define (gobject gtype ptr)
  (let ([info (g-irepository-find-by-gtype gtype)])
      (if (and info (eq? (g-base-info-get-type info) 'object))
          (build-object-ptr info ptr)
          (raise-argument-error 'gi-ffi "gtype not found in GI" gtype))))

(define _gobject (make-ctype _pointer (λ (x) (x ':this)) (λ (x) (gobject (gtype x) x))))

(define (gtype->ffi gtype)
  (case-gtype gtype
    [(invalid void) _void]
    [(char) _byte]
    [(uchar) _ubyte]
    [(boolean) _bool]
    [(int) _int]
    [(uint) _uint]
    [(long) _long]
    [(ulong) _ulong]
    [(int64) _int64]
    [(uint64) _uint64]
    [(float) _float]
    [(double) _double]
    [(pointer) _pointer]
    [(string) _string]
    [else _gobject]))