-- -- (c) Susumu Katayama -- \begin{code}
{-# LANGUAGE CPP, RelaxedPolyRec, FlexibleInstances #-}
module MagicHaskeller.ProgGenSFIORef(ProgGenSFIORef, PGSFIOR) where
import MagicHaskeller.Types
import MagicHaskeller.TyConLib
import Control.Monad
import MagicHaskeller.CoreLang
import Control.Monad.Search.Combinatorial
import MagicHaskeller.PriorSubsts
import Data.List(partition, sortBy, sort, nub, (\\))
import Data.Ix(inRange)

import MagicHaskeller.ClassifyDM

import MagicHaskeller.Instantiate

import MagicHaskeller.ProgramGenerator
import MagicHaskeller.ProgGen(mkCL, ClassLib(..), mguPrograms)
import MagicHaskeller.ProgGenSF(funApSub_, funApSub_spec, lookupNormalized, tokoro10fst)
import MagicHaskeller.Options(Opt(..))

import MagicHaskeller.Expression

import MagicHaskeller.MemoToFiles hiding (freezePS, fps, memoRTIO)

import MagicHaskeller.T10(mergesortWithBy, diffSortedBy)

import qualified Data.Map as M

import MagicHaskeller.DebMT

import MagicHaskeller.FMType
import Data.IORef
import Data.Array
import Data.Ix

import MagicHaskeller.ShortString(readBriefly, showBriefly)
import System.Directory(doesFileExist, createDirectoryIfMissing)

import Debug.Trace
-- trace str = id

reorganize_ = reorganizer_
-- reorganize_ = id

reorganizerId' :: (Functor m, Expression e) => ([Type] -> m e) -> [Type] -> m e
reorganizerId' = reorganizeId'
--reorganizerId' = id

classify = True
traceExpTy _ = id
-- traceExpTy fty = trace ("lookupexp "++ show fty)
traceTy _    = id
-- traceTy fty = trace ("lookup "++ show fty)

-- Memoization table, created from primitive components
type ProgGenSFIORef = PGSFIOR CoreExpr
data PGSFIOR e = PGSFIOR (ExpTrie e) (MemoDeb e) -- internal data representation.
-- ^ Program generator with synergetic filtration.
--   This program generator employs filtration by random testing, and rarely generate semantically equivalent expressions more than once, while different expressions will eventually appear (for most of the types, represented with Prelude types, whose arguments are instance of Arbitrary and which return instance of Ord).
--   The idea is to apply random numbers to the generated expressions, compute the quotient set of the resulting values at each depth of the search tree, and adopt the complete system of representatives for the depth and push the remaining expressions to one step deeper in the search tree.
--   (Thus, adoption of expressions that may be equivalent to another already-generated-expression will be postponed until their \"uniqueness\" is proved.)
--   As a result, (unlike "ProgGen",) expressions with size N may not appear at depth N but some deeper place.
--
--   "ProgGenSF" is more efficient along with a middle-sized primitive set (like @reallyall@ found in LibTH.hs),
--   but is slower than "ProgGen" for a small-sized one.
--
--   Also note that "ProgGenSF" depends on hard use of unsafe* stuff, so if there is a bug, it may segfault....

type ExpTrie  e = IORef (FMType (Array Int [e]))
-- type ExpTrie  e = MapType (ExpMatrix e) -- PGSF, PGSFIOだとこっちだった.

type TypeTrie = MapType (Matrix (Type, Subst, TyVar))

filtBFm cmn ty | classify  = inconsistentDBTToRcT . fmap fromAnnExpr . filterDM cmn ty . fmap (toAnnExprWind (execute (opt cmn) (vl cmn)) ty)  .  mapDepthDB uniqSorter
               | otherwise = dbtToRcT . mapDepthDB uniqSorter

lmtty mt fty = traceTy fty $
               lookupMT mt fty

--memocond i = 3<i
-- memocond i = i<10
memocond i = True

-- memocond ty = size ty < 10 -- popArgs avail tしてからやるのはちょっと無駄.と思ったけど,実際は関係ないみたい.
-- memocond av ty = size ty + sum (map size av) < 10


instance ProgramGeneratorIO (PGSFIOR CoreExpr) where
    mkTrieOptIO cmn classes tcesopt tces = do expTrie <- newIORef EmptyFMT
                                              return $ PGSFIOR expTrie (mkTrieOptSF cmn classes tcesopt tces)
    unifyingProgramsIO ty px = catBags $ fmap (\ (es,_,_) -> map (toAnnExpr $ reducer $ extractCommon px) es) $ dbtToRcT $ unifyingPossibilitiesIO ty px
instance Expression e => WithCommon (PGSFIOR e) where
    extractCommon    (PGSFIOR _ (_,_,_,cmn))      = cmn

unifyingPossibilitiesIO ty pg = unPS (unifyableExprsIO pg [] ty) emptySubst 0


type MemoDeb e = (ClassLib e, TypeTrie, (([[Prim]],[[Prim]]),([[Prim]],[[Prim]])), Common)

mkTrieOptSF :: Common -> [Typed [CoreExpr]] -> [[Typed [CoreExpr]]] -> [[Typed [CoreExpr]]] -> MemoDeb CoreExpr
mkTrieOptSF cmn classes txsopt txs
    = let memoDeb = (mkCL cmn classes, typeTrie, (qtlopt,qtl), cmn)
          typeTrie = mkMTty (tcl cmn) (\ty -> freezePS ty (specTypes memoDeb ty))
      in memoDeb
    where qtlopt = splitPrimss txsopt
          qtl    = splitPrimss txs
-- 非効率だけど,DBoundTに矛盾があってもいいやつ.
inconsistentDBTToRcT :: (Functor m, Monad m, Ord a) => DBoundT m a -> RecompT m a
inconsistentDBTToRcT (DBT f) = RcT $ \d -> do tss <- mapM f [0..d-1]
                                              ts  <- f d
                                              return $ foldl (diffSortedBy compare) (map fst ts) (map (map fst) tss)
-- what to memoize about exptrie
computeExpTip :: PGSFIOR CoreExpr -> Type -> RecompT IO CoreExpr
computeExpTip md@(PGSFIOR _ (_,_,_,cmn)) ty = filtBFm cmn ty $ matchFunctionsIO md ty

mkMTty = mkMT
mkMTexp = mkMT

mondepth = zipDepthRc (\d xs -> trace ("depth="++show d++", and the length is "++show (length xs)) xs) -- depthと表示するなら+1するべきであった.(0から始まるので)


type BFT = Recomp
unBFM = unMx


freezePS :: Type -> PriorSubsts Recomp Type -> Matrix (Type,Subst,TyVar)
freezePS ty ps
    = let mxty = maxVarID ty
      in mapDepth tokoro10ap $ toMx $ fmap fst $ Rc $ unDB $ fromRc $ unPS ps emptySubst (mxty+1) 

tokoro10ap :: [(Type,s,i)] -> [(Type,s,i)]
tokoro10ap = M.elems . M.fromListWith const . map (\ t@(ty,_,_) -> (ty, t))




specializedTypes :: (Search m) => MemoDeb CoreExpr -> [Type] -> Type -> PriorSubsts m ([Type],Type)
specializedTypes memodeb avail t = do specializedCases memodeb avail t
                                      subst <- getSubst
                                      return (map (apply subst) avail, apply subst t)
-- specializedCases is the same as unifyableExprs, except that the latter returns PriorSubsts BF [CoreExpr], and that the latter considers memodepth.
specializedCases, specCases, specCases' :: (Search m) => MemoDeb CoreExpr -> [Type] -> Type -> PriorSubsts m ()
specializedCases memodeb = applyDo (specCases memodeb)
specCases memodeb = wind_ (\avail reqret -> reorganize_ (\newavail -> uniExprs_ memodeb newavail reqret) avail)
{- どっちがわかりやすいかは不明
specCases memodeb avail (t0:->t1) = specCases memodeb (t0 : avail) t1
specCases memodeb avail reqret = reorganize_ (\newavail -> uniExprs_ memodeb newavail reqret) avail
-}





uniExprs_ :: (Search m) => MemoDeb CoreExpr -> [Type] -> Type -> PriorSubsts m ()
uniExprs_ memodeb avail t
    = convertPS fromRc $ psListToPSRecomp lfp
    where lfp depth
              | memocond depth     = lookupUniExprs memodeb avail t depth >> return ()
              | otherwise          = makeUniExprs memodeb avail t depth >> return ()

lookupUniExprs :: Expression e => MemoDeb e -> [Type] -> Type -> Int -> PriorSubsts [] Type
lookupUniExprs memodeb@(_,mt,_,_) avail t depth
    = lookupNormalized  (\tn -> unMx (lmtty mt tn) !! depth) avail t

makeUniExprs :: MemoDeb CoreExpr -> [Type] -> Type -> Int -> PriorSubsts [] Type
makeUniExprs memodeb avail t depth
    = convertPS tokoro10fst $
                do psRecompToPSList (reorganize_ (\av -> specCases' memodeb av t) avail) depth
                   sub   <- getSubst
                   return $ quantify (apply sub $ popArgs avail t)


-- entry point for memoization
specTypes :: (Search m) => MemoDeb CoreExpr -> Type -> PriorSubsts m Type
specTypes memodeb ty
                           = do let (avail,t) = splitArgs ty
                                reorganize_ (\av -> specCases' memodeb av t) avail
-- quantifyはmemo先で既にやられているので不要
                                typ <- applyPS ty
                                return (normalize typ)


-- specCases' trie prims@(primgen,primmono) avail reqret = msum (map (retMono.fromPrim) primmono) `mplus` msum (map retMono fromAvail ++ map retGen primgen)
specCases' memodeb@(CL classLib, ttrie, (prims@(primgen,primmono),_),cmn) avail reqret
 = mapSum retPrimMono primmono `mplus` msum (map retMono avail) `mplus` mapSum retGen primgen
    where fas | constrL $ opt cmn = funApSub_ clbehalf lltbehalf behalf
              | otherwise         = funApSub_spec      clbehalf behalf
              where behalf    = specializedCases memodeb avail
                    lltbehalf = flip mguAssumptions_ avail
                    clbehalf  ty = mguPrograms classLib [] ty >> return ()
          -- retPrimMono :: (Int, Type, Int, Typed [CoreExpr]) -> PriorSubsts BFT ()
          retPrimMono (_, arity, retty, numtvs, _xs:::ty)
                                              = napply arity delayPS $
                                                do tvid <- reserveTVars numtvs
                                                   mguPS reqret (mapTV (tvid+) retty)
                                                   fas (mapTV (tvid+) ty)
          -- retMono :: Type -> PriorSubsts BFT ()
          retMono ty = napply (getArity ty) delayPS $
                       do mguPS reqret (getRet ty)
                          fas ty
          -- retGen :: (Int, Type, Int, Typed [CoreExpr]) -> PriorSubsts BFT ()
          retGen (_, arity, _r, numtvs, _s:::ty) = napply arity delayPS $
                                             do tvid <- reserveTVars numtvs -- この(最初の)IDそのもの(つまり返り値のtvID)はすぐに使われなくなる
                                               -- let typ = apply (unitSubst tvid reqret) (mapTV (tvid+) ty) -- mapTVとapplyはhylo-fusionできるはずだが,勝手にされる?
                                               --                                                              -- unitSubstをinlineにしないと駄目か
                                                mkSubsts (tvndelay $ opt cmn) tvid reqret
                                                fas (mapTV (tvid+) ty)

                                                gentvar <- applyPS (TV tvid)

                                                guard (orderedAndUsedArgs gentvar)
                                                fas gentvar

type Generator m e = PGSFIOR e -> [Type] -> Type -> PriorSubsts m [e]

unifyableExprsIO :: Generator (DBoundT IO) CoreExpr
unifyableExprsIO memodeb = applyDo (wind (fmap (map (mapCE Lambda))) (lookupNormalized (lookupTypeTrieIO memodeb)))

memocondexp _ d = 0<d -- なんか条件がProgGenSFIOでは逆になってるかも.
memodepth _ = 1


lookupTypeTrieIO :: PGSFIOR CoreExpr -> Type -> DBoundT IO ([CoreExpr], Subst, TyVar)
lookupTypeTrieIO md@(PGSFIOR _ (_, mt, _, _)) t
    = DBT $ \db -> fmap concat $ mapM (procTup md db) $ unMx (lmtty mt t) !! db
procTup :: PGSFIOR CoreExpr -> Int -> (Type, Subst, TyVar) -> IO [(([CoreExpr],Subst,TyVar),Int)]
procTup md db (ty, s, i) = mapM (\depth -> unRcT (lookupReorganizedIO md ty) depth >>= \ys -> return ((ys, s, i), db-depth)) [0..db]
{-
lookupTypeTrieRcIO :: Expression e => MemoDeb e -> Type -> RecompT IO ([e], Subst, Int)
lookupTypeTrieRcIO md@((mt,_), _, _) t
    = RcT $ \d -> mapM (procTupRc md d) $ unMx (lmtty mt t) !! d
procTupRc md d (ty, s, i) = do mx <- lmtIO md ty
                               return (unMx mx !! d, s, i)
-}

lookupReorganizedIO md typ = let (avs, retty) = splitArgs $ normalize typ
                             in reorganizerId' (\av -> lmtIO md $ popArgs av retty) avs

-- ホントはProgGenSFのmtを使ったほうが速いはず.
lmtIO :: PGSFIOR CoreExpr -> Type -> RecompT IO CoreExpr
lmtIO md@(PGSFIOR fmtref (_,_,_,cmn)) ty = memoRTIO fmtref (computeExpTip md) ty

memoRTIO :: (Expression e) => ExpTrie e -> (Type -> RecompT IO e) -> Type -> RecompT IO e
memoRTIO fmtref compute ty
    = RcT $ \depth -> if memocondexp ty depth then ensureAtHand fmtref compute ty depth 
                                           else unRcT (compute ty) depth

ensureAtHand fmtref compute ty depth
    = do fmt <- readIORef fmtref
         case lookupFMT ty fmt of
           Nothing -> makeAtHand fmtref compute ty depth
           Just ar | inRange (bounds ar) depth -> return $ ar!depth
                   | otherwise                 -> makeAtHand fmtref compute ty depth

makeAtHand fmtref compute ty depth = do result <- unRcT (compute ty) depth
                                        if memocondexp ty (depth-1) then do ensureAtHand fmtref compute ty (depth-1)
{-
                                                                            modifyIORef fmtref (fst . addToLast ty depth result)
                                                                            return result
-}
                                                                            atomicModifyIORef fmtref (addToLast ty depth result)
{-
                                                                    else do modifyIORef fmtref (fst . mkSingle ty depth result)
                                                                            return result
-}
                                                                    else atomicModifyIORef fmtref (mkSingle ty depth result)
-- exptipをlookupするときもnormalizeすべき.と思ったけど,typetipにあるtypeはnormalizeされてる.

addToLast :: Expression e => Type -> Int -> [e] -> FMType (Array Int [e]) -> (FMType (Array Int [e]), [e])
addToLast ty depth result fmt = (updateFMT upd (error "addToLast: cannot happen") ty fmt, result)
    where upd ar = array (lo,depth) ((depth,result) : assocs ar)
                   where (lo,_hi) = bounds ar
mkSingle :: Expression e => Type -> Int -> [e] -> FMType (Array Int [e]) -> (FMType (Array Int [e]), [e])
mkSingle ty depth result fmt = (updateFMT (error "mkSingle: I think this cannot happen") (array (depth,depth) [(depth,result)]) ty fmt, result)


{-
modify :: Type -> Int -> FMType (Array Int [AnnExpr]) -> (FMType (Array Int [AnnExpr]), [AnnExpr])
modify ty depth fmt
    = case lookupFMT fmt ty of
        Nothing -> do result <- compute  ってことは, IO が間に入るので, atomic ではない.

atomicかどうかは後で考える.

要は,
・該当箇所がmemo対象でない場合,computeして終了.
            memo対象の場合(X),memoにあるなら,それを取ってきて終了.
                                     ないのであれば,{result <- compute;
                                                      該当箇所の1個浅いところがmemo対象でない場合,該当箇所のみをもつsingleton arrayを書き込んで終了(A)
                                                                               memo対象なら,{(X)を1こ浅いところで実行(結果は捨て);
                                                                                              (1個浅いところまでのarrayができてるので,)該当箇所を末尾に加えて終了(B)}}

atomicallyにやるのは(A)と(B)だけか.atomicModifyIORef :: IORef a -> (a -> (a, b)) -> IO b
実はmemocondexpで(memodepthなしで)できそう.
関数(X)が本質的.loopするので.(X)をensureAtHandという名前に.
-}



-- entry for memoization
matchFunctionsIO :: PGSFIOR CoreExpr -> Type -> DBoundT IO CoreExpr
matchFunctionsIO memodeb ty = case splitArgs (saferQuantify ty) of (avail,t) -> matchFunsIO memodeb avail t

-- saferQuantify ty = let offset = maxVarID (unquantify ty) + 1 in quantify' $ mapTV (offset+) ty

matchFunsIO :: PGSFIOR CoreExpr -> [Type] -> Type -> DBoundT IO CoreExpr
matchFunsIO memodeb avail reqret = catBags $ runPS (matchFuns' unifyableExprsIO memodeb avail reqret)

matchFuns' :: (Search m) => Generator m CoreExpr -> Generator m CoreExpr
-- matchFuns' = generateFuns matchPS filtExprs lookupListrie -- MemoDebの型の違いでこれはうまくいかなんだ.
matchFuns' rec md@(PGSFIOR _ (CL classLib, _, (_,(primgen,primmono)),cmn)) avail reqret
    = let clbehalf  = mguPrograms classLib []
          behalf    = rec md avail
          lltbehalf = lookupListrie lenavails rec md avail -- heuristic filtration
          lenavails = length avail
--          fe :: Type -> Type -> [CoreExpr] -> [CoreExpr] -- ^ heuristic filtration
          fe        = filtExprs (guess $ opt cmn)
      in fromAssumptions cmn lenavails behalf (\a b -> guard $ a==b) reqret avail `mplus`
          mapSum (retPrimMono cmn lenavails clbehalf lltbehalf behalf matchPS reqret) primmono `mplus`
          mapSum ((    if tv0 $ opt cmn then retGenTV0 else
                        if tv1 $ opt cmn then retGenTV1 else retGenOrd) cmn lenavails fe clbehalf lltbehalf behalf reqret) primgen

lookupListrie :: (Search m, Expression e) => Int -> Generator m e -> Generator m e
lookupListrie lenavails rec memodeb@(PGSFIOR _ (_,_,_,cmn)) avail t
                                    | constrL opts = matchAssumptions cmn lenavails t avail
                                    | guess opts = do
                                       args <- rec memodeb avail t
                                       let args' = filter (not.isClosed.toCE) args
                                       when (null args') mzero
                                       return args'
                                    | otherwise = do
                                       args <- rec memodeb avail t
                                       let args' = filter (not.isConstrExpr.toCE) args
                                       when (null args') mzero
                                       return args'
    where opts = opt cmn

\end{code}