{-# OPTIONS_HADDOCK show-extensions #-}
-- |
-- Module     : Unbound.Generics.LocallyNameless.Shift
-- Copyright  : (c) 2015, Aleksey Kliger
-- License    : BSD3 (See LICENSE)
-- Maintainer : Aleksey Kliger
-- Stability  : experimental
--
-- The pattern @'Shift' e@ shifts the scope of the embedded term in @e@ one level outwards.
--
{-# LANGUAGE TypeFamilies #-}
module Unbound.Generics.LocallyNameless.Shift where

import Control.Applicative
import Control.DeepSeq (NFData(..))
import Data.Monoid (Monoid(..), All(..))

import Unbound.Generics.LocallyNameless.Alpha (Alpha(..),
                                               decrLevelCtx, isTermCtx,
                                               isZeroLevelCtx,
                                               inconsistentDisjointSet)
import Unbound.Generics.LocallyNameless.Embed (IsEmbed(..))

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

-- | The type @Shift e@ is an embedding pattern that shifts the scope of the
-- free variables of the embedded term @'Embedded' e@ up by one level.
newtype Shift e = Shift e

instance Functor Shift where
  fmap f (Shift e) = Shift (f e)

instance IsEmbed e => IsEmbed (Shift e) where
  type Embedded (Shift e) = Embedded e
  embedded = iso (\(Shift e) -> e) Shift . embedded

instance NFData e => NFData (Shift e) where
  rnf (Shift e) = rnf e `seq` ()

instance Show e => Show (Shift e) where
  showsPrec _ (Shift e) = showString "{" . showsPrec 0 e . showString "}"

instance Alpha e => Alpha (Shift e) where
  isPat (Shift e) = if (isEmbed e) then mempty else inconsistentDisjointSet

  isTerm _ = All False

  isEmbed (Shift e) = isEmbed e

  swaps' ctx perm (Shift e) = Shift (swaps' (decrLevelCtx ctx) perm e)

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

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

  aeq' ctx (Shift e1) (Shift e2) = aeq' ctx e1 e2

  fvAny' ctx afa (Shift e) = Shift <$> fvAny' ctx afa e

  close ctx b se@(Shift e) =
    if isTermCtx ctx
    then error "LocallyNameless.close on Shift"
    else if isZeroLevelCtx ctx
         then
           -- consider type A = Rec (Name t, Shift (Embed e), (Embed e))
           --  (ie the 2nd element of the tuple is not allowed to refer to itself,
           --   but the third is)
           -- if we have (x, e1, e2) and we apply 'rec' to it,
           -- we must close the tuple with respect to itself.
           -- in that case, the ctxLevel is 0 and so none of the names in
           -- e1 need be bound.
           -- on the other hand once we go to
           --   Bind P (Bind A B) for some P and B,
           -- the free vars of e1 in A are bound by P.
           se
         else Shift (close (decrLevelCtx ctx) b e)

  open ctx b se@(Shift e) =
    if isTermCtx ctx
    then error "LocallyNameless.open on Shift"
    else if isZeroLevelCtx ctx
         then se
         else Shift (open (decrLevelCtx ctx) b e)

  nthPatFind (Shift e) = nthPatFind e
  namePatFind (Shift e) = namePatFind e


  acompare' ctx (Shift x) (Shift y) = acompare' ctx x y