#lang scheme/base
(require (prefix-in dbd: (planet schematics/spgsql/spgsql))
         (prefix-in dbd: (planet schematics/spgsql/private/connection))
         (prefix-in dbd: (planet schematics/spgsql/private/sql-data))
         scheme/class
         scheme/match
         (planet bzlib/base)
         (planet bzlib/dbi)
         srfi/19
         "array.ss"
         )
(define-struct (spgsql-handle handle) (t2s))
(define (filter-t2s/attrs attrs)
  (define (helper rest acc t2s)
    (cond ((null? rest) (values t2s (reverse acc)))
          ((equal? (car rest) '#:t2s)
           (cond ((null? (cdr rest))
                  (error 'connect "invalid attribute ~a - no value" (car rest)))
                 ((not (procedure? (cadr rest))) 
                  (error 'connect "#:t2s must take a procedure of (-> string? string?)"))
                 (else
                  (helper (cddr rest) acc (cadr rest)))))
          (else 
           (helper (cdr rest) (cons (car rest) acc) t2s))))
  (helper attrs '() #f))
(define (spgsql-connect driver . attrs)
  (let-values (((loader attrs)
                (filter-file-loader/attrs attrs)))
    (let-values (((t2s attrs)
                  (filter-t2s/attrs attrs)))
      (let ((handle (make-spgsql-handle driver
                                        (apply* dbd:connect attrs)
                                        (make-immutable-hash-registry)
                                        0
                                        t2s)))
        (load-files! handle (if (list? loader) loader
                                (list loader)))
        handle))))
(define (spgsql-disconnect dbh)
  (send (handle-conn dbh) disconnect))
(define (spgsql-query dbh stmt (args '()))
  (if-it (registry-ref (handle-query dbh) stmt)
         (convert-result
          (send (handle-conn dbh) query
                (send (handle-conn dbh) bind-prepared-statement
                      (prepared-inner it)
                      (map cell->sql-cell (phq-map-values (prepared-query it) args)))))
         (begin
           (spgsql-prepare dbh stmt stmt)
           (spgsql-query dbh stmt args))))
(define (spgsql-query/effect dbh stmt (args '())) 
  (define (helper rs) 
    (if (dbd:SimpleResult? rs) 
        (make-effect #f 
                     (if (spgsql-handle-t2s dbh) 
                         (if-it (insert-statement? dbh stmt) 
                                (cell/false dbh
                                            "select currval(?seq::regclass)"
                                            `((seq . ,((spgsql-handle-t2s dbh) it))))
                                #f)
                            #f)
                     #f 
                     #f 
                     (dbd:SimpleResult-command rs) 
                     #f)
        rs))
  (helper (spgsql-query dbh stmt args)))
(define (spgsql-query/effect-set dbh stmt (args '()))
  (result-set-or-effect->result-set (spgsql-query/effect dbh stmt args)))
(define (spgsql-t2s:table_t_table_id_seq table)
  (define (id-name table) 
    (regexp-replace #px"(_t)?$" table "_id_seq"))
  (format "~a_~a" table (id-name table)))
(define (insert-statement? handle stmt) 
  (define (helper stmt) 
    (if-it (regexp-match #px"(?i:insert\\s+(into\\s+)(\\w+))" stmt)
           (caddr it)
           #f))
  (helper (phq-query (prepared-query (registry-ref (handle-query handle) stmt)))))
(define (spgsql-prepare dbh key stmt)
  (let ((query (make-place-holder-query default-converter stmt)))
    (registry-set! (handle-query dbh)
                   key
                   (make-prepared query
                                  (send (handle-conn dbh) prepare (phq-converted query))))))
(define (convert-result rs)
  (if (dbd:SimpleResult? rs)
      rs
      (cons (FieldInfos->columns (dbd:Recordset-info rs))
            (data->records (dbd:Recordset-data rs)))))
(define (FieldInfos->columns fis)
  (map dbd:FieldInfo-name fis))
(define (data->records data)
  (map (lambda (rec)
         (map sql-cell->cell (vector->list rec)))
       data))
(define (sql-cell->cell cell)
  (cond ((dbd:sql-null? cell) '())
        ((dbd:sql-timestamp? cell) (sql-timestamp->date cell))
        ((dbd:sql-date? cell) (sql-date->date cell))
                                                (else cell)))
(define (cell->sql-cell cell)
  (cond ((null? cell) dbd:sql-null)
        ((date? cell) (date->sql-timestamp cell))
                                                ((list? cell) (cell->pg-array cell))
        ((vector? cell) (cell->sql-cell (vector->list cell)))
        (else cell)))
(define (cell->pg-array x)
  (define (helper rest acc)
    (cond ((null? rest) (string-append "{" (string-join acc ",") "}"))
          ((string? (car rest))
           (helper (cdr rest) (cons (format "~s" (car rest)) acc)))
          ((bytes? (car rest)) (helper (cons (bytes->string/utf-8 (car rest)) (cdr rest))
                                       acc))
          ((number? (car rest)) (helper (cdr rest) (cons (number->string (car rest)) acc)))
          (else (error 'cell->pg-array "unsupported type ~a" (car rest)))))
  (helper x '()))
(define (sql-date->date date)
  (match date
         ((struct dbd:sql-date (year month day))
          (make-date 0 0 0 0 day month day 0))))
(define (sql-timestamp->date ts)
  (match ts
         ((struct dbd:sql-timestamp (year month day hour minute second nanosecond tz))
          (make-date nanosecond second minute hour day month year tz))))
(define (date->sql-timestamp d)
  (dbd:make-sql-timestamp (date-year d)
                          (date-month d)
                          (date-day d)
                          (date-hour d)
                          (date-minute d)
                          (date-second d)
                          (date-nanosecond d)
                          (date-zone-offset d)))
(registry-set! drivers 'spgsql/pass-thru-effect
               (make-driver spgsql-connect
                            spgsql-disconnect
                            spgsql-query
                            spgsql-prepare
                            default-begin
                            default-commit
                            default-rollback))
(registry-set! drivers 'spgsql/effect
               (make-driver spgsql-connect
                            spgsql-disconnect
                            spgsql-query/effect
                            spgsql-prepare
                            default-begin
                            default-commit
                            default-rollback))
(registry-set! drivers 'spgsql/effect-set
               (make-driver spgsql-connect
                            spgsql-disconnect
                            spgsql-query/effect-set
                            spgsql-prepare
                            default-begin
                            default-commit
                            default-rollback))
(registry-set! drivers 'spgsql (registry-ref drivers 'spgsql/effect))
(provide (rename-out (dbd:SimpleResult SimpleResult)
                     (dbd:SimpleResult-command SimpleResult-command))
         )
(provide/contract 
 (spgsql-t2s:table_t_table_id_seq (-> string? string?))
 )