{-# LANGUAGE GADTs, TemplateHaskell #-}

module UHC.Light.Compiler.EHC.BuildFunction
( module Data.Functor.Identity
, BFun' (..)
, BFun (..)
, BFunCacheEntry (..)
, BCache, emptyBCache, bcacheCache, bcacheDpdRel
, bcacheLookup, bcacheInsert, bcacheInsertDpd
, BRef (..)
, BState, emptyBState, bstateCache, bstateCallStack )
where
import UHC.Light.Compiler.EHC.Common
import UHC.Light.Compiler.EHC.CompileUnit
import Control.Applicative
import Data.Functor.Identity
import qualified Data.Map as Map
import qualified Data.IntMap as IMap
import Data.Maybe
import UHC.Util.Hashable
import UHC.Util.Lens
import qualified UHC.Util.RelMap as Rel
import Data.Typeable
import UHC.Util.Time


{-# LINE 50 "src/ehc/EHC/BuildFunction.chs" #-}
deriving instance Typeable Identity

{-# LINE 58 "src/ehc/EHC/BuildFunction.chs" #-}
-- | Representation of build functions (embedded comment screws up haddock, hence see source code directly).
-- Regretfully deriving Generic (and thus Hashable) does not work for GADTs, so must be done manually, below.
-- Ord cannot be derived either.
-- First order type, no fields with recursive type are allowed to allow for more easily implementable comparison etc.
data BFun' res where
  --- | Obtain FPath and module name of a file name
  FPathSearchForFile
    :: !String				--- ^ suffix, if absent in name
    -> !FilePath			--- ^ file name
    -> BFun' (HsName, FPath)

  --- | Obtain FPath of an (imported module)
  FPathOfImported
    :: !HsName				--- ^ module name
    -> BFun' FPath

  --- | Extract imported modules from a module
  ImportsOf
    :: !HsName				--- ^ module name
    -> BFun' [HsName]

  --- | Extract compileunit from a module
  EcuOfName
    :: !HsName				--- ^ module name
    -> BFun' EHCompileUnit

  EcuOfNameAndPath
    :: !(Maybe PrevSearchInfo)		--- ^ possibly previous search info
    -> !(HsName,Maybe FPath)		--- ^ module name and possibly known path
    -> BFun' EHCompileUnit

  --- | Extract global options, possibly overridden for a module
  EHCOptsOf
    :: !HsName				--- ^ module name
    -> BFun' EHCOpts

  --- | Get a particular AST from file for a module
  ASTFromFile
    :: !(HsName,Maybe FPath)			--- ^ module name and possibly known path
    -> !ASTType							--- ^ content type
    -> !ASTSuffixKey					--- ^ suffix and content variation
    -> !ASTFileTiming					--- ^ timing (i.e. previous or current)
    -> BFun' res

  --- | Get the modification ClockTime of a file for a module
  ModfTimeOfFile
    :: !HsName							--- ^ module name and possibly known path
    -> !ASTType							--- ^ content type
    -> !ASTSuffixKey					--- ^ suffix and content variation
    -> !ASTFileTiming					--- ^ timing (i.e. previous or current)
    -> BFun' (Maybe ClockTime)

-- | Comparison which ignores GADT type info
bfunCompare :: BFun' res1 -> BFun' res2 -> Ordering
bfunCompare f1 f2 = case (f1,f2) of
    (FPathSearchForFile 	a1 b1		, FPathSearchForFile 	a2 b2		) -> lexico [a1 `compare` a2, b1 `compare` b2]
    (FPathOfImported    	a1   		, FPathOfImported    	a2   		) ->         a1 `compare` a2
    (ImportsOf          	a1   		, ImportsOf          	a2   		) ->         a1 `compare` a2
    (EcuOfName              a1   		, EcuOfName    			a2   		) ->         a1 `compare` a2
    (EcuOfNameAndPath		a1 b1		, EcuOfNameAndPath		a2 b2		) -> lexico [a1 `compare` a2, b1 `compare` b2]
    (EHCOptsOf             	a1   		, EHCOptsOf				a2   		) ->         a1 `compare` a2
    (ASTFromFile            a1 b1 c1 d1	, ASTFromFile			a2 b2 c2 d2	) -> lexico [a1 `compare` a2, b1 `compare` b2, c1 `compare` c2, d1 `compare` d2]
    (ModfTimeOfFile         a1 b1 c1 d1	, ModfTimeOfFile		a2 b2 c2 d2	) -> lexico [a1 `compare` a2, b1 `compare` b2, c1 `compare` c2, d1 `compare` d2]
  where lexico (x:xs)
          | x == EQ   = lexico xs
          | otherwise = x
        lexico []     = EQ

instance Ord (BFun' res) where
  compare = bfunCompare

deriving instance Eq (BFun' res)
deriving instance Show (BFun' res)
deriving instance Typeable BFun'

instance Hashable (BFun' res) where
  hashWithSalt salt x = case x of
	FPathSearchForFile 	a b		-> salt `hashWithSalt` (0::Int) `hashWithSalt` a `hashWithSalt` b
	FPathOfImported	   	a		-> salt `hashWithSalt` (1::Int) `hashWithSalt` a
	ImportsOf		   	a		-> salt `hashWithSalt` (2::Int) `hashWithSalt` a
	EcuOfName		   	a		-> salt `hashWithSalt` (3::Int) `hashWithSalt` a
	EHCOptsOf		   	a		-> salt `hashWithSalt` (4::Int) `hashWithSalt` a
	EcuOfNameAndPath	a b		-> salt `hashWithSalt` (5::Int) `hashWithSalt` a `hashWithSalt` b
	ASTFromFile			a b	c d	-> salt `hashWithSalt` (6::Int) `hashWithSalt` a `hashWithSalt` b `hashWithSalt` c `hashWithSalt` d
	ModfTimeOfFile		a b	c d	-> salt `hashWithSalt` (7::Int) `hashWithSalt` a `hashWithSalt` b `hashWithSalt` c `hashWithSalt` d


{-# LINE 167 "src/ehc/EHC/BuildFunction.chs" #-}
-- | BFun' used as a dependency of another BFun', for now same as a Dynamic
data BFun
  = forall res
    . ({- Typeable (BFun' res), -} Typeable res)
      => BFun
           { bfcdFun 		:: !(BFun' res)
           }

instance Eq BFun where
  (BFun {bfcdFun=f1}) == (BFun {bfcdFun=f2}) = bfunCompare f1 f2 == EQ

instance Ord BFun where
  (BFun {bfcdFun=f1}) `compare` (BFun {bfcdFun=f2}) = bfunCompare f1 f2

instance Hashable BFun where
  hashWithSalt salt (BFun {bfcdFun=x}) = hashWithSalt salt x

{-# LINE 186 "src/ehc/EHC/BuildFunction.chs" #-}
-- | BFun' + BCachedVal' packaged with required class instances, similar to a Dynamic
data BFunCacheEntry
  = forall f res
    . (Typeable f, Typeable res)
      => BFunCacheEntry
           { bfceFun 		:: !(BFun' res)
           , bfceVal		:: !(f res)
           }

{-# LINE 201 "src/ehc/EHC/BuildFunction.chs" #-}
-- | Cache for function calls, first indexed on hash
data BCache
  = BCache
      { _bcacheCache	:: IMap.IntMap [BFunCacheEntry]
      , _bcacheDpdRel	:: Rel.Rel BFun BFun
      }

mkLabel ''BCache

emptyBCache :: BCache
emptyBCache = BCache IMap.empty Rel.empty

{-# LINE 215 "src/ehc/EHC/BuildFunction.chs" #-}
-- | Lookup BCachedVal in 'BCache', preserving type info
bcacheLookup :: (Typeable res, Typeable f) => BFun' res -> BCache -> Maybe (f res)
bcacheLookup key (BCache {_bcacheCache=cache}) = do
    vals <- IMap.lookup (hash key) cache
    lookup key $ catMaybes $ map cvt vals
  where
    cvt (BFunCacheEntry {bfceFun=f, bfceVal=v}) = case (cast f, cast v) of
      (Just f', Just v') -> Just (f',v')
      _                  -> Nothing

-- | Add to 'BCache'
bcacheInsert :: (Typeable res, Typeable f) => BFun' res -> f res -> BCache -> BCache
bcacheInsert k v bc@(BCache {_bcacheCache=c}) = bc { _bcacheCache = IMap.insertWith (++) (hash k) [BFunCacheEntry k v] c }

-- | Add dependency to 'BCache'
bcacheInsertDpd
  :: (Typeable res1, Typeable res2)
     =>
        BFun' res1			-- ^ dependee
     -> BFun' res2			-- ^ depends on
     -> BCache -> BCache
bcacheInsertDpd f1 f2 bc@(BCache {_bcacheDpdRel=dpd}) = bc { _bcacheDpdRel = Rel.insert (BFun f1) (BFun f2) dpd }

{-# LINE 244 "src/ehc/EHC/BuildFunction.chs" #-}
-- | GADT for references to global state, interpreted inside the compiler driver monad, the type of the GADT telling what the type of the value should be.
data BRef val where
  --- | Compile unit
  BRef_ECU
    :: !HsName					--- ^ module name
    -> BRef EHCompileUnit

  --- | An AST embedded in a compile unit
  BRef_AST
    :: !HsName					--- ^ module name
    -> ASTType							--- ^ content type
    -> ASTSuffixKey						--- ^ suffix and content variation
    -> ASTFileTiming			--- ^ timing (i.e. previous or current)
    -> BRef val

  --- | Global options
  BRef_EHCOpts
    :: !HsName					--- ^ module name
    -> BRef EHCOpts

deriving instance Typeable BRef

{-# LINE 272 "src/ehc/EHC/BuildFunction.chs" #-}
-- | Cache for function calls, first indexed on hash
data BState
  = BState
      { _bstateCache		:: !BCache
      , _bstateCallStack	:: ![BFun]
      }

mkLabel ''BState

emptyBState :: BState
emptyBState = BState emptyBCache []