#lang typed/racket/base

(require "assemble-helpers.rkt"
         "../compiler/il-structs.rkt"
         "../compiler/lexical-structs.rkt"
         "../compiler/kernel-primitives.rkt"
         racket/string
         racket/list)

(provide open-code-kernel-primitive-procedure)



(: open-code-kernel-primitive-procedure (CallKernelPrimitiveProcedure -> String))
(define (open-code-kernel-primitive-procedure op)
  (let*: ([operator : KernelPrimitiveName/Inline (CallKernelPrimitiveProcedure-operator op)]
          [operands : (Listof String) (map assemble-oparg (CallKernelPrimitiveProcedure-operands op))]
          [checked-operands : (Listof String)
                            (map (lambda: ([dom : OperandDomain]
					   [pos : Natural]
					   [rand : String]
					   [typecheck? : Boolean])
				   (maybe-typecheck-operand operator dom pos rand typecheck?))
                                 (CallKernelPrimitiveProcedure-expected-operand-types op)
                                 (build-list (length operands) (lambda: ([i : Natural]) i))
                                 operands
                                 (CallKernelPrimitiveProcedure-typechecks? op))])
        (case operator
          [(+)
           (cond [(empty? checked-operands)
                  "0"]
                 [else
                  (string-append "(" (string-join checked-operands " + ") ")")])]
          
          [(-)
           (cond [(empty? (rest checked-operands))
                  (format "(-(~a))" (first checked-operands))]
                 [else
                  (string-append "(" (string-join checked-operands "-") ")")])]
          
          [(*)
           (cond [(empty? checked-operands)
                  "1"]
                 [else
                  (string-append "(" (string-join checked-operands "*") ")")])]

          [(/)
           (string-append "(" (string-join checked-operands "/") ")")]

          [(add1)
           (format "(~a + 1)" (first checked-operands))]
          
          [(sub1)
           (format "(~a - 1)" (first checked-operands))]
          
          [(<)
           (assemble-chain "<" checked-operands)]

          [(<=)
           (assemble-chain "<=" checked-operands)]
          
          [(=)
           (assemble-chain "===" checked-operands)]
          
          [(>)
           (assemble-chain ">" checked-operands)]
          
          [(>=)
           (assemble-chain ">=" checked-operands)]
          
          [(cons)
           (format "[~a, ~a]" (first checked-operands) (second checked-operands))]

          [(car)
           (format "(~a)[0]" (first checked-operands))]
          
          [(cdr)
           (format "(~a)[1]" (first checked-operands))]
          
          [(list)
           (let loop ([checked-operands checked-operands])
             (cond
               [(empty? checked-operands)
                "RUNTIME.NULL"]
               [else
                (format "[~a,~a]" (first checked-operands) (loop (rest checked-operands)))]))]
          
          [(null?)
           (format "(~a === RUNTIME.NULL)" (first checked-operands))]

          [(not)
           (format "(~a === false)" (first checked-operands))]
          
          [(eq?)
           (format "(~a === ~a)" (first checked-operands) (second checked-operands))])))


(: assemble-chain (String (Listof String) -> String))
(define (assemble-chain rator rands)
  (string-append "("
                 (string-join (let: loop : (Listof String) ([rands : (Listof String) rands])
                                (cond
                                  [(empty? rands)
                                   '()]
                                  [(empty? (rest rands))
                                   '()]
                                  [else
                                   (cons (format "(~a ~a ~a)" (first rands) rator (second rands))
                                         (loop (rest rands)))]))
                              "&&")
                 ")"))





(: assemble-domain-check (Symbol OperandDomain String Natural -> String))
(define (assemble-domain-check caller domain operand-string pos)
  (cond
    [(eq? domain 'any)
     operand-string]
    [else
     (let: ([test-string : String
                         (case domain
                           [(number)
                            (format "(typeof(~a) === 'number')"
                                    operand-string)]             
                           [(string)
                            (format "(typeof(~a) === 'string')"
                                    operand-string)]
                           [(list)
                            (format "(~a === [] || (typeof(~a) === 'object' && (~a).length === 2))"
                                    operand-string operand-string operand-string)]
                           [(pair)
                            (format "(typeof(~a) === 'object' && (~a).length === 2)"
                                    operand-string operand-string)]
                           [(box)
                            (format "(typeof(~a) === 'object' && (~a).length === 1)"
                                    operand-string operand-string)])])
           (format "((~a) ? (~a) : RUNTIME.raise(MACHINE, new Error('~a: expected ' + ~s + ' as argument ' + ~s + ' but received ' + ~a)))"
                   test-string
                   operand-string
		   caller
                   (symbol->string domain)
                   (add1 pos)
                   operand-string))]))


(: maybe-typecheck-operand (Symbol OperandDomain Natural String Boolean -> String))
;; Adds typechecks if we can't prove that the operand is of the required type.
(define (maybe-typecheck-operand caller domain-type position operand-string typecheck?)
  (cond
    [typecheck?
     (assemble-domain-check caller domain-type operand-string position)]
    [else
     operand-string]))