{-# OPTIONS -fwarn-unused-imports #-}

module Type.Scheme
	( extractType
	, generaliseType 
	, watchClass)
where

import Type.Exp
import Type.Pretty
import Type.Plate
import Type.Util
import Type.Trace
import Type.Error

import Type.Check.GraphicalData
import Type.Check.Soundness

import Type.Effect.MaskLocal
-- import Type.Effect.MaskFresh	(maskEsFreshT)
-- import Type.Effect.MaskPure	(maskEsPureT)

import Type.State
import Type.Class
import Type.Plug		
import Type.Port
import Type.Context
import Type.Util

import Shared.Error
import qualified Shared.Var	as Var
import qualified Shared.VarUtil	as Var
import Shared.Var		(NameSpace(..))
import Shared.VarPrim

import qualified Main.Arg	as Arg

import qualified Data.Map	as Map
import qualified Data.Set	as Set
import Data.Set			(Set)

import Util



-----
stage	= "Type.Scheme"
debug	= True
trace s	= when debug $ traceM s

watchClass src code
 = do	mC	<- lookupClass (ClassId code)
 
 	let res
		| Just Class { classQueue = queue }	<- mC
		= trace ( "--- class " % code % "----------------------\n"
			% "-- src   = " % src		% "\n"
			% "-- queue = " % queue		% "\n")

		| otherwise
		= return ()
	res


-- | Extract a type from the graph and pack it into standard form.
--	BUGS: we need to add fetters from higher up which are acting on this type.

extractType 
	:: Bool 		-- whether to treat effect and closure vars that were never quantified as TBot
	-> Var 			-- var of type to extract
	-> SquidM (Maybe Type)

extractType final varT
 = do	mCid	<- lookupVarToClassId varT

	case mCid of
	 Nothing	
	  -> do	graph			<- gets stateGraph
	  	let varToClassId	=  graphVarToClassId graph
	  	freakout stage
		 	("extractType: no classId defined for variable " % (varT, Var.bind varT)		% "\n"
			% " visible vars = " % (map (\v -> (v, Var.bind v)) $ Map.keys varToClassId)		% "\n")
			$ return Nothing

	 Just cid	-> extractTypeC final varT cid
	 
extractTypeC final varT cid
 = do 	trace	$ "*** Scheme.extractType " % varT % "\n"
		% "\n"
	
 	tTrace		<- liftM sortFsT 	$ traceType cid
	trace	$ "    tTrace           =\n" %> prettyTS tTrace	% "\n\n"

	-- Check if the data portion of the type is graphical.
	--	If it is then it'll hang packType when it tries to construct an infinite type.
	let cidsDataLoop	= checkGraphicalDataT tTrace
	trace	$ "    cidsDataLoop     = " % cidsDataLoop % "\n\n"

	
	if (isNil cidsDataLoop)
	 -- no graphical data, ok to continue.
	 then extractTypeC1 final varT cid tTrace

	 -- we've got graphical data, add an error to the solver state and bail out.
	 else do
	 	addErrors [ErrorInfiniteTypeClassId {
	 			eClassId	= head cidsDataLoop }]

		return $ Just $ TError KData [tTrace]

extractTypeC1 final varT cid tTrace
 = do	
	-- Cut loops through the effect and closure portions of this type
	let tCutLoops	= cutLoopsT tTrace
	trace	$ "    tCutLoops        =\n" %> prettyTS tCutLoops % "\n\n"

	-- Pack type into standard form
	let tPack	= packType tCutLoops
	trace	$ "    tPack            =\n" %> prettyTS tPack % "\n\n"

	-- Trim closures
	let tTrim	= 
		case kindOfType tPack of
			KClosure	-> packType $ trimClosureC tPack
			_		-> packType $ trimClosureT tPack

	trace	$ "    tTrim            =\n" %> prettyTS tTrim % "\n\n"

	extractType_final final varT cid tTrim
	

extractType_final True varT cid tTrim
 = do	
 	-- plug classIds with vars
 	tPlug		<- plugClassIds [] tTrim
 
	-- close off never-quantified effect and closure vars
 	quantVars	<- gets stateQuantifiedVars
 	let tFinal	=  finaliseT quantVars tPlug
	
	trace	$ "    tFinal          =\n" %> prettyTS tFinal	% "\n\n"
	extractTypeC2 varT cid tFinal
	
extractType_final False varT cid tTrim
	= extractTypeC2 varT cid tTrim

extractTypeC2 varT cid tFinal
 = do	
	-- Reduce context
	classInst	<- gets stateClassInst
	let tReduced	= reduceContextT classInst tFinal
	trace	$ "    tReduced         =\n" %> prettyTS tReduced % "\n\n"

	return	$ Just tReduced
	


-- | Generalise a type
--
generaliseType
	:: Var 
	-> Type
	-> [ClassId]
	-> SquidM Type

generaliseType varT tCore envCids
 = do
	args			<- gets stateArgs
	trace	$ "*** Scheme.generaliseType " % varT % "\n"
		% "\n"
		% "    tCore\n"
		%> prettyTS tCore	% "\n\n"

		% "    envCids          = " % envCids		% "\n"
		% "\n"

	-- work out what effect and closure vars are in contra-variant branches
	let contraTs	= catMaybes
			$ map (\t -> case t of
					TClass KEffect cid	-> Just t
					TClass KClosure cid	-> Just t
					_			-> Nothing)
			$ slurpContraClassVarsT tCore
	
	let tMore	= moreifyFettersT (Set.fromList contraTs) tCore
	
	trace	$ "    contraTs = " % contraTs	% "\n"

	trace	$ "    tMore\n"
		%> prettyTS tMore	% "\n\n"


	-- flatten out the scheme so its easier for staticRs.. to deal with
	let tFlat	= flattenT tMore
	trace	$ "    tFlat\n"
		%> prettyTS tFlat	% "\n\n"

	-- Work out which cids can't be generalised in this type.

	-- 	Can't generalise regions in non-functions.
	--	... some data object is in the same region every time you use it.
	--
	let staticRsData 	= Set.toList $ staticRsDataT	tFlat
	let staticRsClosure 	= Set.toList $ staticRsClosureT	tFlat

	trace	$ "    staticRsData     = " % staticRsData	% "\n"
		% "    staticRsClosure  = " % staticRsClosure	% "\n"


	--	Can't generalise cids which are under mutable constructors.
	--	... if we generalise these classes then we could update an object at one 
	--		type and read it at another, violating soundness.
	--	
	let staticDanger	= if Set.member Arg.GenDangerousVars args
					then []
					else dangerousCidsT tMore

	trace	$ "    staticDanger     = " % staticDanger	% "\n"

	-- These are all the cids we can't generalise
	let staticCids		= envCids ++ staticRsData ++ staticRsClosure ++ staticDanger

	-- Rewrite non-static cids to the var for their equivalence class.
	tPlug			<- plugClassIds staticCids tMore

	trace	$ "    staticCids       = " % staticCids	% "\n\n"
		% "    tPlug\n"
		%> prettyTS tPlug 	% "\n\n"

	-- Clean empty effect classes that aren't ports.
	-- 	BUGS: don't clean variables in the type environment.
	--	TODO we have to do a reduceContext again to pick up (Pure TBot) 
	--	.. the TBot won't show up until we do the cleaning. Won't need this 
	--	once we can discharge these during the grind. It's duplicated in extractType above
	classInst	<- gets stateClassInst

	let tClean	= reduceContextT classInst 
			$ cleanType Set.empty tPlug

	trace	$ "    tClean\n" 
			%> ("= " % prettyTS tClean)		% "\n\n"

	-- Check context for problems.
	checkContext tClean

	-- Mask effects and CMDL constraints on local regions
	-- 	Do this before adding foralls so we don't end up with quantified regions which
	--	aren't present in the type scheme.
	--
	let tMskLocal	= maskLocalT tClean


	trace	$ "    tMskLocal\n"
		%> prettyTS tMskLocal 	% "\n\n"


	-- Quantify free variables.
	let vksFree	= map 	 (\v -> (v, kindOfSpace $ Var.nameSpace v)) 
			$ filter (\v -> not $ Var.nameSpace v == NameValue)
			$ filter (\v -> not $ Var.isCtorName v)
			$ Var.sortForallVars
			$ Set.toList $ freeVars tMskLocal

	-- Remember which vars are quantified
	--	we can use this information later to clean out non-port effect and closure vars
	--	once the solver is done.
	modify $ \s -> s { stateQuantifiedVars	
				= Set.unions
					[ Set.fromList $ map fst vksFree
					, stateQuantifiedVars s ] }
	
	trace	$ "    vksFree   = " % vksFree	% "\n\n"

	let tScheme	= makeTForall vksFree tMskLocal
	
	
	trace	$ "    tScheme\n"
		%> prettyTS tScheme 	% "\n\n"

	return	tScheme


{-
	-- Mask effects on local and fresh regions
	let tMskFresh		= maskEsFreshT tMskLocal
	let tMskPure		= maskEsPureT  tMskFresh

	let tPack		= packType tMskPure


	-- Check the scheme against any available type signature.
	schemeSig	<- gets stateSchemeSig
	let mSig	= (Map.lookup varT schemeSig) :: Maybe Type
	let errsSig	= case mSig of
				Nothing		-> []
				Just sig	-> checkSig varT sig varT tPack
	addErrors errsSig
-}


-- | Empty effect and closure eq-classes which do not appear in the environment or 
--	a contra-variant position in the type can never be anything but _|_,
--	so we can safely erase them now.
--
--   TODO:
--	We need to run the cleaner twice to handle types like this:
--		a -(!e1)> b
--		:- !e1 = !{ !e2 .. !en }
--
--	where all of !e1 .. !en are cleanable.
--	Are two passes enough?
--	
cleanType :: Set Var -> Type -> Type
cleanType save tt
	= cleanType' save $ cleanType' save  tt

cleanType' save tt
 = let	vsFree	= Set.toList $ freeVars tt

	vsPorts	
		= catMaybes
		$ map (\t -> case t of 
			TVar k v	-> Just v
			_		-> Nothing)
		$ portTypesT tt

	vsClean	= [ v 	| v <- vsFree
			, elem (Var.nameSpace v) [Var.NameEffect, Var.NameClosure]
			, not $ Var.isCtorName v 
			, not $ elem v vsPorts 
			, not $ Set.member v save]

	sub	= Map.fromList
		$ map (\v -> (v, TBot (kindOfSpace $ Var.nameSpace v)))
		$ vsClean 

	tClean	= packType 
		$ substituteVT sub tt
	
   in	tClean



-- | After reducing the context of a type to be generalised, if certain constraints
--	remain then this is symptomatic of problems in the source program.
-- 
--	Projection constraints indicate an ambiguous projection.
--	Shape constraints indicate 
--	Type class constraints indicate that no instance for this type is available.
--
checkContext :: Type -> SquidM ()
checkContext tt
 = case tt of
 	TFetters fs t	-> mapM_ checkContextF fs
	_		-> return ()
 
checkContextF ff
 = case ff of
 	FProj j vInst tDict tBind
	 -> addErrors
	 	[ ErrorAmbiguousProjection
			{ eProj		= j } ]

	FConstraint vClass ts
	 | not 	$ elem vClass
	 	[ primMutable,	primMutableT
		, primConst,	primConstT
		, primLazy, 	primDirect
		, primPure,	primLazyH ]
	 -> addErrors
	 	[ ErrorNoInstance
			{ eClassVar		= vClass
			, eTypeArgs		= ts } ]
		
	_ -> return ()
	
