{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE InstanceSigs #-}

module Ivory.Language.Ref where

import Ivory.Language.IChar (IChar)
import Ivory.Language.Sint (Sint8,Sint16,Sint32,Sint64)
import Ivory.Language.Uint (Uint8,Uint16,Uint32,Uint64)

import Ivory.Language.Area
import Ivory.Language.Monad
import Ivory.Language.Proxy
import Ivory.Language.Scope
import Ivory.Language.IBool
import Ivory.Language.Type
import qualified Ivory.Language.Syntax as I


-- References ------------------------------------------------------------------

-- | A non-null pointer to a memory area.
newtype Ref (s :: RefScope) (a :: Area *) = Ref { getRef :: I.Expr }

instance IvoryArea area => IvoryType (Ref s area) where
  ivoryType _ = I.TyRef (ivoryArea (Proxy :: Proxy area))

instance IvoryArea area => IvoryVar (Ref s area) where
  wrapVar    = wrapVarExpr
  unwrapExpr = getRef

instance IvoryArea area => IvoryExpr (Ref s area) where
  wrapExpr = Ref


-- Constant References ---------------------------------------------------------

-- | Turn a reference into a constant reference.
constRef :: IvoryArea area => Ref s area -> ConstRef s area
constRef  = wrapExpr . unwrapExpr

newtype ConstRef (s ::  RefScope) (a :: Area *) = ConstRef
  { getConstRef :: I.Expr
  }

instance IvoryArea area => IvoryType (ConstRef s area) where
  ivoryType _ = I.TyConstRef (ivoryArea (Proxy :: Proxy area))

instance IvoryArea area => IvoryVar (ConstRef s area) where
  wrapVar    = wrapVarExpr
  unwrapExpr = getConstRef

instance IvoryArea area => IvoryExpr (ConstRef s area) where
  wrapExpr = ConstRef


-- Dereferencing ---------------------------------------------------------------

class IvoryRef (ref ::  RefScope -> Area * -> *) where
  unwrapRef :: IvoryVar a => ref s (Stored a) -> I.Expr

instance IvoryRef Ref where
  unwrapRef = unwrapExpr

instance IvoryRef ConstRef where
  unwrapRef = unwrapExpr

-- | Dereferenceing.
deref :: forall eff ref s a.
         (IvoryVar a, IvoryVar (ref s (Stored a)), IvoryRef ref)
      => ref s (Stored a) -> Ivory eff a
deref ref = do
  r <- freshVar "deref"
  emit (I.Deref (ivoryType (Proxy :: Proxy a)) r (unwrapRef ref))
  return (wrapVar r)

-- Copying ---------------------------------------------------------------------

-- | Memory copy.  Emits an assertion that the two references are unequal.
refCopy :: forall eff sTo ref sFrom a.
     ( IvoryRef ref, IvoryVar (Ref sTo a), IvoryVar (ref sFrom a), IvoryArea a)
  => Ref sTo a -> ref sFrom a -> Ivory eff ()
refCopy destRef srcRef =
  emit (I.RefCopy (ivoryArea (Proxy :: Proxy a))
       (unwrapExpr destRef) (unwrapExpr srcRef))

-- Storing ---------------------------------------------------------------------

store :: forall eff s a. IvoryStore a => Ref s (Stored a) -> a -> Ivory eff ()
store ref a = emit (I.Store ty (unwrapExpr ref) (unwrapExpr a))
  where
  ty = ivoryType (Proxy :: Proxy a)

-- | Things that can be safely stored in references.
class IvoryVar a => IvoryStore a where

-- simple types
instance IvoryStore IBool
instance IvoryStore IChar
instance IvoryStore Uint8
instance IvoryStore Uint16
instance IvoryStore Uint32
instance IvoryStore Uint64
instance IvoryStore Sint8
instance IvoryStore Sint16
instance IvoryStore Sint32
instance IvoryStore Sint64