{-# LANGUAGE CPP, MultiParamTypeClasses,
             FunctionalDependencies, UndecidableInstances,
             TypeSynonymInstances, FlexibleInstances
  #-}

module Agda.TypeChecking.Test.Generators where

import Control.Applicative
import Control.Monad
import Control.Monad.State
import qualified Data.List as List (sort, nub)
import Agda.Utils.QuickCheck hiding (Args)

import Agda.Syntax.Position
import Agda.Syntax.Common
import Agda.Syntax.Literal
import Agda.Syntax.Fixity
import Agda.Syntax.Internal
import qualified Agda.Syntax.Concrete.Name as C
import Agda.TypeChecking.Free
import Agda.TypeChecking.Substitute
import Agda.Utils.TestHelpers
import Agda.Utils.Monad
import Agda.Utils.QuickCheck hiding (Args)
import qualified Agda.Utils.VarSet as Set

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

data TermConfiguration = TermConf
      { tcDefinedNames	   :: [QName]
      , tcConstructorNames :: [QName]
      , tcFreeVariables	   :: [Nat]
      , tcLiterals	   :: UseLiterals
      , tcFrequencies	   :: Frequencies
      , tcFixSize	   :: Maybe Int
	-- ^ Maximum size of the generated element. When @Nothing@ this value
	--   is initialized from the 'Test.QuickCheck.size' parameter.
      , tcIsType	   :: Bool
	-- ^ When this is true no lambdas, literals, or constructors are
	--   generated
      }
  deriving Show

data Frequencies = Freqs
      { hiddenFreqs :: HiddenFreqs
      , nameFreqs   :: NameFreqs
      , sortFreqs   :: SortFreqs
      , termFreqs   :: TermFreqs
      }
  deriving Show

data TermFreqs = TermFreqs
      { nameFreq :: Int
      , litFreq	 :: Int
      , sortFreq :: Int
      , lamFreq	 :: Int
      , piFreq	 :: Int
      , funFreq	 :: Int
      }
  deriving Show

data NameFreqs = NameFreqs
      { varFreq :: Int
      , defFreq :: Int
      , conFreq :: Int
      }
  deriving Show

data HiddenFreqs = HiddenFreqs
      { hiddenFreq    :: Int
      , notHiddenFreq :: Int
      }
  deriving Show

data SortFreqs = SortFreqs
      { setFreqs :: [Int]
      , propFreq :: Int
      }
  deriving Show

defaultFrequencies :: Frequencies
defaultFrequencies = Freqs
      { termFreqs   = TermFreqs	  { nameFreq = 40, litFreq = 1, sortFreq = 2, lamFreq = 10, piFreq = 5, funFreq = 5 }
      , nameFreqs   = NameFreqs	  { varFreq = 3, defFreq = 1, conFreq = 1 }
      , hiddenFreqs = HiddenFreqs { hiddenFreq = 1, notHiddenFreq = 5 }
      , sortFreqs   = SortFreqs	  { setFreqs = [3, 1], propFreq = 1 }
      }

noProp :: TermConfiguration -> TermConfiguration
noProp conf = conf { tcFrequencies = fq { sortFreqs = sfq { propFreq = 0 } } }
  where
    fq	= tcFrequencies conf
    sfq	= sortFreqs fq

data UseLiterals = UseLit
      { useLitInt    :: Bool
      , useLitFloat  :: Bool
      , useLitString :: Bool
      , useLitChar   :: Bool
      }
  deriving Show

noLiterals :: UseLiterals
noLiterals = UseLit False False False False

fixSizeConf :: Int -> TermConfiguration -> TermConfiguration
fixSizeConf n conf = conf { tcFixSize = Just n }

resizeConf :: (Int -> Int) -> TermConfiguration -> TermConfiguration
resizeConf f conf = conf { tcFixSize = fmap f $ tcFixSize conf}

decrConf :: TermConfiguration -> TermConfiguration
decrConf = resizeConf (flip (-) 1)

divConf :: TermConfiguration -> Int -> TermConfiguration
divConf conf k = resizeConf (`div` k) conf

isTypeConf :: TermConfiguration -> TermConfiguration
isTypeConf conf = conf { tcIsType = True }

isntTypeConf :: TermConfiguration -> TermConfiguration
isntTypeConf conf = conf { tcIsType = False }

extendConf :: TermConfiguration -> TermConfiguration
extendConf conf = conf { tcFreeVariables = 0 : map (1+) (tcFreeVariables conf) }

extendWithTelConf :: Telescope -> TermConfiguration -> TermConfiguration
extendWithTelConf tel conf = foldr (const extendConf) conf (telToList tel)

makeConfiguration :: [String] -> [String] -> [Nat] -> TermConfiguration
makeConfiguration ds cs vs = TermConf
  { tcDefinedNames     = defs
  , tcConstructorNames = cons
  , tcFreeVariables    = List.sort $ List.nub vs
  , tcFrequencies      = defaultFrequencies
  , tcLiterals	       = noLiterals
  , tcFixSize	       = Nothing
  , tcIsType	       = False
  }
  where
    (defs, cons) = flip evalState 0 $
		   (,) <$> mapM mkName ds <*> mapM mkName cs

    tick     = do x <- get; put (x + 1); return x
    mkName s = do
      n <- tick
      return $ QName { qnameModule = MName []
		     , qnameName   = Name
			{ nameId	  = NameId n 1
			, nameConcrete	  = C.Name noRange [C.Id s]
			, nameBindingSite = noRange
			, nameFixity	  = defaultFixity'
			}
		      }

class GenC a where
  genC :: TermConfiguration -> Gen a

newtype YesType a   = YesType	{ unYesType :: a     }
newtype NoType  a   = NoType	{ unNoType  :: a     }
newtype VarName	    = VarName	{ unVarName :: Nat   }
newtype DefName	    = DefName	{ unDefName :: QName }
newtype ConName	    = ConName	{ unConName :: QName }
newtype SizedList a = SizedList { unSizedList :: [a] }

fixSize :: TermConfiguration -> Gen a -> Gen a
fixSize conf g = sized $ \n -> resize (maybe n id $ tcFixSize conf) g

instance GenC a => GenC (SizedList a) where
  genC conf = do
    n <- fixSize conf natural
    SizedList <$> vectorOf n (genC $ divConf conf n)

instance GenC a => GenC [a] where
  genC conf = do
    n <- natural
    vectorOf n $ genC $ divConf conf n

instance (GenC a, GenC b) => GenC (a, b) where
  genC conf = (,) <$> genC conf2 <*> genC conf2
    where
      conf2 = divConf conf 2

instance GenC Range where
  genC _ = return noRange

instance GenC Hiding where
  genC conf = frequency [ (hideF, return Hidden), (nohideF, return NotHidden) ]
    where
      HiddenFreqs {hiddenFreq = hideF, notHiddenFreq = nohideF } =
	hiddenFreqs $ tcFrequencies conf

instance GenC a => GenC (Arg a) where
  genC conf = (\ (h, a) -> Arg h Relevant a) <$> genC conf

instance GenC a => GenC (Dom a) where
  genC conf = (\ (h, a) -> Dom h Relevant a) <$> genC conf

instance GenC a => GenC (Abs a) where
  genC conf = Abs "x" <$> genC (extendConf conf)

genArgs :: TermConfiguration -> Gen Args
genArgs conf = unSizedList <$> genC (isntTypeConf conf)

instance GenC Sort where
  genC conf = frequency $
    (propF, return Prop) :
    zip setFs (map (return . mkType) [0..])
    where
      freq f = f $ tcFrequencies conf
      setFs = freq (setFreqs . sortFreqs)
      propF = freq (propFreq . sortFreqs)

instance GenC Char where
  genC _ = elements [' '..'~'] -- TODO

instance GenC Double where
  genC _ = arbitrary

instance GenC Integer where
  genC _ = arbitrary

instance GenC Literal where
  genC conf = oneof (concat $ zipWith gen useLits
	      [ uncurry LitInt	  <$> genC conf
	      , uncurry LitFloat  <$> genC conf
	      , uncurry LitString <$> genC conf
	      , uncurry LitChar   <$> genC conf
	      ]
	   )
    where
      useLits = map ($ tcLiterals conf) [ useLitInt, useLitFloat, useLitString, useLitChar ]

      gen True  g = [g]
      gen False g = []

instance GenC Telescope where
  genC conf = do
    n <- fixSize conf natural
    let confs = take n $ iterate extendConf (divConf conf n)
    telFromList <$> mapM genC confs

instance GenC Type where
  genC conf = El <$> genC conf <*> genC (isTypeConf conf)

instance GenC Term where
  genC conf = case tcFixSize conf of
      Nothing -> sized $ \n -> genC $ fixSizeConf n conf
      Just n | n <= 0    -> genLeaf
	     | otherwise -> frequency
	[ (nameF, genName $ genArgs conf)
	, (litF,  Lit <$> genC conf)
	, (sortF, Sort <$> genC conf)
	, (lamF,  genLam)
	, (piF,	  genPi)
	]
    where
      defs    = tcDefinedNames conf
      cons    = tcConstructorNames conf
      vars    = tcFreeVariables conf
      freq f  = f $ tcFrequencies conf
      isType  = tcIsType conf
      useLits = map ($ tcLiterals conf) [ useLitInt, useLitFloat, useLitString, useLitChar ]

      varF  | null vars = 0
	    | otherwise = freq (varFreq . nameFreqs)
      defF  | null defs = 0
	    | otherwise = freq (defFreq . nameFreqs)
      conF  | null cons || isType = 0
	    | otherwise	       = freq (conFreq . nameFreqs)
      litF  | or useLits && not isType = freq (litFreq . termFreqs)
	    | otherwise		    = 0
      nameF | 0 == varF + defF + conF = 0
	    | otherwise		    = freq (nameFreq . termFreqs)
      lamF  | isType    = 0
	    | otherwise = freq (lamFreq  . termFreqs)
      sortF = freq (sortFreq . termFreqs)
      piF   = freq (piFreq   . termFreqs)

      genLam :: Gen Term
      genLam = Lam <$> genC conf <*> genC (isntTypeConf $ decrConf conf)

      genPi :: Gen Term
      genPi = uncurry Pi <$> genC conf

      genVar, genDef, genCon :: Gen Args -> Gen Term
      genVar args = Var <$> elements vars <*> args
      genDef args = Def <$> elements defs <*> args
      genCon args = Con <$> elements cons <*> args

      genName :: Gen Args -> Gen Term
      genName args = frequency
	[ (varF, genVar args)
	, (defF, genDef args)
	, (conF, genCon args)
	]

      genLeaf :: Gen Term
      genLeaf = frequency
	[ (nameF, genName $ return [])
	, (litF,  Lit  <$> genC conf)
	, (sortF, Sort <$> genC conf)
	]

-- | Only generates default configurations. Names and free variables varies.
genConf :: Gen TermConfiguration
genConf = do
  ds <- listOf $ elements defs
  cs <- listOf $ elements cons
  vs <- listOf natural
  return $ makeConfiguration ds cs vs
  where
    defs = [ [c] | c <- ['a'..'z'] ]
    cons = [ [c] | c <- ['A'..'Z'] ]

instance Arbitrary TermConfiguration where
  arbitrary   = genConf

-- Shrinking --------------------------------------------------------------

class ShrinkC a b | a -> b where
  shrinkC  :: TermConfiguration -> a -> [b]
  noShrink :: a -> b

instance ShrinkC a b => ShrinkC (YesType a) b where
  shrinkC conf (YesType x) = shrinkC (isTypeConf conf) x
  noShrink (YesType x) = noShrink x

instance ShrinkC a b => ShrinkC (NoType a) b where
  shrinkC conf (NoType x) = shrinkC (isntTypeConf conf) x
  noShrink (NoType x) = noShrink x

instance ShrinkC a b => ShrinkC [a] [b] where
  noShrink	  = map noShrink
  shrinkC conf xs = noShrink (removeChunks xs) ++ shrinkOne xs
   where
    -- Code stolen from Test.QuickCheck.Arbitrary
    removeChunks xs = rem (length xs) xs
     where
      rem 0 _  = []
      rem 1 _  = [[]]
      rem n xs = xs1
               : xs2
               : ( [ xs1' ++ xs2 | xs1' <- rem n1 xs1, not (null xs1') ]
             `ilv` [ xs1 ++ xs2' | xs2' <- rem n2 xs2, not (null xs2') ]
                 )
       where
        n1  = n `div` 2
        xs1 = take n1 xs
        n2  = n - n1
        xs2 = drop n1 xs

        []     `ilv` ys     = ys
        xs     `ilv` []     = xs
        (x:xs) `ilv` (y:ys) = x : y : (xs `ilv` ys)

    shrinkOne []     = []
    shrinkOne (x:xs) = [ x' : noShrink xs | x'  <- shrinkC conf x ]
                    ++ [ noShrink x : xs' | xs' <- shrinkOne xs ]

instance (ShrinkC a a', ShrinkC b b') => ShrinkC (a, b) (a', b') where
  noShrink (x, y) = (noShrink x, noShrink y)
  shrinkC conf (x, y) =
    [ (x', noShrink y) | x' <- shrinkC conf x ] ++
    [ (noShrink x, y') | y' <- shrinkC conf y ]

instance ShrinkC VarName Nat where
  shrinkC conf (VarName x) = [ y | y <- tcFreeVariables conf, y < x ]
  noShrink = unVarName

instance ShrinkC DefName QName where
  shrinkC conf (DefName c) = takeWhile (/= c) $ tcDefinedNames conf
  noShrink = unDefName

instance ShrinkC ConName QName where
  shrinkC conf (ConName c) = takeWhile (/= c) $ tcConstructorNames conf
  noShrink = unConName

instance ShrinkC Literal Literal where
  shrinkC _ (LitInt _ 0) = []
  shrinkC conf l	 = LitInt noRange 0 : case l of
      LitInt    r n -> LitInt    r <$> shrink n
      LitString r s -> LitString r <$> shrinkC conf s
      LitChar   r c -> LitChar   r <$> shrinkC conf c
      LitFloat  r x -> LitFloat  r <$> shrink x
      LitQName  r x -> []
  noShrink = id

instance ShrinkC Char Char where
  shrinkC _ 'a' = []
  shrinkC _ _	= ['a']
  noShrink = id

instance ShrinkC Hiding Hiding where
  shrinkC _ Hidden    = [NotHidden]
  shrinkC _ Instance  = [Instance]
  shrinkC _ NotHidden = []
  noShrink = id

instance ShrinkC a b => ShrinkC (Abs a) (Abs b) where
  shrinkC conf (NoAbs s x) = NoAbs s <$> shrinkC conf x
  shrinkC conf (Abs   s x) = Abs s <$> shrinkC (extendConf conf) x
  noShrink = fmap noShrink

instance ShrinkC a b => ShrinkC (Arg a) (Arg b) where
  shrinkC conf (Arg h r x) = (\ (h,x) -> Arg h r x) <$> shrinkC conf (h, x)
  noShrink = fmap noShrink

instance ShrinkC a b => ShrinkC (Dom a) (Dom b) where
  shrinkC conf (Dom h r x) = (\ (h,x) -> Dom h r x) <$> shrinkC conf (h, x)
  noShrink = fmap noShrink

instance ShrinkC a b => ShrinkC (Blocked a) (Blocked b) where
  shrinkC conf (Blocked m x)  = Blocked m <$> shrinkC conf x
  shrinkC conf (NotBlocked x) = NotBlocked <$> shrinkC conf x
  noShrink = fmap noShrink

-- Andreas 2010-09-21: simplify? since Sort Prop is no longer abused as DontCare
instance ShrinkC Sort Sort where
  shrinkC conf Prop = []
  shrinkC conf s = Prop : case s of
    Type n     -> [] -- No Level instance yet -- Type <$> shrinkC conf n
    Prop       -> __IMPOSSIBLE__
    Inf        -> []
    DLub s1 s2 -> __IMPOSSIBLE__
  noShrink = id

instance ShrinkC Telescope Telescope where
  shrinkC conf EmptyTel		 = []
  shrinkC conf (ExtendTel a tel) =
    killAbs tel : (uncurry ExtendTel <$> shrinkC conf (a, tel))
  noShrink = id

instance ShrinkC Type Type where
  shrinkC conf (El s t) = uncurry El <$> shrinkC conf (s, YesType t)
  noShrink = id

instance ShrinkC Term Term where
  shrinkC conf (DontCare _)  = []
  shrinkC conf (Sort Prop) = []
  shrinkC conf t	   = filter validType $ case ignoreSharing t of
    Var i args   -> map unArg args ++
		    (uncurry Var <$> shrinkC conf (VarName i, NoType args))
    Def d args   -> map unArg args ++
		    (uncurry Def <$> shrinkC conf (DefName d, NoType args))
    Con d args   -> map unArg args ++
		    (uncurry Con <$> shrinkC conf (ConName d, NoType args))
    Lit l	 -> Lit <$> shrinkC conf l
    Level l      -> [] -- TODO
    Lam h b      -> killAbs b : (uncurry Lam <$> shrinkC conf (h, b))
    Pi a b       -> unEl (unDom a) : unEl (killAbs b) :
		    (uncurry Pi <$> shrinkC conf (a, b))
    Sort s       -> Sort <$> shrinkC conf s
    MetaV m args -> map unArg args ++ (MetaV m <$> shrinkC conf (NoType args))
    DontCare _   -> []
    Shared{}     -> __IMPOSSIBLE__
    where
      validType t
	| not (tcIsType conf) = True
	| otherwise	    = case t of
	    Con _ _ -> False
	    Lam _ _ -> False
	    Lit _	  -> False
	    _	  -> True
  noShrink = id

killAbs :: KillVar a => Abs a -> a
killAbs (Abs   _ x) = killVar 0 x
killAbs (NoAbs _ x) = x

class KillVar a where
  killVar :: Nat -> a -> a

instance KillVar Term where
  killVar i t = case ignoreSharing t of
    Var j args | j == i	   -> DontCare (Var j [])
	       | j >  i	   -> Var (j - 1) $ killVar i args
	       | otherwise -> Var j	  $ killVar i args
    Def c args		   -> Def c	  $ killVar i args
    Con c args		   -> Con c	  $ killVar i args
    Lit l		   -> Lit l
    Level l                -> Level l -- TODO
    Sort s		   -> Sort s
    Lam h b		   -> Lam h	  $ killVar i b
    Pi a b		   -> uncurry Pi  $ killVar i (a, b)
    MetaV m args	   -> MetaV m	  $ killVar i args
    DontCare mv            -> DontCare    $ killVar i mv
    Shared{}               -> __IMPOSSIBLE__

instance KillVar Type where
  killVar i (El s t) = El s $ killVar i t

instance KillVar Telescope where
  killVar i EmptyTel	      = EmptyTel
  killVar i (ExtendTel a tel) = uncurry ExtendTel $ killVar i (a, tel)

instance KillVar a => KillVar (Arg a) where
  killVar i = fmap (killVar i)

instance KillVar a => KillVar (Dom a) where
  killVar i = fmap (killVar i)

instance KillVar a => KillVar (Abs a) where
  killVar i = fmap (killVar (i + 1))

instance KillVar a => KillVar [a] where
  killVar i = map (killVar i)

instance KillVar a => KillVar (Maybe a) where
  killVar i = fmap (killVar i)

instance (KillVar a, KillVar b) => KillVar (a, b) where
  killVar i (x, y) = (killVar i x, killVar i y)

-- Tests ------------------------------------------------------------------

isWellScoped :: Free a => TermConfiguration -> a -> Bool
isWellScoped conf t = allVars (freeVars t) `Set.isSubsetOf` Set.fromList (tcFreeVariables conf)

-- | Check that the generated terms don't have any out of scope variables.
prop_wellScopedVars :: TermConfiguration -> Property
prop_wellScopedVars conf =
  forAllShrink (genC conf) (shrinkC conf) $ \t ->
  isWellScoped conf (t :: Term)
