
module Yhc.Core.Firstify.Mitchell.Template where

import Control.Monad.State
import Data.List
import Data.Maybe
import Debug.Trace
import Yhc.Core hiding (uniqueBoundVarsCore, uniqueBoundVars)
import Yhc.Core.FreeVar3
import Yhc.Core.UniqueId
import Yhc.Core.Util


-- all templates must be at least: CoreApp (CoreFun _) _
type Template = CoreExpr

templateNone :: Template
templateNone = CoreVar "_"


-- given an expression, what would be the matching template
-- must be careful to avoid if there is an inner template not redoing it
templateCreate :: (CoreFuncName -> Bool) -> (CoreFuncName -> Bool) -> CoreExpr -> Template
templateCreate isPrim isHO o@(CoreApp (CoreFun x) xs)
        | any ((/=) templateNone . templateCheck isHO) $ tail $ universe o = templateNone
        | isPrim x && res /= templateNone = trace ("Warning: primitive HO call, " ++ x) templateNone
        | otherwise = res
    where
        res = templateNorm $ templateCheck isHO o

templateCreate _ _ _ = templateNone


templateNorm :: Template -> Template
templateNorm = flip evalState (1 :: Int) . uniqueBoundVars


templateCheck :: (CoreFuncName -> Bool) -> CoreExpr -> Template
templateCheck isHO o@(CoreApp (CoreFun x) xs) = join (CoreApp (CoreFun x)) (map f xs)
    where
        free = collectFreeVars o
        f (CoreLam vs x) = CoreLam vs (f x)
        f (CoreFun x) | isHO x = CoreFun x
        f (CoreApp (CoreFun x) xs) | isHO x = CoreApp (CoreFun x) (map f xs)
        f (CoreVar x) | x `notElem` free = CoreVar x
        f (CoreApp x xs) | isCoreCon x || isCoreFun x = join (CoreApp x) (map f xs)
        f x = join generate (map f children)
            where (children,generate) = uniplate x

        join g xs | any (/= templateNone) xs = g xs
                  | otherwise = templateNone

templateCheck _ _ = templateNone



-- pick a human readable name for a template result
templateName :: Template -> String
templateName (CoreApp (CoreFun name) xs) = concat $ intersperse "_" $ map short $ name :
    [x | CoreFun x <- map (fst . fromCoreApp . snd . fromCoreLam) xs, '_' `notElem` x]
    where short = reverse . takeWhile (/= ';') . reverse
templateName _ = "template"


-- for each CoreVar "_", get the associated expression
templateHoles :: CoreExpr -> Template -> [CoreExpr]
templateHoles x y | y == templateNone = [x]
                  | otherwise = concat $ zipWith templateHoles (children x) (children y)


templateExpand :: (CoreFuncName -> Maybe Template) -> Template -> Template
templateExpand mp = transform f
    where
        f (CoreFun x) = case mp x of
                            Just y -> transform f y
                            Nothing -> CoreFun x
        f x = x


templateGenerate :: UniqueIdM m => (CoreFuncName -> CoreFunc) -> CoreFuncName -> Template -> m CoreFunc
templateGenerate ask newname o@(CoreApp (CoreFun name) xs) = do
    let fun = ask name
        CoreFunc _ args body | isCoreFunc fun = fun
            | otherwise = error $ "Tried specialising on a primitve: " ++ show o
    x <- duplicateExpr $ coreLam args body
    xs <- mapM duplicateExpr xs
    count1 <- getIdM
    xs <- mapM (transformM f) xs
    count2 <- getIdM
    putIdM count1
    vs <- getVars (count2-count1)
    return $ CoreFunc newname vs (coreApp x xs)
    where
        f x | x == templateNone = liftM CoreVar getVar
        f x = return x


-- given an expand function, and an existing template, and a new template
-- return a new template, based on the original, but only if there is an embedding
--
-- cannot weaken the head of an application without blurring the entire app
-- must remove a chunk which is variable consistent
-- remove lambdas if you can
templateWeaken :: (Template -> Template) -> Template -> Template -> Template
templateWeaken expand bad new =
    case f new of
        Just (CoreApp x xs) | all (== templateNone) xs -> new
        Just x -> x
        Nothing -> new
    where
        res = f new
        bad2 = blurVar bad
        free = collectFreeVars new
        safe x = null (collectFreeVars x \\ free)

        -- return Nothing to indicate remove but not safe
        f x | die x || any isNothing cs2 = if safe x then Just templateNone else Nothing
            | otherwise = Just $ gen $ map fromJust cs2
            where
                (cs,gen) = uniplate x
                cs2 = map f cs

        -- do you want to remove this subexpression
        die (CoreLam _ x) | die x = True
        die (CoreApp x xs) | die x = True
        die x = blurVar (expand x) == bad2
