{-# LANGUAGE UndecidableInstances #-}

-- | Computing the free variables of a term lazily.
--
-- We implement a reduce (traversal into monoid) over internal syntax
-- for a generic collection (monoid with singletons).  This should allow
-- a more efficient test for the presence of a particular variable.
--
-- Worst-case complexity does not change (i.e. the case when a variable
-- does not occur), but best case-complexity does matter.  For instance,
-- see 'Agda.TypeChecking.Substitute.mkAbs': each time we construct
-- a dependent function type, we check it is actually dependent.
--
-- The distinction between rigid and strongly rigid occurrences comes from:
--   Jason C. Reed, PhD thesis, 2009, page 96 (see also his LFMTP 2009 paper)
--
-- The main idea is that x = t(x) is unsolvable if x occurs strongly rigidly
-- in t.  It might have a solution if the occurrence is not strongly rigid, e.g.
--
--   x = \f -> suc (f (x (\ y -> k)))  has  x = \f -> suc (f (suc k))
--
-- [Jason C. Reed, PhD thesis, page 106]
--
-- Under coinductive constructors, occurrences are never strongly rigid.
-- Also, function types and lambdas do not establish strong rigidity.
-- Only inductive constructors do so.
-- (See issue 1271).

module Agda.TypeChecking.Free.Lazy where

import Control.Applicative hiding (empty)
import Control.Monad.Reader

import Data.Foldable (foldMap)
import Data.IntMap (IntMap)
import Data.Semigroup (Semigroup, Monoid, (<>), mempty, mappend, mconcat)
import Data.Set (Set)

import Agda.Syntax.Common
import Agda.Syntax.Internal

-- import Agda.TypeChecking.Irrelevance

import Agda.Utils.Functor
import Agda.Utils.Monad
import Agda.Utils.Singleton
import Agda.Utils.Size

type MetaSet = Set MetaId

-- | Depending on the surrounding context of a variable,
--   it's occurrence can be classified as flexible or rigid,
--   with finer distinctions.
--
--   The constructors are listed in increasing order (wrt. information content).
data FlexRig
  = Flexible MetaSet  -- ^ In arguments of metas.
  | WeaklyRigid       -- ^ In arguments to variables and definitions.
  | Unguarded         -- ^ In top position, or only under inductive record constructors.
  | StronglyRigid     -- ^ Under at least one and only inductive constructors.
  deriving (Eq, Ord, Show)

-- | 'FlexRig' composition.  For accumulating the context of a variable.
--
--   'Flexible' is dominant.  Once we are under a meta, we are flexible
--   regardless what else comes.
--
--   'WeaklyRigid' is next in strength.  Destroys strong rigidity.
--
--   'StronglyRigid' is still dominant over 'Unguarded'.
--
--   'Unguarded' is the unit.  It is the top (identity) context.
composeFlexRig :: FlexRig -> FlexRig -> FlexRig
composeFlexRig o o' =
  case (o, o') of
    (Flexible ms1, Flexible ms2) -> Flexible $ ms1 `mappend` ms2
    (Flexible ms1, _) -> Flexible ms1
    (_, Flexible ms2) -> Flexible ms2
    (WeaklyRigid, _) -> WeaklyRigid
    (_, WeaklyRigid) -> WeaklyRigid
    (StronglyRigid, _) -> StronglyRigid
    (_, StronglyRigid) -> StronglyRigid
    (Unguarded, Unguarded) -> Unguarded

-- -- | 'FlexRig' supremum.  Extract the most information about a variable.
-- --
-- --   We make this the default 'Monoid' for 'FlexRig'.
-- instance Monoid FlexRig where
--   mempty  = minBound
--   mappend = max

-- | Occurrence of free variables is classified by several dimensions.
--   Currently, we have 'FlexRig' and 'Relevance'.
data VarOcc = VarOcc
  { varFlexRig   :: FlexRig
  , varRelevance :: Relevance
  }
  deriving (Eq, Show)

-- | When we extract information about occurrence, we care most about
--   about 'StronglyRigid' 'Relevant' occurrences.
maxVarOcc :: VarOcc -> VarOcc -> VarOcc
maxVarOcc (VarOcc o r) (VarOcc o' r') = VarOcc (max o o') (min r r')

topVarOcc :: VarOcc
topVarOcc = VarOcc StronglyRigid Relevant

botVarOcc :: VarOcc
botVarOcc = VarOcc (Flexible mempty) Irrelevant

type VarMap = IntMap VarOcc

-- * Collecting free variables.

-- | Where should we skip sorts in free variable analysis?

data IgnoreSorts
  = IgnoreNot            -- ^ Do not skip.
  | IgnoreInAnnotations  -- ^ Skip when annotation to a type.
  | IgnoreAll            -- ^ Skip unconditionally.
  deriving (Eq, Show)

-- | The current context.

data FreeEnv c = FreeEnv
  { feIgnoreSorts   :: !IgnoreSorts
    -- ^ Ignore free variables in sorts.
  , feBinders       :: !Int
    -- ^ Under how many binders have we stepped?
  , feFlexRig       :: !FlexRig
    -- ^ Are we flexible or rigid?
  , feRelevance     :: !Relevance
    -- ^ What is the current relevance?
  , feSingleton     :: SingleVar c
    -- ^ Method to return a single variable.
  }

type Variable    = (Int, VarOcc)
type SingleVar c = Variable -> c

-- | The initial context.

initFreeEnv :: SingleVar c -> FreeEnv c
initFreeEnv sing = FreeEnv
  { feIgnoreSorts = IgnoreNot
  , feBinders     = 0
  , feFlexRig     = Unguarded
  , feRelevance   = Relevant
  , feSingleton   = sing
  }

type FreeM c = Reader (FreeEnv c) c

instance Monoid c => Semigroup (FreeM c) where
  (<>) = liftA2 mappend
instance Monoid c => Monoid (FreeM c) where
  mempty  = pure mempty
  mappend = (<>)
  mconcat = mconcat <.> sequence

-- instance Singleton a c => Singleton a (FreeM c) where
--   singleton = pure . singleton

-- | Base case: a variable.
variable :: (Monoid c) => Int -> FreeM c
variable n = do
  m <- (n -) <$> asks feBinders
  if m < 0 then mempty else do
    o <- asks feFlexRig
    r <- asks feRelevance
    s <- asks feSingleton
    pure $ s (m, VarOcc o r)

-- | Going under a binder.
bind :: FreeM a -> FreeM a
bind = bind' 1

bind' :: Nat -> FreeM a -> FreeM a
bind' n = local $ \ e -> e { feBinders = n + feBinders e }

-- | Changing the 'FlexRig' context.
go :: FlexRig -> FreeM a -> FreeM a
go o = local $ \ e -> e { feFlexRig = composeFlexRig o $ feFlexRig e }

-- | Changing the 'Relevance'.
goRel :: Relevance-> FreeM a -> FreeM a
goRel r = local $ \ e -> e { feRelevance = composeRelevance r $ feRelevance e }

-- | What happens to the variables occurring under a constructor?
underConstructor :: ConHead -> FreeM a -> FreeM a
underConstructor (ConHead c i fs) =
  case (i,fs) of
    -- Coinductive (record) constructors admit infinite cycles:
    (CoInductive, _)   -> go WeaklyRigid
    -- Inductive data constructors do not admit infinite cycles:
    (Inductive, [])    -> go StronglyRigid
    -- Inductive record constructors do not admit infinite cycles,
    -- but this cannot be proven inside Agda.
    -- Thus, unification should not prove it either.
    (Inductive, (_:_)) -> id

-- | Gather free variables in a collection.
class Free' a c where
  -- Misplaced SPECIALIZE pragma:
  -- {-# SPECIALIZE freeVars' :: a -> FreeM Any #-}
  -- So you cannot specialize all instances in one go. :(
  freeVars' :: (Monoid c) => a -> FreeM c

instance Free' Term c where
  -- SPECIALIZE instance does not work as well, see
  -- https://ghc.haskell.org/trac/ghc/ticket/10434#ticket
  -- {-# SPECIALIZE instance Free' Term All #-}
  -- {-# SPECIALIZE freeVars' :: Term -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Term -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Term -> FreeM VarSet #-}
  freeVars' t = case t of
    Var n ts     -> variable n `mappend` do go WeaklyRigid $ freeVars' ts
    -- λ is not considered guarding, as
    -- we cannot prove that x ≡ λy.x is impossible.
    Lam _ t      -> freeVars' t
    Lit _        -> mempty
    Def _ ts     -> go WeaklyRigid $ freeVars' ts  -- because we are not in TCM
      -- we cannot query whether we are dealing with a data/record (strongly r.)
      -- or a definition by pattern matching (weakly rigid)
      -- thus, we approximate, losing that x = List x is unsolvable
    Con c _ ts   -> underConstructor c $ freeVars' ts
    -- Pi is not guarding, since we cannot prove that A ≡ B → A is impossible.
    -- Even as we do not permit infinite type expressions,
    -- we cannot prove their absence (as Set is not inductive).
    -- Also, this is incompatible with univalence (HoTT).
    Pi a b       -> freeVars' (a,b)
    Sort s       -> freeVars' s
    Level l      -> freeVars' l
    MetaV m ts   -> go (Flexible $ singleton m) $ freeVars' ts
    DontCare mt  -> goRel Irrelevant $ freeVars' mt
    Shared p     -> freeVars' (derefPtr p)

instance Free' a c => Free' (Type' a) c where
  -- {-# SPECIALIZE instance Free' Type All #-}
  -- {-# SPECIALIZE freeVars' :: Type -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Type -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Type -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Type -> FreeM VarMap #-}
  freeVars' (El s t) =
    ifM ((IgnoreNot ==) <$> asks feIgnoreSorts)
      {- then -} (freeVars' (s, t))
      {- else -} (freeVars' t)

instance Free' Sort c where
  -- {-# SPECIALIZE instance Free' Sort All #-}
  -- {-# SPECIALIZE freeVars' :: Sort -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Sort -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Sort -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Sort -> FreeM VarMap #-}
  freeVars' s =
    ifM ((IgnoreAll ==) <$> asks feIgnoreSorts) mempty $ {- else -}
    case s of
      Type a     -> freeVars' a
      Prop       -> mempty
      Inf        -> mempty
      SizeUniv   -> mempty
      DLub s1 s2 -> go WeaklyRigid $ freeVars' (s1, s2)

instance Free' Level c where
  -- {-# SPECIALIZE instance Free' Level All #-}
  -- {-# SPECIALIZE freeVars' :: Level -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Level -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Level -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Level -> FreeM VarMap #-}
  freeVars' (Max as) = freeVars' as

instance Free' PlusLevel c where
  -- {-# SPECIALIZE instance Free' PlusLevel All #-}
  -- {-# SPECIALIZE freeVars' :: PlusLevel -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: PlusLevel -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: PlusLevel -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: PlusLevel -> FreeM VarMap #-}
  freeVars' ClosedLevel{} = mempty
  freeVars' (Plus _ l)    = freeVars' l

instance Free' LevelAtom c where
  -- {-# SPECIALIZE instance Free' LevelAtom All #-}
  -- {-# SPECIALIZE freeVars' :: LevelAtom -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: LevelAtom -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: LevelAtom -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: LevelAtom -> FreeM VarMap #-}
  freeVars' l = case l of
    MetaLevel m vs   -> go (Flexible $ singleton m) $ freeVars' vs
    NeutralLevel _ v -> freeVars' v
    BlockedLevel _ v -> freeVars' v
    UnreducedLevel v -> freeVars' v

instance Free' a c => Free' [a] c where
  -- {-# SPECIALIZE instance Free' a All => Free' [a] All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => [a] -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => [a] -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => [a] -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => [a] -> FreeM VarMap #-}
  freeVars' = foldMap freeVars'

instance Free' a c => Free' (Maybe a) c where
  -- {-# SPECIALIZE instance Free' a All => Free' (Maybe a) All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => Maybe a -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => Maybe a -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => Maybe a -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => Maybe a -> FreeM VarMap #-}
  freeVars' = foldMap freeVars'

instance (Free' a c, Free' b c) => Free' (a,b) c where
  -- {-# SPECIALIZE instance (Free' a All, Free' b All) => Free' (a,b) All #-}
  -- {-# SPECIALIZE freeVars' :: (Free' a Any, Free' b Any) => (a,b) -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: (Free' a All, Free' b All) => (a,b) -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: (Free' a VarSet, Free' b VarSet) => (a,b) -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: (Free' a VarMap, Free' b VarMap) => (a,b) -> FreeM VarMap #-}
  freeVars' (x,y) = freeVars' x `mappend` freeVars' y

instance Free' a c => Free' (Elim' a) c where
  -- {-# SPECIALIZE instance Free' a All => Free' (Elim' a) All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => Elim' a -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => Elim' a -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => Elim' a -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => Elim' a -> FreeM VarMap #-}
  freeVars' (Apply a) = freeVars' a
  freeVars' (Proj{} ) = mempty

instance Free' a c => Free' (Arg a) c where
  -- {-# SPECIALIZE instance Free' a All => Free' (Arg a) All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => Arg a -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => Arg a -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => Arg a -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => Arg a -> FreeM VarMap #-}
  freeVars' a = goRel (getRelevance a) $ freeVars' $ unArg a

instance Free' a c => Free' (Dom a) c where
  -- {-# SPECIALIZE instance Free' a All => Free' (Dom a) All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => Dom a -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => Dom a -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => Dom a -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => Dom a -> FreeM VarMap #-}
  freeVars' = freeVars' . unDom

instance Free' a c => Free' (Abs a) c where
  -- {-# SPECIALIZE instance Free' a All => Free' (Abs a) All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => Abs a -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => Abs a -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => Abs a -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => Abs a -> FreeM VarMap #-}
  freeVars' (Abs   _ b) = bind $ freeVars' b
  freeVars' (NoAbs _ b) = freeVars' b

instance Free' a c => Free' (Tele a) c where
  -- {-# SPECIALIZE instance Free' a All => Free' (Tele a) All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a Any => Tele a -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Free' a All => Tele a -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarSet => Tele a -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Free' a VarMap => Tele a -> FreeM VarMap #-}
  freeVars' EmptyTel          = mempty
  freeVars' (ExtendTel a tel) = freeVars' (a, tel)

instance Free' Clause c where
  -- {-# SPECIALIZE instance Free' Clause All #-}
  -- {-# SPECIALIZE freeVars' :: Clause -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: Clause -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: Clause -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: Clause -> FreeM VarMap #-}
  freeVars' cl = bind' (size $ clauseTel cl) $ freeVars' $ clauseBody cl

instance Free' EqualityView c where
  freeVars' (OtherType t) = freeVars' t
  freeVars' (EqualityType s _eq l t a b) = freeVars' s `mappend` freeVars' [l, t, a, b]