{-# OPTIONS_HADDOCK show-extensions #-}
-- |
-- Module     : Unbound.Generics.LocallyNameless.Embed
-- Copyright  : (c) 2014, Aleksey Kliger
-- License    : BSD3 (See LICENSE)
-- Maintainer : Aleksey Kliger
-- Stability  : experimental
--
-- The pattern @'Embed' t@ contains a term @t@.
{-# LANGUAGE DeriveGeneric, TypeFamilies #-}
module Unbound.Generics.LocallyNameless.Embed where

import Control.Applicative (pure, (<$>))
import Control.DeepSeq (NFData(..))
import Data.Monoid (mempty, All(..))
import Data.Profunctor (Profunctor(..))

import GHC.Generics (Generic)

import Unbound.Generics.LocallyNameless.Alpha
import Unbound.Generics.LocallyNameless.Internal.Iso (iso)

-- | @Embed@ allows for terms to be /embedded/ within patterns.  Such
--   embedded terms do not bind names along with the rest of the
--   pattern.  For examples, see the tutorial or examples directories.
--
--   If @t@ is a /term type/, then @Embed t@ is a /pattern type/.
--
--   @Embed@ is not abstract since it involves no binding, and hence
--   it is safe to manipulate directly.  To create and destruct
--   @Embed@ terms, you may use the @Embed@ constructor directly.
--   (You may also use the functions 'embed' and 'unembed', which
--   additionally can construct or destruct any number of enclosing
--   'Shift's at the same time.)
newtype Embed t = Embed t deriving (Eq, Ord, Generic)

class IsEmbed e where
  -- | The term type embedded in the embedding 'e'
  type Embedded e :: *
  -- | Insert or extract the embedded term.
  -- If you're not using the lens library, see 'Unbound.Generics.LocallyNameless.Operations.embed'
  -- and 'Unbound.Generics.LocallyNameless.Operations.unembed'
  -- otherwise 'embedded' is an isomorphism that you can use with lens.
  -- @
  -- embedded :: Iso' (Embedded e) e
  -- @
  embedded :: (Profunctor p, Functor f) => p (Embedded e) (f (Embedded e)) -> p e (f e)

instance IsEmbed (Embed t) where
  type Embedded (Embed t) = t
  embedded = iso (\(Embed t) -> t) Embed

instance NFData t => NFData (Embed t) where
  rnf (Embed t) = rnf t `seq` ()

instance Show a => Show (Embed a) where
  showsPrec _ (Embed a) = showString "{" . showsPrec 0 a . showString "}"

instance Alpha t => Alpha (Embed t) where
  isPat (Embed t) = if getAll (isTerm t) then mempty else inconsistentDisjointSet

  isTerm _ = All False

  isEmbed (Embed t) = getAll (isTerm t)

  swaps' ctx perm (Embed t) =
    if isTermCtx ctx
    then Embed t
    else Embed (swaps' (termCtx ctx) perm t)

  freshen' ctx p =
    if isTermCtx ctx
    then error "LocallyNameless.freshen' called on a term"
    else return (p, mempty)

  lfreshen' ctx p cont =
    if isTermCtx ctx
    then error "LocallyNameless.lfreshen' called on a term"
    else cont p mempty


  aeq' ctx (Embed x) (Embed y) = aeq' (termCtx ctx) x y

  fvAny' ctx afa ex@(Embed x) =
    if isTermCtx ctx
    then pure ex
    else Embed <$> fvAny' (termCtx ctx) afa x

  close ctx b (Embed x) =
    if isTermCtx ctx
    then error "LocallyNameless.close on Embed"
    else Embed (close (termCtx ctx) b x)

  open ctx b (Embed x) =
    if isTermCtx ctx
    then error "LocallyNameless.open on Embed"
    else Embed (open (termCtx ctx) b x)

  nthPatFind _ = mempty
  namePatFind _ = mempty

  acompare' ctx (Embed x) (Embed y) = acompare' (termCtx ctx) x y