{-# LANGUAGE
    GeneralizedNewtypeDeriving
  , StandaloneDeriving
  , NamedFieldPuns #-}

-- | Provides a monad transformer for constructing Yices queries.
--
-- This is not an essential part of the interface.  Expressions
-- may be built using @Yices.Easy.Types@ directly.
--
-- The monad is simply a convenient way to handle bookkeeping
-- regarding fresh names, accumulated declarations \/
-- assertions, and choosing variables to retrieve from the
-- @'Model'@.

module Yices.Easy.Build
  ( -- * The monad transformer
    BuildT
  , Build
  , runBuildT
  , execBuildT
  , runBuild
  , execBuild

    -- * Invoking Yices directly
  , solveBuildT
  , solveBuild

    -- * Fresh names
  , freshName

    -- * Declarations
  , declare
  , fresh

    -- * Assertions
  , assert

    -- * Getting variables
  , get

    -- * Declaring and getting variables
  , declInt
  , declBool
  , declBitvec
  ) where

import Yices.Easy.Types
import Yices.Easy.Run

import Data.Functor.Identity
import Control.Monad
import Control.Applicative

import qualified Data.Map                  as M
import qualified Control.Monad.Trans.State as S

data State = State
  { sNext    :: Integer
  , sDecls   :: [Declaration]
  , sAsserts :: [Assertion]
  , sGets    :: M.Map Ident VarType }

startState :: State
startState = State
  { sNext    = 0
  , sDecls   = []
  , sAsserts = []
  , sGets    = M.empty }

-- | Monad transformer for building Yices queries.
newtype BuildT m a = BuildT (S.StateT State m a)

type Build a = BuildT Identity a

deriving instance (Functor m) => Functor (BuildT m)
deriving instance (Monad   m) => Monad   (BuildT m)
deriving instance (Functor m, Monad m) => Applicative (BuildT m)

runBuildT :: (Monad m) => BuildT m a -> m (a, Query)
runBuildT (BuildT x) = do
  (v, State { sDecls, sAsserts, sGets }) <- S.runStateT x startState
  let gets = map (uncurry Get) $ M.assocs sGets
  return (v, Query (Context sDecls sAsserts) gets)

execBuildT :: (Monad m) => BuildT m a -> m Query
execBuildT x = snd `liftM` runBuildT x

runBuild :: Build a -> (a, Query)
runBuild = runIdentity . runBuildT

execBuild :: Build a -> Query
execBuild = runIdentity . execBuildT

-- | Invoke @'solve'@ on a @'Build'@ action directly.
solveBuild :: Build a -> IO Result
solveBuild = solve . execBuild

-- | Invoke @'solve'@ on a @'BuildT' 'IO'@ action directly.
solveBuildT :: BuildT IO a -> IO Result
solveBuildT x = execBuildT x >>= solve

-- | Generate a fresh name from a base string.
--
-- The name will not conflict with a user-specified name
-- unless that name begins with an underscore.
freshName :: (Monad m) => Ident -> BuildT m Ident
freshName x = BuildT $ do
  s@State { sNext=n } <- S.get
  S.put $ s { sNext=succ n }
  return $ concat ["_", x, "_", show n]

-- | Declare a variable.
declare :: (Monad m) => Ident -> Type -> BuildT m Expr
declare x t = BuildT $ do
  S.modify $ \s -> s { sDecls = Declare x t : sDecls s }
  return $ Var x

-- | Declare a fresh variable given a base name.
--
-- See @'freshName'@ for naming details.
fresh :: (Monad m) => Ident -> Type -> BuildT m Expr
fresh x t = freshName x >>= flip declare t

-- | Assert a Boolean truth.
assert :: (Monad m) => Expr -> BuildT m ()
assert e = BuildT . S.modify $ \s ->
  s { sAsserts = Assert e : sAsserts s }

-- | Arrange to get this variable in the resulting @'Model'@.
get :: (Monad m) => Ident -> VarType -> BuildT m ()
get x t = BuildT . S.modify $ \s ->
  s { sGets = M.insert x t $ sGets s }

declGet :: (Monad m) => Type -> VarType -> Ident -> BuildT m Expr
declGet t v x = do
  e <- declare x t
  get x v
  return e

-- | Declare an @int@ variable and @'get'@ it.
declInt :: (Monad m) => Ident -> BuildT m Expr
declInt = declGet (TyName "int") VarInt

-- | Declare a @bool@ variable and @'get'@ it.
declBool :: (Monad m) => Ident -> BuildT m Expr
declBool = declGet (TyName "bool") VarBool

-- | Declare a bitvector variable and @'get'@ it.
declBitvec :: (Monad m) => Size -> Ident -> BuildT m Expr
declBitvec n = declGet (TyBitvec n) (VarBitvec n)
