{-# language Rank2Types #-}
{-# language ScopedTypeVariables #-}
{-# language DataKinds #-}
{-# language TypeOperators #-}
{-# language TypeFamilies #-}
{-# language FlexibleInstances #-}
{-# language FlexibleContexts #-}
{-# language MultiParamTypeClasses #-}
{-# language PolyKinds #-}
{-# language GADTs #-}
{-# language ConstraintKinds #-}
{-# language GeneralizedNewtypeDeriving #-}

module Feldspar.Software.Verify.Primitive where

import Feldspar.Sugar
import Feldspar.Representation
import Feldspar.Software.Primitive
import Feldspar.Software.Expression
import Feldspar.Software.Representation hiding (Nil)
import Feldspar.Software.Verify.Command
import Feldspar.Verify.Arithmetic

import Feldspar.Verify.Monad (Verify)
import qualified Feldspar.Verify.FirstOrder as FO
import qualified Feldspar.Verify.Monad as V
import qualified Feldspar.Verify.SMT as SMT
import qualified Feldspar.Verify.Abstract as A

import Data.Struct
import qualified Data.Map.Strict as Map

import qualified Control.Monad.RWS.Strict as S

import qualified SimpleSMT as SMT hiding (not, declareFun)

import qualified Language.Embedded.Expression as Imp
import qualified Language.Embedded.Imperative.CMD as Imp

import qualified Data.Bits as Bits
import qualified Data.Complex as Complex
import Data.Constraint hiding (Sub)
import Data.Int
import Data.Word
import Data.Typeable

import Language.Syntactic

import GHC.Stack

--------------------------------------------------------------------------------
-- *
--------------------------------------------------------------------------------

newtype Symbolic a = Symbolic { unSymbolic :: Rat }
  deriving (Eq, Ord, Show, V.TypedSExpr, V.SMTOrd)

instance V.Fresh (Symbolic a)
  where
    fresh = V.freshSExpr

symbCast :: Symbolic a -> Symbolic b
symbCast = Symbolic . unSymbolic

symbFun :: SymbParam a => String -> [Symbolic a] -> Symbolic a
symbFun name (args :: [Symbolic a]) = V.fromSMT $
  SMT.fun (symbType (undefined :: a) ++ "-" ++ name) (map V.toSMT args)

fromComplexConstant :: (RealFrac a, SymbParam b) => Complex.Complex a -> Symbolic b
fromComplexConstant c = symbFun "complex" [real, imag]
  where
    real = Symbolic $ fromRational $ toRational $ Complex.realPart c
    imag = Symbolic $ fromRational $ toRational $ Complex.imagPart c

--------------------------------------------------------------------------------

data SymbFloat
data SymbDouble
data SymbComplexFloat
data SymbComplexDouble

class SymbParam a
  where
    symbType :: a -> String

instance SymbParam SymbFloat  where symbType _ = "float"
instance SymbParam SymbDouble where symbType _ = "double"
instance SymbParam SymbComplexFloat  where symbType _ = "cfloat"
instance SymbParam SymbComplexDouble where symbType _ = "cdouble"

instance SymbParam a => Num (Symbolic a)
  where
    fromInteger = Symbolic . fromInteger
    x + y  = symbFun "+" [x, y]
    x - y  = symbFun "-" [x, y]
    x * y  = symbFun "*" [x, y]
    abs    = smtAbs
    signum = smtSignum

instance SymbParam a => Fractional (Symbolic a) where
  fromRational = Symbolic . fromRational
  x / y = symbFun "/" [x, y]

instance SymbParam a => Floating (Symbolic a) where
  pi      = fromRational (toRational pi)
  exp x   = symbFun "exp" [x]
  log x   = symbFun "log" [x]
  sqrt x  = symbFun "sqrt" [x]
  x ** y  = symbFun "pow" [x, y]
  sin x   = symbFun "sin" [x]
  cos x   = symbFun "cos" [x]
  tan x   = symbFun "tan" [x]
  asin x  = symbFun "asin" [x]
  acos x  = symbFun "acos" [x]
  atan x  = symbFun "atan" [x]
  sinh x  = symbFun "sinh" [x]
  cosh x  = symbFun "cosh" [x]
  tanh x  = symbFun "tanh" [x]
  asinh x = symbFun "asinh" [x]
  acosh x = symbFun "acosh" [x]
  atanh x = symbFun "atanh" [x]

--------------------------------------------------------------------------------

class Floating a => Complex a
  where
    type RealPart a
    complex   :: RealPart a -> RealPart a -> a
    polar     :: RealPart a -> RealPart a -> a
    real      :: a -> RealPart a
    imag      :: a -> RealPart a
    magnitude :: a -> RealPart a
    phase     :: a -> RealPart a
    conjugate :: a -> a

instance Complex (V.SMTExpr Prim (Complex.Complex Float))
  where
    type RealPart (V.SMTExpr Prim (Complex.Complex Float)) = V.SMTExpr Prim Float
    complex   (Float x) (Float y) = ComplexFloat (complex x y)
    polar     (Float x) (Float y) = ComplexFloat (polar x y)
    real      (ComplexFloat x)    = Float (real x)
    imag      (ComplexFloat x)    = Float (imag x)
    magnitude (ComplexFloat x)    = Float (magnitude x)
    phase     (ComplexFloat x)    = Float (phase x)
    conjugate (ComplexFloat x)    = ComplexFloat (conjugate x)

instance Complex (V.SMTExpr Prim (Complex.Complex Double))
  where
    type RealPart (V.SMTExpr Prim (Complex.Complex Double)) = V.SMTExpr Prim Double
    complex   (Double x) (Double y) = ComplexDouble (complex x y)
    polar     (Double x) (Double y) = ComplexDouble (polar x y)
    real      (ComplexDouble x)     = Double (real x)
    imag      (ComplexDouble x)     = Double (imag x)
    magnitude (ComplexDouble x)     = Double (magnitude x)
    phase     (ComplexDouble x)     = Double (phase x)
    conjugate (ComplexDouble x)     = ComplexDouble (conjugate x)

witnessComplex :: (SoftwarePrimType a, SoftwarePrimType (Complex.Complex a)) =>
  Prim (Complex.Complex a) ->
  Dict ( Floating (V.SMTExpr Prim a)
       , Complex  (V.SMTExpr Prim (Complex.Complex a))
       , RealPart (V.SMTExpr Prim (Complex.Complex a)) ~ V.SMTExpr Prim a)
witnessComplex (_ :: Prim (Complex.Complex a)) =
  case softwareRep :: SoftwarePrimTypeRep (Complex.Complex a) of
    ComplexFloatST  -> Dict
    ComplexDoubleST -> Dict

witnessFractional :: (SoftwarePrimType a, Fractional a) =>
  Prim a ->
  Dict (Floating (V.SMTExpr Prim a))
witnessFractional (_ :: Prim a) = case softwareRep :: SoftwarePrimTypeRep a of
    FloatST         -> Dict
    DoubleST        -> Dict
    ComplexFloatST  -> Dict
    ComplexDoubleST -> Dict

witnessIntegral :: (SoftwarePrimType a, Integral a) =>
  Prim a ->
  Dict (Integral (V.SMTExpr Prim a), Bits (V.SMTExpr Prim a))
witnessIntegral (_ :: Prim a) = case softwareRep :: SoftwarePrimTypeRep a of
  Int8ST   -> Dict
  Int16ST  -> Dict
  Int32ST  -> Dict
  Int64ST  -> Dict
  Word8ST  -> Dict
  Word16ST -> Dict
  Word32ST -> Dict
  Word64ST -> Dict

witnessBits :: (SoftwarePrimType a, Bits.Bits a) =>
  Prim a ->
  Dict ( Num a
       , Integral (V.SMTExpr Prim a)
       , Bits (V.SMTExpr Prim a))
witnessBits (_ :: Prim a) = case softwareRep :: SoftwarePrimTypeRep a of
  Int8ST   -> Dict
  Int16ST  -> Dict
  Int32ST  -> Dict
  Int64ST  -> Dict
  Word8ST  -> Dict
  Word16ST -> Dict
  Word32ST -> Dict
  Word64ST -> Dict

--------------------------------------------------------------------------------

toRat :: (SoftwarePrimType a, Fractional a) => V.SMTExpr Prim a -> Rat
toRat (x :: V.SMTExpr Prim a) = case softwareRep :: SoftwarePrimTypeRep a of
  FloatST         -> let Float         (Symbolic y) = x in y
  DoubleST        -> let Double        (Symbolic y) = x in y
  ComplexFloatST  -> let ComplexFloat  (Symbolic y) = x in y
  ComplexDoubleST -> let ComplexDouble (Symbolic y) = x in y

fromRat :: forall a. (SoftwarePrimType a, Num a) => Rat -> V.SMTExpr Prim a
fromRat x = case softwareRep :: SoftwarePrimTypeRep a of Int8ST -> Int8 (f2i x)
  where
    f2i :: forall s w. (Sign s, Width w) => Rat -> BV s w
    f2i (Rat x) = BV $ SMT.List [SMT.fam "int2bv" [width (undefined :: w)], SMT.fun "to_int" [x]]

i2n :: forall a b.
  ( V.SMTEval Prim a, SoftwarePrimType a, Integral a
  , V.SMTEval Prim b, SoftwarePrimType b, Num b) =>
  V.SMTExpr Prim a ->
  V.SMTExpr Prim b
i2n x = toBV x $ case softwareRep :: SoftwarePrimTypeRep b of
  Int8ST   -> Int8   . i2i
  Int16ST  -> Int16  . i2i
  Int32ST  -> Int32  . i2i
  Int64ST  -> Int64  . i2i
  Word8ST  -> Word8  . i2i
  Word16ST -> Word16 . i2i
  Word32ST -> Word32 . i2i
  Word64ST -> Word64 . i2i
  FloatST  -> Float  . Symbolic . i2f
  DoubleST -> Double . Symbolic . i2f
  ComplexFloatST  -> ComplexFloat  . Symbolic . i2f
  ComplexDoubleST -> ComplexDouble . Symbolic . i2f
  where
    toBV :: forall a b. (V.SMTEval Prim a, SoftwarePrimType a, Integral a) =>
      V.SMTExpr Prim a -> (forall s w. (Sign s, Width w) => BV s w -> b) -> b
    toBV (x :: V.SMTExpr Prim a) k = case softwareRep :: SoftwarePrimTypeRep a of
      Int8ST   -> let Int8   y = x in k y
      Int16ST  -> let Int16  y = x in k y
      Int32ST  -> let Int32  y = x in k y
      Int64ST  -> let Int64  y = x in k y
      Word8ST  -> let Word8  y = x in k y
      Word16ST -> let Word16 y = x in k y
      Word32ST -> let Word32 y = x in k y
      Word64ST -> let Word64 y = x in k y

    i2f :: (Sign s, Width w) => BV s w -> Rat
    i2f (BV x) = Rat (SMT.fun "to_real" [SMT.fun "bv2int" [x]])

    i2i :: forall s1 w1 s2 w2. (Sign s1, Width w1, Sign s2, Width w2) => BV s1 w1 -> BV s2 w2
    i2i x = case compare m n of
      LT | isSigned x -> V.fromSMT (SMT.signExtend (n-m) (V.toSMT x))
         | otherwise  -> V.fromSMT (SMT.zeroExtend (n-m) (V.toSMT x))
      EQ -> V.fromSMT (V.toSMT x)
      GT -> V.fromSMT (SMT.extract (V.toSMT x) (n-1) 0)
      where
        m = width (undefined :: w1)
        n = width (undefined :: w2)

--------------------------------------------------------------------------------

class SymbParam a => SymbComplex a
  where
    type SymbRealPart a

instance SymbComplex SymbComplexFloat
  where
    type SymbRealPart SymbComplexFloat  = SymbFloat

instance SymbComplex SymbComplexDouble
  where
    type SymbRealPart SymbComplexDouble = SymbDouble

instance SymbComplex a => Complex (Symbolic a)
  where
    type RealPart (Symbolic a) = Symbolic (SymbRealPart a)
    complex x y = symbFun "complex" [symbCast x, symbCast y]
    polar x y   = symbFun "polar" [symbCast x, symbCast y]
    real x      = symbCast (symbFun "real" [x])
    imag x      = symbCast (symbFun "imag" [x])
    magnitude x = symbCast (symbFun "magnitude" [x])
    phase x     = symbCast (symbFun "phase" [x])
    conjugate x = symbFun "conjugate" [x]

--------------------------------------------------------------------------------

declareSymbFun :: SymbParam a => String -> a ->
  [SMT.SExpr] -> SMT.SExpr -> SMT.SMT ()
declareSymbFun name (_ :: a) args res =
  S.void $ SMT.declareFun (symbType (undefined :: a) ++ "-" ++ name) args res

declareSymbArith :: SymbParam a => a -> SMT.SMT ()
declareSymbArith x = do
  declareSymbFun "+" x     [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "-" x     [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "*" x     [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "/" x     [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "exp" x   [SMT.tReal] SMT.tReal
  declareSymbFun "log" x   [SMT.tReal] SMT.tReal
  declareSymbFun "sqrt" x  [SMT.tReal] SMT.tReal
  declareSymbFun "pow" x   [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "sin" x   [SMT.tReal] SMT.tReal
  declareSymbFun "cos" x   [SMT.tReal] SMT.tReal
  declareSymbFun "tan" x   [SMT.tReal] SMT.tReal
  declareSymbFun "asin" x  [SMT.tReal] SMT.tReal
  declareSymbFun "acos" x  [SMT.tReal] SMT.tReal
  declareSymbFun "atan" x  [SMT.tReal] SMT.tReal
  declareSymbFun "sinh" x  [SMT.tReal] SMT.tReal
  declareSymbFun "cosh" x  [SMT.tReal] SMT.tReal
  declareSymbFun "tanh" x  [SMT.tReal] SMT.tReal
  declareSymbFun "asinh" x [SMT.tReal] SMT.tReal
  declareSymbFun "acosh" x [SMT.tReal] SMT.tReal
  declareSymbFun "atanh" x [SMT.tReal] SMT.tReal

declareSymbComplex :: SymbParam a => a -> SMT.SMT ()
declareSymbComplex x = do
  declareSymbArith x
  declareSymbFun "complex" x   [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "polar" x     [SMT.tReal, SMT.tReal] SMT.tReal
  declareSymbFun "real" x      [SMT.tReal] SMT.tReal
  declareSymbFun "imag" x      [SMT.tReal] SMT.tReal
  declareSymbFun "magnitude" x [SMT.tReal] SMT.tReal
  declareSymbFun "phase" x     [SMT.tReal] SMT.tReal
  declareSymbFun "conjugate" x [SMT.tReal] SMT.tReal

declareFeldsparGlobals :: SMT.SMT ()
declareFeldsparGlobals = do
  declareSymbArith   (undefined :: SymbFloat)
  declareSymbArith   (undefined :: SymbDouble)
  declareSymbComplex (undefined :: SymbComplexFloat)
  declareSymbComplex (undefined :: SymbComplexDouble)
  SMT.declareFun "skolem-int8"   [] (SMT.tBits 8)
  SMT.declareFun "skolem-int16"  [] (SMT.tBits 16)
  SMT.declareFun "skolem-int32"  [] (SMT.tBits 32)
  SMT.declareFun "skolem-int64"  [] (SMT.tBits 64)
  SMT.declareFun "skolem-word8"  [] (SMT.tBits 8)
  SMT.declareFun "skolem-word16" [] (SMT.tBits 16)
  SMT.declareFun "skolem-word32" [] (SMT.tBits 32)
  SMT.declareFun "skolem-word64" [] (SMT.tBits 64)
  return ()

instance FO.Substitute Prim
  where
    type SubstPred Prim = SoftwarePrimType
    subst sub (Prim exp) = Prim (everywhereUp f exp)
      where
        f :: ASTF SoftwarePrimDomain a -> ASTF SoftwarePrimDomain a
        f (Sym (FreeVar x :&: ty)) = case FO.lookupSubst sub (Imp.ValComp x) of
          Imp.ValComp y -> Sym (FreeVar y :&: ty)
          Imp.ValRun  z -> Sym (Lit z :&: ty)
        f (Sym (ArrIx iarr :&: ty) :$ i) = Sym (ArrIx iarr' :&: ty) :$ i
          where iarr' = FO.lookupSubst sub iarr
        f x = x

instance FO.TypeablePred SoftwarePrimType
  where
    witnessTypeable Dict = Dict

--------------------------------------------------------------------------------

instance V.SMTEval Prim Bool where
  fromConstant = Bool . SMT.bool
  witnessOrd _ = Dict

instance V.SMTEval Prim Int8 where
  fromConstant = Int8 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-int8" [])

instance V.SMTEval Prim Int16 where
  fromConstant = Int16 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-int16" [])

instance V.SMTEval Prim Int32 where
  fromConstant = Int32 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-int32" [])

instance V.SMTEval Prim Int64 where
  fromConstant = Int64 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-int64" [])

instance V.SMTEval Prim Word8 where
  fromConstant = Word8 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-word8" [])

instance V.SMTEval Prim Word16 where
  fromConstant = Word16 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-word16" [])

instance V.SMTEval Prim Word32 where
  fromConstant = Word32 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-word32" [])

instance V.SMTEval Prim Word64 where
  fromConstant = Word64 . fromIntegral
  witnessNum _ = Dict
  witnessOrd _ = Dict
  skolemIndex  = V.fromSMT (SMT.fun "skolem-word64" [])

instance V.SMTEval Prim Float where
  fromConstant = Float . fromRational . toRational
  witnessOrd _ = Dict
  witnessNum _ = Dict

instance V.SMTEval Prim Double where
  fromConstant = Double . fromRational . toRational
  witnessOrd _ = Dict
  witnessNum _ = Dict

instance V.SMTEval Prim (Complex.Complex Float) where
  fromConstant = ComplexFloat . fromComplexConstant
  witnessNum _ = Dict

instance V.SMTEval Prim (Complex.Complex Double) where
  fromConstant = ComplexDouble . fromComplexConstant
  witnessNum _ = Dict

--------------------------------------------------------------------------------

instance V.SMTEval1 Prim where
  type Pred Prim = SoftwarePrimType
  newtype SMTExpr Prim Bool   = Bool SMT.SExpr
    deriving (Typeable)
  newtype SMTExpr Prim Float  = Float  (Symbolic SymbFloat)
    deriving (Typeable, Num, Fractional, Floating, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Double = Double (Symbolic SymbDouble)
    deriving (Typeable, Num, Fractional, Floating, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim (Complex.Complex Float) =
      ComplexFloat (Symbolic SymbComplexFloat)
    deriving (Typeable, Num, Fractional, Floating, V.TypedSExpr)
  newtype SMTExpr Prim (Complex.Complex Double) =
      ComplexDouble (Symbolic SymbComplexDouble)
    deriving (Typeable, Num, Fractional, Floating, V.TypedSExpr)
  newtype SMTExpr Prim Int8   = Int8   (BV Signed W8)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Int16  = Int16  (BV Signed W16)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Int32  = Int32  (BV Signed W32)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Int64  = Int64  (BV Signed W64)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Word8  = Word8  (BV Unsigned W8)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Word16 = Word16 (BV Unsigned W16)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Word32 = Word32 (BV Unsigned W32)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)
  newtype SMTExpr Prim Word64 = Word64 (BV Unsigned W64)
    deriving (Typeable, Num, Real, Enum, Integral, Bits, V.SMTOrd, V.TypedSExpr)

  eval (Prim exp :: Prim a) =
    simpleMatch (\(exp :&: ty) -> case softwarePrimWitType ty of
      Dict -> case V.witnessPred (undefined :: Prim a) of
        Dict -> verifyPrim exp) exp

  witnessPred (_ :: Prim a) = case softwareRep :: SoftwarePrimTypeRep a of
    BoolST   -> Dict
    Int8ST   -> Dict
    Int16ST  -> Dict
    Int32ST  -> Dict
    Int64ST  -> Dict
    Word8ST  -> Dict
    Word16ST -> Dict
    Word32ST -> Dict
    Word64ST -> Dict
    FloatST  -> Dict
    DoubleST -> Dict
    ComplexFloatST  -> Dict
    ComplexDoubleST -> Dict

instance V.SMTOrd (V.SMTExpr Prim Bool) where
  Bool x .<.  Bool y = SMT.not x SMT..&&. y
  Bool x .>.  Bool y = x SMT..&&. SMT.not y
  Bool x .<=. Bool y = SMT.not x SMT..||. y
  Bool x .>=. Bool y = x SMT..||. SMT.not y

instance V.TypedSExpr (V.SMTExpr Prim Bool) where
  smtType _ = SMT.tBool
  toSMT (Bool x) = x
  fromSMT x = Bool x

--------------------------------------------------------------------------------

verifyPrim :: forall a .
  (SoftwarePrimType (DenResult a), V.SMTEval Prim (DenResult a), HasCallStack) =>
  SoftwarePrim a ->
  Args (AST SoftwarePrimDomain) a ->
  V.Verify (V.SMTExpr Prim (DenResult a))
verifyPrim (FreeVar x) _ = peekValue x
verifyPrim (Lit x) _     = return (V.fromConstant x)
verifyPrim Add (x :* y :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) =
    S.liftM2 (+) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Sub (x :* y :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) =
    S.liftM2 (-) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Mul (x :* y :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) =
    S.liftM2 (*) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Neg (x :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) =
    fmap negate (V.eval (Prim x))
verifyPrim Abs (x :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) =
    fmap abs (V.eval (Prim x))
verifyPrim Sign (x :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) =
    fmap signum (V.eval (Prim x))
verifyPrim Div (x :* y :* Nil)
  | Dict <- witnessIntegral (undefined :: Prim (DenResult a)) =
    S.liftM2 div (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Mod (x :* y :* Nil)
  | Dict <- witnessIntegral (undefined :: Prim (DenResult a)) =
    S.liftM2 mod (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Quot (x :* y :* Nil)
  | Dict <- witnessIntegral (undefined :: Prim (DenResult a)) =
    S.liftM2 quot (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Rem (x :* y :* Nil)
  | Dict <- witnessIntegral (undefined :: Prim (DenResult a)) =
    S.liftM2 rem (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim FDiv (x :* y :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    S.liftM2 (/) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Pi Nil
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    return pi
verifyPrim Exp (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap exp (V.eval (Prim x))
verifyPrim Log (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap log (V.eval (Prim x))
verifyPrim Sqrt (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap sqrt (V.eval (Prim x))
verifyPrim Pow (x :* y :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    S.liftM2 (**) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Sin (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap sin (V.eval (Prim x))
verifyPrim Cos (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap cos (V.eval (Prim x))
verifyPrim Tan (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap tan (V.eval (Prim x))
verifyPrim Asin (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap asin (V.eval (Prim x))
verifyPrim Acos (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap acos (V.eval (Prim x))
verifyPrim Atan (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap atan (V.eval (Prim x))
verifyPrim Sinh (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap sinh (V.eval (Prim x))
verifyPrim Cosh (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap cosh (V.eval (Prim x))
verifyPrim Tanh (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap tanh (V.eval (Prim x))
verifyPrim Asinh (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap asinh (V.eval (Prim x))
verifyPrim Acosh (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap acosh (V.eval (Prim x))
verifyPrim Atanh (x :* Nil)
  | Dict <- witnessFractional (undefined :: Prim (DenResult a)) =
    fmap atanh (V.eval (Prim x))
verifyPrim Complex (x :* y :* Nil)
  | Dict <- witnessComplex (undefined :: Prim (DenResult a)) =
    S.liftM2 complex (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Polar (x :* y :* Nil)
  | Dict <- witnessComplex (undefined :: Prim (DenResult a)) =
    S.liftM2 polar (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim Real ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessComplex (undefined :: Prim b) =
    fmap real (V.eval (Prim x))
verifyPrim Imag ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessComplex (undefined :: Prim b) =
    fmap imag (V.eval (Prim x))
verifyPrim Magnitude ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessComplex (undefined :: Prim b) =
    fmap magnitude (V.eval (Prim x))
verifyPrim Phase ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessComplex (undefined :: Prim b) =
    fmap phase (V.eval (Prim x))
verifyPrim Conjugate ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessComplex (undefined :: Prim b) =
    fmap conjugate (V.eval (Prim x))
verifyPrim I2N ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- V.witnessNum (undefined :: Prim b) = do
    fmap i2n (V.eval (Prim x))
verifyPrim I2B ((x :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- V.witnessNum (undefined :: Prim b) = do
    x <- V.eval (Prim x)
    return (V.fromSMT (SMT.not (x V..==. 0)))
verifyPrim B2I (x :* Nil)
  | Dict <- V.witnessNum (undefined :: Prim (DenResult a)) = do
    x <- V.eval (Prim x)
    return (V.smtIte (V.toSMT x) 1 0)
verifyPrim Round ((x :: ASTF SoftwarePrimDomain b) :* Nil) = do
  x <- V.eval (Prim x)
  return (fromRat (toRat x))
verifyPrim Not (x :* Nil) =
  fmap (V.fromSMT . SMT.not . V.toSMT) (V.eval (Prim x))
verifyPrim And (x :* y :* Nil) = do
  x <- V.eval (Prim x)
  y <- V.eval (Prim y)
  return (V.fromSMT (V.toSMT x SMT..&&. V.toSMT y))
verifyPrim Or (x :* y :* Nil) = do
  x <- V.eval (Prim x)
  y <- V.eval (Prim y)
  return (V.fromSMT (V.toSMT x SMT..||. V.toSMT y))
verifyPrim Eq ((x :: ASTF SoftwarePrimDomain b) :* y :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b) =
    fmap V.fromSMT (S.liftM2 (V..==.) (V.eval (Prim x)) (V.eval (Prim y)))
verifyPrim Neq ((x :: ASTF SoftwarePrimDomain b) :* y :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b) =
    fmap V.fromSMT (S.liftM2 (./=.) (V.eval (Prim x)) (V.eval (Prim y)))
  where
    x ./=. y = SMT.not (x V..==. y)
verifyPrim Lt ((x :: ASTF SoftwarePrimDomain b) :* y :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- V.witnessOrd  (undefined :: Prim b) =
    fmap V.fromSMT (S.liftM2 (V..<.) (V.eval (Prim x)) (V.eval (Prim y)))
verifyPrim Gt ((x :: ASTF SoftwarePrimDomain b) :* y :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- V.witnessOrd  (undefined :: Prim b) =
    fmap V.fromSMT (S.liftM2 (V..>.) (V.eval (Prim x)) (V.eval (Prim y)))
verifyPrim Lte ((x :: ASTF SoftwarePrimDomain b) :* y :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- V.witnessOrd  (undefined :: Prim b) =
    fmap V.fromSMT (S.liftM2 (V..<=.) (V.eval (Prim x)) (V.eval (Prim y)))
verifyPrim Gte ((x :: ASTF SoftwarePrimDomain b) :* y :* Nil)
  | Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- V.witnessOrd  (undefined :: Prim b) =
    fmap V.fromSMT (S.liftM2 (V..>=.) (V.eval (Prim x)) (V.eval (Prim y)))
verifyPrim BitAnd (x :* y :* Nil)
  | Dict <- witnessBits (undefined :: Prim (DenResult a)) =
    S.liftM2 (.&.) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim BitOr (x :* y :* Nil)
  | Dict <- witnessBits (undefined :: Prim (DenResult a)) =
    S.liftM2 (.|.) (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim BitXor (x :* y :* Nil)
  | Dict <- witnessBits (undefined :: Prim (DenResult a)) =
    S.liftM2 xor (V.eval (Prim x)) (V.eval (Prim y))
verifyPrim BitCompl (x :* Nil)
  | Dict <- witnessBits (undefined :: Prim (DenResult a)) =
    fmap complement (V.eval (Prim x))
verifyPrim ShiftL (x :* (y :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessBits (undefined :: Prim (DenResult a)),
    Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- witnessIntegral (undefined :: Prim b) = do
    -- todo: should check for undefined behaviour
    x <- V.eval (Prim x)
    y <- V.eval (Prim y)
    return (shiftL x (i2n y))
verifyPrim ShiftR (x :* (y :: ASTF SoftwarePrimDomain b) :* Nil)
  | Dict <- witnessBits (undefined :: Prim (DenResult a)),
    Dict <- V.witnessPred (undefined :: Prim b),
    Dict <- witnessIntegral (undefined :: Prim b) = do
    -- todo: should check for undefined behaviour
    x <- V.eval (Prim x)
    y <- V.eval (Prim y)
    return (shiftR x (i2n y))
verifyPrim (ArrIx (Imp.IArrComp name :: Imp.IArr Index b)) (i :* Nil) = do
  i <- V.eval (Prim i)
  readArray name i
verifyPrim Cond (cond :* x :* y :* Nil) =
  S.liftM3 V.smtIte
    (fmap V.toSMT (V.eval (Prim cond)))
    (V.eval (Prim x))
    (V.eval (Prim y))
verifyPrim exp _ = error ("Unimplemented: " ++ show exp)

--------------------------------------------------------------------------------
