{-# LANGUAGE CPP
           , RankNTypes
           , FlexibleContexts
           , GADTs
           , TypeFamilies
  #-}

----------------------------------------------------------------------

-- |

-- Module      :  Unbound.LocallyNameless.Alpha

-- License     :  BSD-like (see LICENSE)

-- Maintainer  :  Stephanie Weirich <sweirich@cis.upenn.edu>

-- Portability :  GHC only (-XKitchenSink)

--

----------------------------------------------------------------------


module Unbound.LocallyNameless.Alpha where

import Generics.RepLib hiding (GT)
import Unbound.PermM
import Unbound.LocallyNameless.Types
import Unbound.LocallyNameless.Fresh
import Unbound.Util

import Data.List (intersect)
import Data.Maybe (isJust)

import Data.Semigroup (Semigroup)
import qualified Data.Semigroup as Semigroup
import Data.Monoid

------------------------------------------------------------

-- Overview

--

-- We have two classes of types:

--    Terms (which contain variables) and

--    Patterns (which contain binders)

--

-- Terms include

--    Names

--    Bind p t when p is a Pattern and t is a Term

--    Standard type constructors (Unit, (,), Maybe, [], etc)

--

-- Patterns include

--    Names

--    Embed t when t is a Term

--    Rebind p q when p and q are both Patterns

--    Rec p when p is a pattern

--    Shift a when a is an Embed or some number of Shifts wrapped around Embed

--    Standard type constructors (Unit, (,), Maybe, [], etc)

--

-- Terms support a number of operations, including alpha-equivalence,

-- free variables, swapping, etc.  Because Patterns occur in terms, so

-- they too support the same operations, but only for the annotations

-- inside them.

-- Therefore, both Terms and Patterns are instances of the "Alpha" type class

-- which lists these operations.  However, some types (such as [Name])

-- are both Terms and Patterns, and the behavior of the operations

-- is different when we use [Name] as a term and [Name] as a pattern.

-- Therefore, we index each of the operations with a mode that tells us

-- what version we should be defining.

--

-- [SCW: could we use multiparameter type classes? Alpha m t]

--

-- Patterns also support a few extra operations that Terms do not

-- for dealing with the binding variables.

--     These are used to find the index of names inside patterns.

------------------------------------------------------------


------------------------------------------------------------

-- The Alpha class

------------------------------------------------------------


-- | The @Alpha@ type class is for types which may contain names.  The

--   'Rep1' constraint means that we can only make instances of this

--   class for types that have generic representations (which can be

--   automatically derived by RepLib.)

--

--   Note that the methods of @Alpha@ should almost never be called

--   directly.  Instead, use other methods provided by this module

--   which are defined in terms of @Alpha@ methods.

--

--   Most of the time, the default definitions of these methods will

--   suffice, so you can make an instance for your data type by simply

--   declaring

--

--   > instance Alpha MyType

--

--   Occasionally, however, it may be useful to override the default

--   implementations of one or more @Alpha@ methods for a particular

--   type.  For example, consider a type like

--

--   > data Term = ...

--   >           | Annotation Stuff Term

--

--   where the @Annotation@ constructor of @Term@ associates some sort

--   of annotation with a term --- say, information obtained from a

--   parser about where in an input file the term came from.  This

--   information is needed to produce good error messages, but should

--   not be taken into consideration when, say, comparing two @Term@s

--   for alpha-equivalence.  In order to make 'aeq' ignore

--   annotations, you can override the implementation of @aeq'@ like

--   so:

--

--   > instance Alpha Term where

--   >   aeq' c (Annotation _ t1) t2 = aeq' c t1 t2

--   >   aeq' c t1 (Annotation _ t2) = aeq' c t1 t2

--   >   aeq' c t1 t2 = aeqR1 rep1 t1 t2

--

--   Note how the call to 'aeqR1' handles all the other cases generically.

--

--   If you use "Abstract" types (i.e. those with representations derived via

--   derive_abstract) then you must provide a definition of aeq' and

--   acompare'.  In these cases, Unbound has no information about the

--   structure of the type and cannot do anything sensible.


--      swaps'    -- identity function

--      fv'       -- const mempty

--      freshen'  -- identity function

--      lfreshen' -- identity function

--      open/close -- treat like constants


class (Rep1 AlphaD a) => Alpha a where

  -- | See 'swaps'.

  swaps' :: AlphaCtx -> Perm AnyName -> a -> a
  swaps' = swapsR1 rep1

  -- | See 'fv'.

  fv' :: Collection f => AlphaCtx -> a -> f AnyName
  fv' = fvR1 rep1

  -- | See 'lfreshen'.

  lfreshen' :: LFresh m => AlphaCtx -> a -> (a -> Perm AnyName -> m b) -> m b
  lfreshen' = lfreshenR1 rep1

  -- | See 'freshen'.

  freshen' :: Fresh m => AlphaCtx -> a -> m (a, Perm AnyName)
  freshen' = freshenR1 rep1

  -- | See 'aeq'.

  aeq' :: AlphaCtx -> a -> a -> Bool
  aeq' = aeqR1 rep1

  -- | See 'acompare'.

  acompare' :: AlphaCtx -> a -> a -> Ordering
  acompare' = acompareR1 rep1

{-
  -- | See 'match'.
  match'   :: AlphaCtx -> a -> a -> Maybe (Perm AnyName)
  match'   = matchR1 rep1
-}

  -- | Replace free names by bound names.

  close :: Alpha b => AlphaCtx -> b -> a -> a
  close = closeR1 rep1

  -- | Replace bound names by free names.

  open :: Alpha b => AlphaCtx -> b -> a -> a
  open = openR1 rep1

  -- | @isPat x@ dynamically checks whether @x@ can be used as a valid

  --   pattern.  The default instance returns @Just@ if at all

  --   possible.  If successful, returns a list of names bound by the

  --   pattern.

  isPat :: a -> Maybe [AnyName]
  isPat = isPatR1 rep1

  -- | @isTerm x@ dynamically checks whether @x@ can be used as a

  --   valid term. The default instance returns @True@ if at all

  --   possible.

  isTerm :: a -> Bool
  isTerm = isTermR1 rep1

  -- | @isEmbed@ is needed internally for the implementation of

  --   @isPat@.  @isEmbed@ is true for terms wrapped in @Embed@ and zero

  --   or more occurrences of @Shift@.  The default implementation

  --   simply returns @False@.

  isEmbed :: a -> Bool
  isEmbed _ = False
  ---------------- PATTERN OPERATIONS ----------------------------


  -- | @'nthpatrec' p n@ looks up the @n@th name in the pattern @p@

  -- (zero-indexed), returning the number of names encountered if not

  -- found.

  nthpatrec :: a -> NthCont
  nthpatrec = nthpatR1 rep1

  -- | Find the (first) index of the name in the pattern if one

  --   exists; otherwise, return the number of names encountered

  --   instead.

  findpatrec :: a -> AnyName -> FindResult
  findpatrec = findpatR1 rep1

-- | Type class for embedded terms (either @Embed@ or @Shift@).

class IsEmbed e where
  type Embedded e :: *

  -- | Construct an embedded term, which is an instance of 'Embed'

  --   with any number of enclosing 'Shift's.  That is, @embed@ can have

  --   any of the types

  --

  -- * @t -> Embed t@

  --

  -- * @t -> Shift (Embed t)@

  --

  -- * @t -> Shift (Shift (Embed t))@

  --

  -- and so on.

  embed   :: Embedded e -> e

  -- | Destruct an embedded term.  @unembed@ can have any of the types

  --

  -- * @Embed t -> t@

  --

  -- * @Shift (Embed t) -> t@

  --

  -- and so on.

  unembed :: e -> Embedded e

------------------------------------------------------------

--  Pattern operation internals

------------------------------------------------------------


-- | The result of a 'findpatrec' operation.

data FindResult = Index Integer      -- ^ The (first) index of the name we

                                     --   sought

                | NamesSeen Integer  -- ^ We haven't found the name

                                     --   (yet), but have seen this many

                                     --   others while looking for it

  deriving (Eq, Ord)


instance Semigroup FindResult where
  NamesSeen i <> NamesSeen j = NamesSeen (i + j)
  NamesSeen i <> Index j     = Index (i + j)
  Index j     <> _           = Index j

-- | @FindResult@ forms a monoid which combines information from

--   several 'findpatrec' operations.  @mappend@ takes the leftmost

--   'Index', and combines the number of names seen to the left of it

--   so we can correctly compute its global index.

instance Monoid FindResult where
  mempty = NamesSeen 0
#if !MIN_VERSION_base(4,11,0)
  mappend = (Semigroup.<>)
#endif

-- | Find the (first) index of the name in the pattern, if it exists.

findpat :: Alpha a => a -> AnyName -> Maybe Integer
findpat x n = case findpatrec x n of
                   Index i     -> Just i
                   NamesSeen _ -> Nothing


-- | The result of an 'nthpatrec' operation.

data NthResult = Found AnyName    -- ^ The name found at the given

                                  --   index.

               | CurIndex Integer -- ^ We haven't yet reached the

                                  --   required index; this is the

                                  --   index into the remainder of the

                                  --   pattern (which decreases as we

                                  --   traverse the pattern).


-- | A continuation which takes the remaining index and searches for

--   that location in a pattern, yielding a name or a remaining index

--   if the end of the pattern was reached too soon.

newtype NthCont = NthCont { runNthCont :: Integer -> NthResult }


instance Semigroup NthCont where
  (NthCont f) <> (NthCont g)
    = NthCont $ \i -> case f i of
        Found n     -> Found n
        CurIndex i' -> g i'

-- | @NthCont@ forms a monoid: function composition which

--   short-circuits once a result is found.

instance Monoid NthCont where
  mempty = NthCont $ \i -> CurIndex i
#if !MIN_VERSION_base(4,11,0)  
  mappend = (Semigroup.<>)
#endif

-- | If we see a name, check whether the index is 0: if it is, we've

--   found the name we're looking for, otherwise continue with a

--   decremented index.

nthName :: AnyName -> NthCont
nthName nm = NthCont $ \i -> if i == 0
                               then Found nm
                               else CurIndex (i-1)

-- | @'nthpat' b n@ looks up up the @n@th name in the pattern @b@

-- (zero-indexed).  PRECONDITION: the number of names in the pattern

-- must be at least @n@.

nthpat :: Alpha a => a -> Integer -> AnyName
nthpat x i = case runNthCont (nthpatrec x) i of
                 CurIndex j -> error
                   ("BUG: pattern index " ++ show i ++
                    " out of bounds by " ++ show j ++ "in")
                 Found nm   -> nm

------------------------------------------------------------

--  AlphaCtx

------------------------------------------------------------


-- An AlphaCtx records the current mode (Term/Pat) and current level,

-- and gets passed along during operations which need to keep track of

-- the mode and/or level.


-- | Many of the operations in the 'Alpha' class take an 'AlphaCtx':

-- stored information about the iteration as it progresses. This type

-- is abstract, as classes that override these operations should just pass

-- the context on.

data AlphaCtx = AC { mode :: Mode , level :: Integer }

-- | Toplevel alpha-contexts

initial :: AlphaCtx
initial = AC Term 0

-- | Call when going under a binder

incr :: AlphaCtx -> AlphaCtx
incr c = c { level = level c + 1 }

-- | Call when going in an embedding

decr :: AlphaCtx -> AlphaCtx
decr c = -- if level c == 0 then error "Too many outers"

         -- else

         c { level = level c - 1 }

-- | Call when entering a pattern

pat  :: AlphaCtx -> AlphaCtx
pat c  = c { mode = Pat }

-- | Call when entering a term embedded in a pattern

term  :: AlphaCtx -> AlphaCtx
term c  = c { mode = Term }

-- | A mode is basically a flag that tells us whether we should be

--   looking at the names in the term, or if we are in a pattern and

--   should /only/ be looking at the names in the annotations. The

--   standard mode is to use 'Term'; many functions do this by default.

data Mode = Term | Pat deriving (Show, Eq, Read)

-- | Open a term using the given pattern.

openT :: (Alpha p, Alpha t) => p -> t -> t
openT = open initial

-- | @openP p1 p2@ opens the pattern @p2@ using the pattern @p1@.

openP :: (Alpha p1, Alpha p2) => p1 -> p2 -> p2
openP = open (pat initial)

-- | Close a term using the given pattern.

closeT :: (Alpha p, Alpha t) => p -> t -> t
closeT = close initial

-- | @closeP p1 p2@ closes the pattern @p2@ using the pattern @p1@.

closeP :: (Alpha p1, Alpha p2) => p1 -> p2 -> p2
closeP = close (pat initial)

-- | Class constraint hackery to allow us to override the default

--   definitions for certain classes.  'AlphaD' is essentially a

--   reified dictionary for the 'Alpha' class.

data AlphaD a = AlphaD {
  isPatD    :: a -> Maybe [AnyName],
  isTermD   :: a -> Bool,
  isEmbedD  :: a -> Bool,
  swapsD    :: AlphaCtx -> Perm AnyName -> a -> a,
  fvD       :: forall f. Collection f => AlphaCtx -> a -> f AnyName,
  freshenD  :: forall m. Fresh m => AlphaCtx -> a -> m (a, Perm AnyName),
  lfreshenD :: forall m b. LFresh m => AlphaCtx -> a -> (a -> Perm AnyName -> m b) -> m b,
  aeqD      :: AlphaCtx -> a -> a -> Bool,
  acompareD :: AlphaCtx -> a -> a -> Ordering,
  closeD    :: forall b. Alpha b => AlphaCtx -> b -> a -> a,
  openD     :: forall b. Alpha b => AlphaCtx -> b -> a -> a,
  findpatD  :: a -> AnyName -> FindResult,
  nthpatD   :: a -> NthCont
  }

instance Alpha a => Sat (AlphaD a) where
  dict = AlphaD isPat isTerm isEmbed swaps' fv' freshen' lfreshen' aeq' -- match'

          acompare' close open findpatrec nthpatrec

----------------------------------------------------------------------

-- Generic definitions for 'Alpha' methods.  (Note that all functions

-- that take representations end in 'R1'.)

----------------------------------------------------------------------


-- | Generic version of close

closeR1 :: Alpha b => R1 AlphaD a -> AlphaCtx -> b -> a -> a
closeR1 (Data1 _ cons) = \i a d ->
   case (findCon cons d) of
      Val c rec kids ->
        to c (map_l (\z -> closeD z i a) rec kids)
closeR1 _               = \_ _ d -> d

-- | Generic version of open

openR1 :: Alpha b => R1 AlphaD a -> AlphaCtx -> b -> a -> a
openR1 (Data1 _ cons) = \i a d ->
   case (findCon cons d) of
      Val c rec kids ->
        to c (map_l (\z -> openD z i a) rec kids)
openR1 _               = \_ _ d -> d

-- | Generic version of swaps

swapsR1 :: R1 AlphaD a -> AlphaCtx -> Perm AnyName -> a -> a
swapsR1 (Data1 _ cons)  = \ p x d ->
  case (findCon cons d) of
    Val c rec kids -> to c (map_l (\z -> swapsD z p x) rec kids)
swapsR1 _               = \ _ _ d -> d

-- | Generic version of fv

fvR1 :: Collection f => R1 (AlphaD) a -> AlphaCtx -> a -> f AnyName
fvR1 (Data1 _ cons) = \ p  d ->
  case (findCon cons d) of
    Val _ rec kids -> fv1 rec p kids where
          fv1 :: Collection f => MTup (AlphaD) l -> AlphaCtx -> l -> f AnyName
          fv1 MNil _ Nil = emptyC
          fv1 (r :+: rs) p (p1 :*: t1) =
               fvD r p p1 `union` fv1 rs p t1
fvR1 _ = \ _ _ -> emptyC


{-
matchR1 :: R1 (AlphaD) a -> AlphaCtx -> a -> a -> Maybe (Perm AnyName)
matchR1 (Data1 _ cons) = loop cons where
   loop (Con emb reps : rest) p x y =
     case (from emb x, from emb y) of
      (Just p1, Just p2) -> match1 reps p p1 p2
      (Nothing, Nothing) -> loop rest p x y
      (_,_)              -> Nothing
   loop [] _ _ _ = error "Impossible"
matchR1 Int1 = \ _ x y -> if x == y then Just empty else Nothing
matchR1 Integer1 = \ _ x y -> if x == y then Just empty else Nothing
matchR1 Char1 = \ _ x y -> if x == y then Just empty else Nothing
matchR1 _ = \ _ _ _ -> Nothing

match1 :: MTup (AlphaD) l -> AlphaCtx -> l -> l -> Maybe (Perm AnyName)
match1 MNil _ Nil Nil = Just empty
match1 (r :+: rs) c (p1 :*: t1) (p2 :*: t2) = do
  l1 <- matchD r c p1 p2
  l2 <- match1 rs c t1 t2
  (l1 `join` l2)
-}

-- | Generic version of aeq

aeqR1 :: R1 (AlphaD) a -> AlphaCtx -> a -> a -> Bool
aeqR1 (Data1 _ cons) = loop cons where
   loop (Con emb reps : rest) p x y =
     case (from emb x, from emb y) of
      (Just p1, Just p2) -> aeq1 reps p p1 p2
      (Nothing, Nothing) -> loop rest p x y
      (_,_)              -> False
   loop [] _ _ _ = error "Impossible"
aeqR1 Int1     = \ _ x y ->  x == y
aeqR1 Integer1 = \ _ x y -> x == y
aeqR1 Char1    = \ _ x y -> x == y
aeqR1 (Abstract1 _) = \ _ x y -> error "Must override aeq' for abstract types"
aeqR1 _        = \ _ _ _ -> False

-- | Generic list version of aeq

aeq1 :: MTup (AlphaD) l -> AlphaCtx -> l -> l -> Bool
aeq1 MNil _ Nil Nil = True
aeq1 (r :+: rs) c (p1 :*: t1) (p2 :*: t2) = do
  aeqD r c p1 p2 && aeq1 rs c t1 t2

-- | Generic version of freshen

freshenR1 :: Fresh m => R1 (AlphaD) a -> AlphaCtx -> a -> m (a,Perm AnyName)
freshenR1 (Data1 _ cons) = \ p d ->
   case findCon cons d of
     Val c rec kids -> do
       (l, p') <- freshenL rec p kids
       return (to c l, p')
freshenR1 _ = \ _ n -> return (n, empty)

-- | Generic list version of freshen

freshenL :: Fresh m => MTup (AlphaD) l -> AlphaCtx -> l -> m (l, Perm AnyName)
freshenL MNil _ Nil = return (Nil, empty)
freshenL (r :+: rs) p (t :*: ts) = do
  (xs, p2) <- freshenL rs p ts
  (x, p1) <- freshenD r p (swapsD r p p2 t)
  return ( x :*: xs, p1 <> p2)

lfreshenR1 :: LFresh m => R1 AlphaD a -> AlphaCtx -> a ->
              (a -> Perm AnyName -> m b) -> m b
lfreshenR1 (Data1 _ cons) = \p d f ->
   case findCon cons d of
     Val c rec kids -> lfreshenL rec p kids (\ l p' -> f (to c l) p')
lfreshenR1 _ = \ _ n f -> f n empty

lfreshenL :: LFresh m => MTup (AlphaD) l -> AlphaCtx -> l ->
              (l -> Perm AnyName -> m b) -> m b
lfreshenL MNil _ Nil f = f Nil empty
lfreshenL (r :+: rs) p (t :*: ts) f =
  lfreshenL rs p ts ( \ y p2 ->
  lfreshenD r p (swapsD r p p2 t) ( \ x p1 ->
     f (x :*: y) (p1 <> p2)))


findpatR1 :: R1 AlphaD b -> b -> AnyName -> FindResult
findpatR1 (Data1 _dt cons) = \ d n ->
   case findCon cons d of
     Val _c rec kids -> findpatL rec kids n
findpatR1 _ = \ _ _ -> mempty

findpatL :: MTup AlphaD l -> l -> AnyName -> FindResult
findpatL MNil Nil _              = mempty
findpatL (r :+: rs) (t :*: ts) n = findpatD r t n <> findpatL rs ts n

nthpatR1 :: R1 AlphaD b -> b -> NthCont
nthpatR1 (Data1 _dt cons) = \ d ->
   case findCon cons d of
     Val _c rec kids -> nthpatL rec kids
nthpatR1 _ = \ _ -> mempty

nthpatL :: MTup AlphaD l -> l -> NthCont
nthpatL MNil Nil              = mempty
nthpatL (r :+: rs) (t :*: ts) = nthpatD r t <> nthpatL rs ts

combine :: Maybe [AnyName] -> Maybe [AnyName] -> Maybe [AnyName]
combine (Just ns1) (Just ns2) | ns1 `intersect` ns2 == [] =
                                  Just (ns1 ++ ns2)
combine _ _ = Nothing

isPatR1 :: R1 AlphaD b -> b -> Maybe [AnyName]
isPatR1 (Data1 _dt cons) = \ d ->
   case findCon cons d of
     Val _c rec kids ->
       foldl_l (\ c b a -> combine (isPatD c a) b) (Just []) rec kids
isPatR1 _ = \ _ -> Just []

isTermR1 :: R1 AlphaD b -> b -> Bool
isTermR1 (Data1 _dt cons) = \ d ->
   case findCon cons d of
     Val _c rec kids -> foldl_l (\ c b a -> isTermD c a && b) True rec kids
isTermR1 _ = \ _ -> True

-- Exactly like the generic Ord instance defined in Generics.RepLib.PreludeLib,

-- except that the comparison operation takes an AlphaCtx


acompareR1 :: R1 AlphaD a -> AlphaCtx -> a -> a -> Ordering
acompareR1 Int1  _ = \x y -> compare x y
acompareR1 Char1 _ = \x y -> compare x y
acompareR1 (Data1 _ cons) c = \x y ->
             let loop (Con emb rec : rest) =
                     case (from emb x, from emb y) of
                        (Just t1, Just t2) -> compareTupM rec c t1 t2
                        (Just _ , Nothing) -> LT
                        (Nothing, Just _ ) -> GT
                        (Nothing, Nothing) -> loop rest
                 loop [] = error "acompareR1 found no constructors! Please report this as a bug."
             in loop cons
acompareR1 r1 _ = error ("acompare' not supported for " ++ show r1)

compareTupM :: MTup AlphaD l -> AlphaCtx -> l -> l -> Ordering
compareTupM MNil _ Nil Nil = EQ
compareTupM (x :+: xs) c (y :*: ys) (z :*: zs) =
   mappend (acompareD x c y z) (compareTupM xs c ys zs)


------------------------------------------------------------

-- Specific Alpha instances for the binding combinators:

--      Name, Bind, Embed, Rebind, Rec, Shift

-----------------------------------------------------------


-- In the Name instance, if the mode is Term then the operation

-- observes the name. In Pat mode the name is a binder, so the name is

-- usually ignored.

instance Rep a => Alpha (Name a) where

  -- Both bound and free names are valid terms.

  isTerm _ = True

  -- Only free names are valid as patterns, which serve as binders.

  isPat n@(Nm _ _) = Just [AnyName n]
  isPat _          = Nothing

  fv' c n@(Nm _ _)  | mode c == Term = singleton (AnyName n)
  fv' _ _                            = emptyC

  swaps' c p x | mode c == Term =
                     case apply p (AnyName x) of
                       AnyName y ->
                         case gcastR (getR y) (getR x) y of
                           Just y' -> y'
                           Nothing -> error "Internal error in swaps': sort mismatch"
  swaps' _ _ x = x

  aeq' c x y   | mode c == Term = x == y

  aeq' _ _ _   = True

{-
  match' _ x  y   | x == y         = Just empty
  match' c n1 n2  | mode c == Term = Just $ single (AnyName n1) (AnyName n2)
  match' c _ _    | mode c == Pat  = Just empty
-}

  freshen' c nm | mode c == Pat  = do x <- fresh nm
                                      return (x, single (AnyName nm) (AnyName x))

  freshen' _ _ = error "freshen' on Name in Term mode"

  lfreshen' c nm f = case mode c of
     Pat  -> do x <- lfresh nm
                avoid [AnyName x] $ f x (single (AnyName nm) (AnyName x))
     Term -> error "lfreshen' on Name in Term mode"

  open c a (Bn r j x) | mode c == Term && level c == j =
    case nthpat a x of
      AnyName nm -> case gcastR (getR nm) r nm of
        Just nm' -> nm'
        Nothing  -> error "Internal error in open: sort mismatch"
  open _ _ n = n

  close c a nm@(Nm r _) | mode c == Term && level c >= 0 =
      case findpat a (AnyName nm) of
        Just x  -> Bn r (level c) x
        Nothing -> nm
  close _ _ n = n

  findpatrec nm1 (AnyName nm2) =
    case gcastR (getR nm1) (getR nm2) nm1 of
      Just nm1' -> if nm1' == nm2 then Index 0 else NamesSeen 1
      Nothing   -> NamesSeen 1

  nthpatrec = nthName . AnyName

  acompare' c (Nm r1 n1)    (Nm r2 n2)
    | mode c == Term = mconcat [compare r1 r2, compare n1 n2]

  acompare' c (Bn r1 m1 n1) (Bn r2 m2 n2)
    | mode c == Term = mconcat [compare r1 r2, compare m1 m2, compare n1 n2]

  acompare' c (Nm _ _)   (Bn _ _ _) | mode c == Term = LT
  acompare' c (Bn _ _ _) (Nm _ _)   | mode c == Term = GT

  acompare' _ _          _                           = EQ

instance Alpha AnyName  where

  isTerm _ = True

  isPat n@(AnyName (Nm _ _)) = Just [n]
  isPat _                    = Nothing

  fv' c n@(AnyName (Nm _ _))  | mode c == Term = singleton n
  fv' _ _                                      = emptyC

  swaps' c p x = case mode c of
                   Term -> apply p x
                   Pat  -> x

  aeq' _ x y | x == y         = True
  aeq' c _ _ | mode c == Term = False

  aeq' _ _ _                  = True

{-
  match' _ x y | x == y          = Just empty
  match' c (AnyName n1) (AnyName n2)
    | mode c == Term =
      case gcastR (getR n1) (getR n2) n1 of
        Just n1' -> Just $ single (AnyName n1) (AnyName n2)
        Nothing  -> Nothing
  match' c _ _           | mode c == Pat   = Just empty
-}

  acompare' _ x y | x == y          = EQ
  acompare' c (AnyName n1) (AnyName n2)
    | mode c == Term =
      case compareR (getR n1) (getR n2) of
       EQ ->  case gcastR (getR n1) (getR n2) n1 of
          Just n1' -> acompare' c n1' n2
          Nothing  -> error "impossible"
       neq -> neq
  acompare' _ _ _           = EQ

  freshen' c (AnyName nm) = case mode c of
     Pat  -> do x <- fresh nm
                return (AnyName x, single (AnyName nm) (AnyName x))
     Term -> error "freshen' on AnyName in Term mode"

  lfreshen' c (AnyName nm) f = case mode c of
     Pat  -> do x <- lfresh nm
                avoid [AnyName x] $ f (AnyName x) (single (AnyName nm) (AnyName x))
     Term -> error "lfreshen' on AnyName in Term mode"

  open c a (AnyName (Bn _ j x)) | level c == j = nthpat a x
  open _ _ n = n

  close c a nm@(AnyName (Nm r _)) =
    case findpat a nm of
      Just x  -> AnyName (Bn r (level c) x)
      Nothing -> nm

  close _ _ n = n

  findpatrec nm1 nm2 | nm1 == nm2 = Index 0
  findpatrec _ _ = NamesSeen 1

  nthpatrec = nthName

instance (Rep order, Rep card, Alpha p, Alpha t) => Alpha (GenBind order card p t) where
    isPat _ = Nothing
    isTerm (B p t) = isJust (isPat p) && isTerm t

    swaps' c pm (B p t) =
        (B (swaps' (pat c) pm p)
           (swaps' (incr c) pm t))

    fv' c (B p t) = fv' (pat c) p `union` fv' (incr c) t

    freshen' c (B p t) = do
      (p', pm1) <- freshen' (pat c) p
      (t', pm2) <- freshen' (incr c) (swaps' (incr c) pm1 t)
      return (B p' t', pm1 <> pm2)

    lfreshen' c (B p t) f =
      lfreshen' (pat c) p (\ p' pm1 ->
      lfreshen' (incr c) (swaps' (incr c) pm1 t) (\ t' pm2 ->
      f (B p' t') (pm1 <> pm2)))

    aeq' c (B p1 t1) (B p2 t2) = do
      aeq' (pat c) p1 p2  && aeq' (incr c) t1 t2

{-
    match' c (B p1 t1) (B p2 t2) = do
      pp <- match' (pat c) p1 p2
      pt <- match' (incr c) t1 t2
      -- need to make sure that all permutations of
      -- bound variables at this
      -- level are the identity
      (pp `join` pt)
-}

    open  c a (B p t)    = B (open (pat c) a p)  (open  (incr c) a t)
    close c a (B p t)    = B (close (pat c) a p) (close (incr c) a t)

    --  Comparing two binding terms.

    acompare' c (B p1 t1) (B p2 t2) =
      mappend (acompare' (pat c) p1 p2) (acompare' (incr c) t1 t2)

    findpatrec _ b = error $ "Binding used as a pattern"
    nthpatrec    b = error $ "Binding used as a pattern"

instance (Alpha p, Alpha q) => Alpha (Rebind p q) where
  isTerm _ = False
  isPat (R p q) = combine (isPat p) (isPat q)

  swaps' c pm (R p q) = R (swaps' c pm p) (swaps' (incr c) pm q)

  fv' c (R p q) =  fv' c p `union` fv' (incr c) q

  lfreshen' c (R p q) g
    | mode c == Term = error "lfreshen' on Rebind in Term mode"
    | otherwise =
        lfreshen' c p $ \ p' pm1 ->
        lfreshen' (incr c) (swaps' (incr c) pm1 q) $ \ q' pm2 ->
        g (R p' q') (pm1 <> pm2)

  freshen' c (R p q)
    | mode c == Term = error "freshen' on Rebind in Term mode"
    | otherwise = do
        (p', pm1) <- freshen' c p
        (q', pm2) <- freshen' (incr c) (swaps' (incr c) pm1 q)
        return (R p' q', pm1 <> pm2)

  aeq' c (R p1 q1) (R p2 q2 ) = do
      aeq' c p1 p2 && aeq' c q1 q2

{-
  match' c (R p1 q1) (R p2 q2) = do
     pp <- match' c p1 p2
     pq <- match' (incr c)  q1 q2
     (pp `join` pq)
-}

  acompare' c (R a1 a2) (R b1 b2) =
      mappend (acompare' c a1 b1) (acompare' (incr c) a2 b2)


  open c a (R p q)  = R (open c a p) (open (incr c) a q)
  close c a (R p q) = R (close c a p) (close (incr c) a q)

  findpatrec (R p q) nm = findpatrec p nm <> findpatrec q nm

  nthpatrec (R p q) = nthpatrec p <> nthpatrec q


instance Alpha p => Alpha (Rec p) where
   {-  default defs of these work if we don't care about incrs

   fv' c (Rec a) = fv' c a
   aeq' c (Rec a) (Rec b) = aeq' c a b
   acompare' c (Rec a) (Rec b) = acompare' c a b
   swaps' p pm (Rec a) = Rec (swaps' p pm a)
   findpatrec (Rec a) nm = findpatrec a nm
   nthpatrec (Rec a)  i  = nthpatrec a i

   -}
   isPat (Rec p) = isPat p
   isTerm _ = False

   open  c a (Rec p) = Rec (open  (incr c) a p)
   close c a (Rec p) = Rec (close (incr c) a p)


-- note: for Embeds, when the mode is "term" then we are

-- implementing the "binding" version of the function

-- and we generally should treat the embeds as constants

instance Alpha t => Alpha (Embed t) where
   isPat (Embed t)   = if (isTerm t) then Just [] else Nothing
   isTerm _          = False
   isEmbed (Embed t) = isTerm t

   swaps' c pm (Embed t) =
     case mode c of
       Pat  -> Embed (swaps' (term c) pm t)
       Term -> Embed t

   fv' c (Embed t) =
     case mode c of
       Pat  -> fv' (term c) t
       Term -> emptyC

   freshen' c p | mode c == Term = error "freshen' called on a term"
                | otherwise      = return (p, empty)

   lfreshen' c p f         | mode c == Term = error "lfreshen' called on a term"
                           | otherwise      = f p empty

   aeq' c (Embed x) (Embed y) = aeq' (term c) x y

   acompare' c (Embed x) (Embed y) = acompare' (term c) x y

{-
   match' c (Embed x) (Embed y) | mode c == Pat  = match' (term c) x y
   match' c (Embed x) (Embed y) | mode c == Term =
                                    if x `aeq` y
                                    then Just empty
                                    else Nothing
-}

   close c b (Embed x) = case mode c of
     Pat  -> Embed (close (term c) b x)
     Term -> error "close on Embed"

   open c b (Embed x) = case mode c of
     Pat  -> Embed (open (term c) b x)
     Term -> error "open on Embed"

   findpatrec _ _ = mempty
   nthpatrec _    = mempty

instance IsEmbed (Embed t) where
  type Embedded (Embed t) = t

  embed             = Embed
  unembed (Embed t) = t

instance Alpha a => Alpha (Shift a) where

  -- The contents of Shift may only be an Embed or another Shift.

  isPat (Shift a)   = if (isEmbed a) then Just [] else Nothing
  isTerm _          = False
  isEmbed (Shift a) = isEmbed a

  close c b (Shift x) = Shift (close (decr c) b x)
  open  c b (Shift x) = Shift (open  (decr c) b x)

instance IsEmbed e => IsEmbed (Shift e) where
  type Embedded (Shift e) = Embedded e

  embed             = Shift . embed
  unembed (Shift e) = unembed e

-- Instances for other types use the default definitions.

instance Alpha Bool
instance Alpha Float
instance Alpha ()
instance Alpha a => Alpha [a]
instance Alpha Int
instance Alpha Integer
instance Alpha Double
instance Alpha Char
instance Alpha a => Alpha (Maybe a)
instance (Alpha a,Alpha b) => Alpha (Either a b)
instance (Alpha a,Alpha b) => Alpha (a,b)
instance (Alpha a,Alpha b,Alpha c) => Alpha (a,b,c)
instance (Alpha a, Alpha b,Alpha c, Alpha d) => Alpha (a,b,c,d)
instance (Alpha a, Alpha b,Alpha c, Alpha d, Alpha e) =>
   Alpha (a,b,c,d,e)

instance (Rep a) => Alpha (R a)