;; -*-scheme-*-

(define-module (g-wrapped gw-runtime-spec)
  :use-module (g-wrap))

(let ((m (gw:new-module "gw-runtime")))

  ;; Some helper functions for ops common to most types.  If these
  ;; prove very useful, perhaps we'll make them a public part of
  ;; g-wrap.
  (define (standard-c-call-gen result func-call-code)
    (list (gw:result-get-c-name result) " = " func-call-code ";\n"))

  (define (add-standard-result-handlers! type c->scm-converter)
    (define (standard-pre-handler result)
      (let* ((ret-type-name (gw:result-get-proper-c-type-name result))
             (ret-var-name (gw:result-get-c-name result)))
        (list "{\n"
              "    " ret-type-name " " ret-var-name ";\n")))
    
    (gw:type-set-pre-call-result-ccodegen! type standard-pre-handler)
    
    (gw:type-set-post-call-result-ccodegen!
     type
     (lambda (result)
       (let* ((scm-name (gw:result-get-scm-name result))
              (c-name (gw:result-get-c-name result)))
         (list
          (c->scm-converter scm-name c-name)
          "  }\n")))))

  (gw:module-set-guile-module! m '(g-wrapped gw-runtime))
  
  (gw:module-set-pre-header-ccodegen!
   m
   (lambda (client-only?)
     (list
      "#define _GNU_SOURCE\n")))
  
  (gw:module-set-declarations-ccodegen!
   m
   (lambda (client-only?)
     (list
      "#include <limits.h>\n"
      "#include \"g-wrap-runtime-guile.h\"\n")))
  
  (gw:module-set-init-ccodegen!
   m
   (lambda (client-only?)
     (if client-only?
         '()
         (gw:inline-scheme
          '(define gw:runtime-modules-hash (make-hash-table 131))
          '(define gw:runtime-modules-list '())
          '(define-public (gw:module-register-runtime name)
             (if (hash-ref gw:runtime-modules-hash name)
                 (error "Tried to double-register module " name)
                 (begin
                   (hash-set! gw:runtime-modules-hash name #t)
                   (set! gw:runtime-modules-list
                         (cons name gw:runtime-modules-list)))))
          '(define-public (gw:list-runtime-modules)
             (map string-copy gw:runtime-modules-list))
          `(gw:module-register-runtime ,(gw:module-get-name m))))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; void
  (let ((wt (gw:wrap-type m '<gw:void> "void" "void")))

    ;; Illegal as an arg.  Make them pay.
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (list "; 2 + 2 = 5;\n")))
    
    ;; can't use a standard one since void's don't exist as results.
    (gw:type-set-call-ccodegen!
     wt
     (lambda (result func-call-code)
       (list func-call-code ";\n")))

    (gw:type-set-post-call-result-ccodegen!
     wt
     (lambda (result)
       (list (gw:result-get-scm-name result) " = SCM_UNSPECIFIED;\n"))))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; bool - boolean type
  (let ((wt (gw:wrap-type m '<gw:bool> "int" "const int")))
    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (let ((x (gw:param-get-scm-name param)))
         (list "( SCM_BOOL_T == (" x ") " "|| SCM_BOOL_F == (" x "))"))))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= ((SCM_BOOL_F != (" scm-name ")));\n"))))

    (gw:type-set-call-ccodegen! wt standard-c-call-gen)

    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = ((" c-name ") ? SCM_BOOL_T : SCM_BOOL_F );\n"))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; char
  (let ((wt (gw:wrap-type m '<gw:char> "char" "const char")))
    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (list "gh_char_p(" (gw:param-get-scm-name param) ")")))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2char(" scm-name ");\n"))))

    (gw:type-set-call-ccodegen! wt standard-c-call-gen)

    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_char2scm(" c-name ");\n"))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; int
  (let ((wt (gw:wrap-type m '<gw:int> "int" "const int")))

    (gw:type-set-init-ccodegen!
     wt
     (lambda (type client-only?)
       (list
        "gw__module_gw_runtime_scm_intmin = gh_long2scm(INT_MIN);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_intmin);\n"
        "gw__module_gw_runtime_scm_intmax = gh_long2scm(INT_MAX);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_intmax);\n"))) 

    (gw:type-set-global-ccodegen!
     wt
     (lambda (type client-only?)
       (list 
        "static SCM gw__module_gw_runtime_scm_intmin;\n"
        "static SCM gw__module_gw_runtime_scm_intmax;\n")))
    
    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "((scm_integer_p(" x ") == SCM_BOOL_T) &&"
               " (scm_geq_p(" x ", gw__module_gw_runtime_scm_intmin) == SCM_BOOL_T) &&"
               " (scm_leq_p(" x ", gw__module_gw_runtime_scm_intmax) == SCM_BOOL_T))"))))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2long(" scm-name ");\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_long2scm(" c-name ");\n"))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; unsigned-int
  (let ((wt (gw:wrap-type m '<gw:unsigned-int> "unsigned int" "const unsigned int")))
    (gw:type-set-init-ccodegen!
     wt
     (lambda (type client-only?)
       (list
        "gw__module_gw_runtime_scm_uintmax = gh_ulong2scm(UINT_MAX);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_uintmax);\n"))) 

    (gw:type-set-global-ccodegen!
     wt
     (lambda (type client-only?) (list "static SCM gw__module_gw_runtime_scm_uintmax;\n")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "((scm_integer_p(" x ") == SCM_BOOL_T) &&"
               " (scm_positive_p(" x ") == SCM_BOOL_T) &&"
               " (scm_leq_p(" x ", gw__module_gw_runtime_scm_uintmax) == SCM_BOOL_T))"))))
    
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2ulong(" scm-name ");\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_ulong2scm(" c-name ");\n"))))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; long
  (let ((wt (gw:wrap-type m '<gw:long> "long" "const long")))
    (gw:type-set-init-ccodegen!
     wt
     (lambda (type client-only?)
       (list
        "gw__module_gw_runtime_scm_longmin = gh_long2scm(LONG_MIN);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_longmin);\n"
        "gw__module_gw_runtime_scm_longmax = gh_long2scm(LONG_MAX);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_longmax);\n"
        ))) 

    (gw:type-set-global-ccodegen!
     wt
     (lambda (type client-only?)
       (list "static SCM gw__module_gw_runtime_scm_longmin;\n"
             "static SCM gw__module_gw_runtime_scm_longmax;\n")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "((scm_integer_p(" x ") == SCM_BOOL_T) &&"
               " (scm_geq_p(" x ", gw__module_gw_runtime_scm_longmin) == SCM_BOOL_T) &&"
               " (scm_leq_p(" x ", gw__module_gw_runtime_scm_longmax) == SCM_BOOL_T)"))))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2long(" scm-name ");\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_long2scm(" c-name ");\n"))))
  

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; unsigned-long
  (let ((wt (gw:wrap-type m
                          'unsigned-long
                          "unsigned long"
                          "const unsigned long")))
    (gw:type-set-init-ccodegen!
     wt
     (lambda (type client-only?)
       (list
        "gw__module_gw_runtime_scm_ulongmax = gh_long2scm(ULONG_MAX);\n"
        "scm_protect_object(gw__module_gw_runtime_scm_ulongmax);\n"))) 

    (gw:type-set-global-ccodegen!
     wt
     (lambda (type client-only?) (list "static SCM gw__module_gw_runtime_scm_ulongmax;\n")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "((scm_integer_p(" x ") == SCM_BOOL_T) &&"
               " (scm_positive_p(" x ") == SCM_BOOL_T) &&"
               " (scm_leq_p(" x ", gw__module_gw_runtime_scm_ulongmax) == SCM_BOOL_T))"))))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2ulong(" scm-name ");\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)

    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_ulong2scm(" c-name ");\n"))))


;; long long support is currently unavailable.  To fix that, we're
;; going to need to do some work to handle broken versions of guile
;; (or perhaps just refuse to add long long support for those
;; versions.  The issue is that some versions of guile in
;; libguile/__scm.h just "typedef long long_long" even on platforms
;; that have long long's that are larger than long.  This is a mess,
;; meaning, among other things, that long_long won't be big enough to
;; hold LONG_LONG_MAX, etc.  yuck.

;   ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;   ;; long-long
;   (let ((wt (gw:wrap-type m '<gw:long-long> "long long" "const long long")))
;     (gw:type-set-init-ccodegen!
;      wt
;      (lambda (type client-only?)
;        (list
;         "gw__module_gw_runtime_scm_llongmin = scm_long_long2num(LONG_LONG_MIN);\n"
;         "scm_protect_object(gw__module_gw_runtime_scm_llongmin);\n"
;         "gw__module_gw_runtime_scm_llongmax = scm_long_long2num(LONG_LONG_MAX);\n"
;         "scm_protect_object(gw__module_gw_runtime_scm_llongmax);\n"))) 

;     (gw:type-set-global-ccodegen!
;      wt
;      (lambda (type client-only?)
;        (list "static SCM gw__module_gw_runtime_scm_llongmin;\n"
;              "static SCM gw__module_gw_runtime_scm_llongmax;\n")))

;     (gw:type-set-scm-arg-type-test-ccodegen!
;      wt
;      (lambda (param)
;        ;; I don't know if it's more efficient to work on the C side or
;        ;; the scheme side...
;        (let ((x (gw:param-get-scm-name param)))
;          (list "((scm_integer_p(" x ") == SCM_BOOL_T) &&"
;                " (scm_geq_p(" x ", gw__module_gw_runtime_scm_llongmin) == SCM_BOOL_T) &&"
;                " (scm_leq_p(" x ", gw__module_gw_runtime_scm_llongmax) == SCM_BOOL_T))"))))


;     (gw:type-set-pre-call-arg-ccodegen!
;      wt
;      (lambda (param)
;        (let* ((scm-name (gw:param-get-scm-name param))
;               (c-name (gw:param-get-c-name param)))
;          (list
;           c-name
;           " = scm_num2long_long("
;           scm-name
;           ", "
;           "(char *) SCM_ARG1, \"gw:type-long-long-conversion\");\n"))))

;     (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
;     (add-standard-result-handlers!
;      wt
;      (lambda (scm-name c-name)
;        (list scm-name " = scm_long_long2num(" c-name ");\n"))))
  
  ;; Not converting this yet because there's no non-hacked up
  ;; scm->ulonglong conversion and back.  If someone needs this, we'll
  ;; just re-enable it and the corresponding C code.

                                        ; (add-type 'unsigned-long-long "unsigned long long"  
                                        ;           ;; fn-convert-to-scm 
                                        ;           (lambda (x) (list "gh_ulonglong2scm(" x ")"))
                                        ;           ;; fn-convert-from-scm 
                                        ;           (lambda (x) (list "gh_scm2ulonglong(" x ")"))
                                        ;           ;; fn-scm-is-a
                                        ;           (lambda (x) (list "gh_exact_p(" x ")")))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; float
  (let ((wt (gw:wrap-type m '<gw:float> "float" "const float")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "(scm_number_p(" x ") == SCM_BOOL_T)"))))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2double(" scm-name ");\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_double2scm(" c-name ");\n"))))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; double
  (let ((wt (gw:wrap-type m '<gw:double> "double" "const double")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       ;; I don't know if it's more efficient to work on the C side or
       ;; the scheme side...
       (let ((x (gw:param-get-scm-name param)))
         (list "(scm_number_p(" x ") == SCM_BOOL_T)"))))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name "= gh_scm2double(" scm-name ");\n"))))

    (gw:type-set-call-ccodegen! wt standard-c-call-gen)

    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = gh_double2scm(" c-name ");\n"))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; m-chars-caller-owned
  ;;   malloced char* (string), caller-owned
  ;;
  ;; arg temps will be malloced before call, freed on return.
  ;; result temps will be freed after conversion to scheme.
  ;; 
  (let ((wt (gw:wrap-type m '<gw:m-chars-caller-owned> "char *" "const char*")))
    
    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (let ((x (gw:param-get-scm-name param)))
         (list "((" x " == SCM_BOOL_F) || SCM_STRINGP(" x "))"))))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list
          c-name " = (((" scm-name ") == SCM_BOOL_F) ? "
          "NULL : gh_scm2newstr(" scm-name ", NULL));\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)

     (gw:type-set-post-call-arg-ccodegen!
      wt
      (lambda (param)
        (let* ((c-name (gw:param-get-c-name param)))
          (list "if(" c-name ") free((void *) " c-name ");\n"))))
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list
        "  /* we coerce to (char *) here b/c broken guile 1.3.4 prototype */\n"
        scm-name " = ((" c-name ") ? gh_str02scm((char *) " c-name ") : SCM_BOOL_F);\n"

        "  if(" c-name ") free((void *) " c-name ");\n"))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; m-chars-callee-owned
  ;;   malloced char* (string), callee-owned
  ;;
  ;; arg temps will be malloced before call, not freed on return.
  ;; result temps will not be freed after conversion to scheme.
  ;; 
  (let ((wt (gw:wrap-type m '<gw:m-chars-callee-owned> "char *" "const char*")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (let ((x (gw:param-get-scm-name param)))
         (list "((" x " == SCM_BOOL_F) || SCM_STRINGP(" x "))"))))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list
          c-name " = (((" scm-name ") == SCM_BOOL_F) ? "
          "NULL : gh_scm2newstr(" scm-name ", NULL));\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)

    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list
        "  /* we coerce to (char *) here b/c broken guile 1.3.4 prototype */\n"
        scm-name
        " = ((" c-name ") ? gh_str02scm((char *) " c-name ") : SCM_BOOL_F);\n"))))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; scm - pass scheme pointers through unmolested.
  (let ((wt (gw:wrap-type m '<gw:scm> "SCM" "const SCM")))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name " = "scm-name ";\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = " c-name ";\n"))))
  
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; gw:wct - wrapped c pointer type object
  (let ((wt (gw:wrap-type m '<gw:wct> "SCM" "const SCM")))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (list "gw_wct_p(" (gw:param-get-scm-name param) ")")))
    
    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name " = "scm-name ";\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = " c-name ";\n"))))

  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; gw:wcp - wrapped c pointer object
  (let ((wt (gw:wrap-type m '<gw:wcp> "SCM" "const SCM")))

    (gw:type-set-init-ccodegen!
     wt
     (lambda (type client-only?)
       (gw:inline-scheme
        `(define-public (gw:wcp=? x y) (equal? a b)))))

    (gw:type-set-scm-arg-type-test-ccodegen!
     wt
     (lambda (param)
       (list "gw_wcp_p(" (gw:param-get-scm-name param) ")")))

    (gw:type-set-pre-call-arg-ccodegen!
     wt
     (lambda (param)
       (let* ((scm-name (gw:param-get-scm-name param))
              (c-name (gw:param-get-c-name param)))
         (list c-name " = "scm-name ";\n"))))
    
    (gw:type-set-call-ccodegen! wt standard-c-call-gen)
    
    (add-standard-result-handlers!
     wt
     (lambda (scm-name c-name)
       (list scm-name " = " c-name ";\n"))))

  (gw:wrap-function
   m
   'gw:wct?
   '<gw:bool> "gw_wct_p" '((<gw:scm> obj))
   "Is obj a gw:wct?")
  
  (gw:wrap-function
   m
   'gw:wcp?
   '<gw:bool> "gw_wcp_p" '((<gw:scm> obj))
   "Is obj a gw:wcp?")
  
  (gw:wrap-function
   m
   'gw:wcp-is-of-type?
   '<gw:bool> "gw_wcp_is_of_type_p" '((<gw:wct> type) (<gw:wcp> wcp))
   "Returns #f iff the given wcp is not of the type specified.  type must be a
g-wrap wrapped c type object, usually available via global bindings.  For
example (gw:wcp-is-a? <gw:void*> foo)")

  (gw:wrap-function
   m
   'gw:wcp-coerce
   '<gw:wcp> "gw_wcp_coerce" '((<gw:wcp> wcp) (<gw:wct> new-type))
   "Coerce the given wcp to new-type.  This can be dangerous, so be careful.")

  (let ((nnt (gw:wrap-non-native-type m '<gw:void*> "void *" "const void *")))
    #t)
  
  )
