{-# LANGUAGE CPP #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# 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.Monoid

import Test.QuickCheck

import qualified Agda.Benchmarking as Bench

import Agda.Syntax.Common hiding (Arg, Dom, NamedArg)
import Agda.Syntax.Internal

-- import Agda.TypeChecking.Irrelevance

import Agda.Utils.Function
import Agda.Utils.Functor
import Agda.Utils.Monad
import Agda.Utils.Singleton
import Agda.Utils.VarSet (VarSet)

#include "undefined.h"
import Agda.Utils.Impossible

-- | 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      -- ^ 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, Enum, Bounded)

-- | '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, _) -> Flexible
    (_, Flexible) -> Flexible
    (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 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 => Monoid (FreeM c) where
  mempty  = pure mempty
  mappend = liftA2 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 = local $ \ e -> e { feBinders = 1 + 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 _ ts   -> go Flexible $ freeVars' ts
    DontCare mt  -> goRel Irrelevant $ freeVars' mt
    Shared p     -> freeVars' (derefPtr p)
    ExtLam cs ts -> freeVars' (cs, ts)

instance Free' Type 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 _ vs   -> go Flexible $ 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' ClauseBody c where
  -- {-# SPECIALIZE instance Free' ClauseBody All #-}
  -- {-# SPECIALIZE freeVars' :: ClauseBody -> FreeM Any #-}
  -- {-# SPECIALIZE freeVars' :: ClauseBody -> FreeM All #-}
  -- {-# SPECIALIZE freeVars' :: ClauseBody -> FreeM VarSet #-}
  -- {-# SPECIALIZE freeVars' :: ClauseBody -> FreeM VarMap #-}
  freeVars' (Body t)   = freeVars' t
  freeVars' (Bind b)   = freeVars' b
  freeVars'  NoBody    = mempty

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' = freeVars' . clauseBody

-- Generators

instance Arbitrary FlexRig where
  arbitrary = arbitraryBoundedEnum

instance Arbitrary VarOcc where
  arbitrary = VarOcc <$> arbitrary <*> arbitrary

-- -}
