module Z3

import Prelude
import Node

ptype Context
-- Flag if optimizer or solver
ptype Solver : Bool → U
ptype Arith
ptype Z3Bool
ptype Model
ptype Z3

pfunc initZ3 : Promise Z3 := `require('z3-solver').init()`
pfunc z3ResetMemory : Promise Z3 := `(z3) => z3.Z3.reset_memory()`
pfunc initContext : Z3 → String → Context := `(z3, name) => z3.Context(name)`

pfunc newSolver : Context → Solver False := `(Context) => new Context.Solver()`
pfunc newOptimizer : Context → Solver True := `(Context) => new Context.Optimize()`


-- These probably should be IO or Promise
pfunc freshInt : {{Context}} → Promise Arith := `(Context) => Promise.resolve(Context.Int.fresh())`
pfunc z3const : {{Context}} → String → Arith := `(Context, name) => Context.Int.const(name)`
pfunc arith_add : Arith → Arith → Arith := `(a,b) => a.add(b)`
pfunc z3int : {{Context}} → Int → Arith := `(Context,a) => Context.Int.val(a)`

-- We can't overload operators for these because of the return type

pfunc z3eq : Arith → Arith → Z3Bool := `(a,b) => a.eq(b)`
pfunc z3ge : Arith → Arith → Z3Bool := `(a,b) => a.ge(b)`

pfunc z3add : ∀ b. Solver b → Z3Bool → Promise Unit := `(_, solver, exp) => Promise.resolve(solver.add(exp))`
pfunc z3minimize : Solver True → Arith → Promise Unit := `(solver, exp) => Promise.resolve(solver.minimize(exp))`

pfunc z3check : ∀ b. Solver b → Promise String := `(b, solver) => solver.check()`

pfunc getModel : ∀ b. Solver b → Promise Model := `(b, solver) => Promise.resolve(solver.model())`

pfunc getInt : {{Model}} → Arith → Int := `(model, exp) => {
  let n = +(model.eval(exp).toString())
  return isNaN(n) ? 0 : n
}`

instance Add Arith where
  a + b = arith_add a b
