; Block all reductions (e.g. needed when quoting under a 'delay')
(define ct-blockAll
  (make-thread-cell #f))

(define (ct-isBlockAll)
  (thread-cell-ref ct-blockAll))

(define (ct-setBlockAll x)
  (thread-cell-set! ct-blockAll x))

; Check encodings of normal forms
(define (ct-isDataCon val)
  (and (vector? val) (>= (vector-ref val 0) 0)))
(define (ct-isConstant val)
  (or (number? val) (string? val) (char? val)
      (and (vector? val) (<= (vector-ref val 0) -100))))
(define (ct-isTypeCon val)
  (and (vector? val) (= (vector-ref val 0) -1)))
(define (ct-isBlocked val)
  (and (vector? val) (= (vector-ref val 0) -2)))
(define (ct-isPi val)
  (and (vector? val) (= (vector-ref val 0) -3)))
(define (ct-isTypeMatchable val)
  (or (ct-isTypeCon val) (ct-isPi val)))
(define (ct-isDelay val)
  (and (vector? val) (= (vector-ref val 0) -4)))
(define (ct-isForce val)
  (and (vector? val) (= (vector-ref val 0) -5)))
(define (ct-isErased val)
  (and (vector? val) (= (vector-ref val 0) -6)))
(define (ct-isType val)
  (and (vector? val) (= (vector-ref val 0) -7)))
(define (ct-isLambda val)
  (and (vector? val) (= (vector-ref val 0) -8)))
(define (ct-isTopLambda val)
  (and (vector? val) (= (vector-ref val 0) -9)))

; A function might be a blocked application, which is represented as
; a vector (-1, name, args), so make a new vector extending args
; (or a meta, which is a vector (-10, name, args))
(define (ct-addArg f a)
    (if (vector? f)
        (vector (vector-ref f 0) (vector-ref f 1)
             (append (vector-ref f 2) (list a)))
        (vector -11 f (list a))))

; to apply a function, either run it if it is a function, or add
; a blocked argument if it's stuck
(define (ct-app f a)
   (cond
     [(ct-isTopLambda f) ((vector-ref f 2) a)]
     [(ct-isLambda f) ((vector-ref f 1) a)]
     [(procedure? f) (f a)]
     [else (ct-addArg f a)]))

; force a delayed evaluation
(define (ct-doForce arg default)
    (if (ct-isDelay arg)
        ((vector-ref arg 4))
        default))

; primitives
(define (ct-toSignedInt x bits)
  (if (bitwise-bit-set? x bits)
      (bitwise-ior x (arithmetic-shift (- 1) bits))
      (bitwise-and x (sub1 (arithmetic-shift 1 bits)))))

(define (ct-toUnsignedInt x bits)
  (bitwise-and x (sub1 (arithmetic-shift 1 bits))))

(define ct-u+ (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toUnsignedInt (+ xval yval) bits)))))
(define ct-u- (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toUnsignedInt (- xval yval) bits)))))
(define ct-u* (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toUnsignedInt (* xval yval) bits)))))
(define ct-u/ (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toUnsignedInt (quotient xval yval) bits)))))

(define ct-s+ (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toSignedInt (+ xval yval) bits)))))
(define ct-s- (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toSignedInt (- xval yval) bits)))))
(define ct-s* (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toSignedInt (* xval yval) bits)))))
(define ct-s/ (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toSignedInt (quotient xval yval) bits)))))

(define ct+ (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (+ xval yval)))))
(define ct- (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (- xval yval)))))
(define ct* (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (* xval yval)))))
(define ct/ (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (quotient xval yval)))))

(define ct-mod (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (remainder xval yval)))))

(define ct-neg (lambda (x)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))]
          (vector tag (- xval)))))

(define ct-bits-shl-signed (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ct-toSignedInt (shl xval yval) bits)))))
(define ct-bits-shl (lambda (x y bits)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (remainder (ash xval yval) (ash 1 bits))))))
(define ct-shl (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ash xval yval)))))

(define ct-shr (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (ash xval (- yval))))))

(define ct-and (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (logand xval yval)))))
(define ct-or (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (logor xval yval)))))
(define ct-xor (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (vector tag (logxor xval yval)))))

(define ct-string-ref (lambda (x i)
    (let [(ival (vector-ref i 1))]
         (if (and (>= ival 0) (< ival (string-length x)))
             (string-ref x ival)
             '()))))
(define ct-string-cons (lambda (x y) (string-append (string x) y)))
(define ct-string-reverse (lambda (x)
  (list->string (reverse (string->list x)))))
(define (ct-string-substr offin lenin s)
    (let* [(off (vector-ref offin 1))
           (len (vector-ref lenin 1))
           (l (string-length s))
           (b (max 0 off))
           (x (max 0 len))
           (end (min l (+ b x)))]
          (if (> b l)
              ""
              (substring s b end))))

; Don't wrap the result for bool results, we do that in Builtins.idr
(define ct< (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (< xval yval))))
(define ct<= (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (<= xval yval))))
(define ct= (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (= xval yval))))
(define ct>= (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (>= xval yval))))
(define ct> (lambda (x y)
    (let [(tag (vector-ref x 0))
          (xval (vector-ref x 1))
          (yval (vector-ref y 1))]
          (> xval yval))))

; casts
; when targetting integers, we add the Vector on the Idris side

(define ct-cast-num
  (lambda (x)
    (if (number? x) x 0)))
(define destroy-prefix
  (lambda (x)
    (cond
      ((equal? x "") "")
      ((equal? (string-ref x 0) #\#) "")
      (else x))))

(define ct-cast-string-double
  (lambda (x)
    (exact->inexact (cast-num (string->number (destroy-prefix x))))))

(define ct-cast-char-boundedInt
  (lambda (x y)
    (ct-toSignedInt (char->integer x) y)))

(define ct-cast-char-boundedUInt
  (lambda (x y)
    (ct-toUnsignedInt (char->integer x) y)))

(define ct-cast-int-char
  (lambda (xin)
    (let [(x (vector-ref xin 1))]
      (if (or
          (and (>= x 0) (<= x #xd7ff))
          (and (>= x #xe000) (<= x #x10ffff)))
        (integer->char x)
        (integer->char 0)))))

(define ct-cast-signed
  (lambda (xin bits)
    (let [(x (vector-ref xin 1))]
      (ct-toSignedInt x bits))))

(define ct-cast-unsigned
  (lambda (xin bits)
    (let [(x (vector-ref xin 1))]
      (ct-toUnsignedInt x bits))))

(define ct-cast-string-int
  (lambda (x)
    (exact-truncate (cast-num (string->number (destroy-prefix x))))))

(define ct-cast-string-boundedInt
  (lambda (x y)
    (blodwen-toSignedInt (cast-string-int x) y)))

(define ct-cast-string-boundedUInt
  (lambda (x y)
    (blodwen-toUnsignedInt (cast-string-int x) y)))

(define ct-cast-number-string
  (lambda (xin)
    (let [(x (vector-ref xin 1))]
      (number->string x))))

(define ct-exact-truncate
  (lambda (x)
    (inexact->exact (truncate x))))

(define ct-exact-truncate-boundedInt
  (lambda (x y)
    (ct-toSignedInt (exact-truncate x) y)))

(define ct-exact-truncate-boundedUInt
  (lambda (x y)
    (ct-toUnsignedInt (exact-truncate x) y)))

(define ct-int-double
  (lambda (xin)
    (let [(x (vector-ref xin 1))]
      (exact->inexact x))))
