{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE FlexibleContexts #-}
--
-- Maybe.hs --- Optional Ivory values.
--
-- Copyright (C) 2013, Galois, Inc.
-- All Rights Reserved.
--
-- This software is released under the "BSD3" license.  Read the file
-- "LICENSE" for more information.
--

-- | This module provides an interface for a nullable Ivory type.
--
-- To define a type like Haskell's @Maybe Float@, define an
-- Ivory structure type, and make the structure an instance
-- of 'MaybeType'.
--
-- > [ivory|
-- > struct maybe_float
-- >   { mf_valid :: Stored IBool
-- >   ; mf_value :: Stored IFloat
-- >   }
-- > |]
-- >
-- > instance MaybeType "maybe_float" IFloat where
-- >   maybeValidLabel = mf_valid
-- >   maybeValueLabel = mf_value
--
-- With this definition in place, any of the functions in this
-- module will accept a @Struct \"maybe_float\"@.
--
-- These structure types must be defined in an Ivory module as
-- usual, and it is recommended to make them private unless they
-- are necessary as part of the module's public interface.
module Ivory.Stdlib.Maybe
  (
  -- * Interface
  MaybeType(..)

  -- * Initialization
  , initJust, initNothing

  -- * Getting
  , getMaybe

  -- * Setting
  , setJust, setNothing
  , setDefault, setDefault_

  -- * Modifying
  , mapMaybe
  , mapMaybeM, mapMaybeM_
  , forMaybeM, forMaybeM_
  ) where

import GHC.TypeLits

import Ivory.Language

class (IvoryStruct sym, IvoryExpr t, IvoryStore t, IvoryInit t) =>
      MaybeType (sym :: Symbol) t | sym -> t where
  -- | Return a boolean field indicating whether the value is valid.
  maybeValidLabel :: Label sym (Stored IBool)
  -- | Return the field containing a value, if it is valid.
  maybeValueLabel :: Label sym (Stored t)

-- | Return an initializer for a maybe type with a valid value.
initJust :: MaybeType sym a => a -> Init (Struct sym)
initJust x =
  istruct
    [ maybeValidLabel .= ival true
    , maybeValueLabel .= ival x
    ]

-- | Return an initializer for a maybe type with no value.
initNothing :: MaybeType sym a => Init (Struct sym)
initNothing =
  istruct
    [ maybeValidLabel .= ival false
    ]

-- | Retrieve a maybe's value given a default if it is nothing.
getMaybe :: MaybeType sym a
         => ConstRef s1 (Struct sym)
         -> a
         -> Ivory eff a
getMaybe ref def = do
  valid <- deref (ref ~> maybeValidLabel)
  value <- deref (ref ~> maybeValueLabel)
  assign (valid ? (value, def))

-- | Set a maybe's value to a default if it is nothing, returning
-- the current value.
setDefault :: MaybeType sym a
           => Ref s1 (Struct sym)
           -> a
           -> Ivory eff a
setDefault ref def = do
  setDefault_ ref def
  deref (ref ~> maybeValueLabel)

-- | Set a maybe's value to a default value if it is nothing.
setDefault_ :: MaybeType sym a
            => Ref s1 (Struct sym)
            -> a
            -> Ivory eff ()
setDefault_ ref def = do
  valid <- deref (ref ~> maybeValidLabel)
  ifte_ (iNot valid)
    (do store (ref ~> maybeValidLabel) true
        store (ref ~> maybeValueLabel) def)
    (return ())

-- | Modify a maybe value by an expression if it is not nothing.
mapMaybe :: MaybeType sym a
         => (a -> a)
         -> Ref s1 (Struct sym)
         -> Ivory eff ()
mapMaybe f ref = mapMaybeM (return . f) ref

-- | Modify a maybe value by an action if it is not nothing.
mapMaybeM :: MaybeType sym a
          => (a -> Ivory eff a)
          -> Ref s1 (Struct sym)
          -> Ivory eff ()
mapMaybeM f ref = do
  valid <- deref (ref ~> maybeValidLabel)
  ifte_ valid
    (do value  <- deref (ref ~> maybeValueLabel)
        value' <- f value
        store (ref ~> maybeValueLabel) value')
    (return ())

-- | Flipped version of 'mapMaybeM'.
forMaybeM :: MaybeType sym a
          => Ref s1 (Struct sym)
          -> (a -> Ivory eff a)
          -> Ivory eff ()
forMaybeM = flip mapMaybeM

-- | Call an action with a maybe value if it is not nothing.
mapMaybeM_ :: MaybeType sym a
           => (a -> Ivory eff ())
           -> Ref s1 (Struct sym)
           -> Ivory eff ()
mapMaybeM_ f ref = do
  valid <- deref (ref ~> maybeValidLabel)
  ifte_ valid
    (do value <- deref (ref ~> maybeValueLabel)
        f value)
    (return ())

-- | Flipped version of 'mapMaybeM_'.
forMaybeM_ :: MaybeType sym a
           => Ref s1 (Struct sym)
           -> (a -> Ivory eff ())
           -> Ivory eff ()
forMaybeM_ = flip mapMaybeM_

-- | Set a maybe value to a valid value.
setJust :: MaybeType sym a
        => Ref s1 (Struct sym)
        -> a
        -> Ivory eff ()
setJust ref x = do
  store (ref ~> maybeValidLabel) true
  store (ref ~> maybeValueLabel) x

-- | Set a maybe value to an invalid value.
setNothing :: MaybeType sym a
           => Ref s1 (Struct sym)
           -> Ivory eff ()
setNothing ref = store (ref ~> maybeValidLabel) false