-- 
-- (c) Susumu Katayama
--
module MagicHaskeller.MemoToFiles where
import System.IO
import System.Directory(doesFileExist, createDirectoryIfMissing)
import MagicHaskeller.ShortString
import Data.ByteString.Char8      as C
import Data.ByteString.Lazy.Char8 as LC

import Control.Monad.Search.Combinatorial
import MagicHaskeller.DebMT
import MagicHaskeller.Types

import MagicHaskeller.PriorSubsts
import Data.Monoid
import Data.Ix



-- copied from ProgGen.lhs. toMemo削って型変えた．てゆーかそれ以前に，散らばってるfreezePSをProgramGenerator辺りにまとめたい気も
freezePS :: Search m => Type -> PriorSubsts m (Bag e) -> m (Possibility e)
freezePS ty ps
    = let mxty = maxVarID ty -- `max` maximum (map maxVarID avail)
      -- in toMemo $ mergesortDepthWithBy (\(xs,k,i) (ys,_,_) -> (xs `mappend` ys, k, i)) (\(_,k,_) (_,l,_) -> k `compare` l) $ unPS ps emptySubst (mxty+1)
      in mergesortDepthWithBy (\(xs,k,i) (ys,_,_) -> (xs `mappend` ys, k, i)) (\(_,k,_) (_,l,_) -> k `compare` l) $ fps mxty ps
      -- in toMemo $ mergesortDepthWithBy (\(xs,k,i) (ys,_,_) -> (xs `mappend` ys, k, i)) (\ (_,k,_) (_,l,_) -> normalize (apply k ty) `compare` normalize (apply l ty)) $ fps mxty ps
fps :: Search m => Int -> PriorSubsts m es -> m (es,[(Int, Type)],Int)
fps mxty (PS f) = do (exprs, sub, m) <- f emptySubst (mxty+1)
                     return (exprs, filterSubst sub mxty, m)
    where filterSubst :: Subst -> Int -> [(Int, Type)]
	  filterSubst sub  mx = [ t | t@(i,_) <- sub, inRange (0,mx) i ] -- note that the assoc list is NOT sorted.

-- これってProgGen限定か
memoPSRTIO :: ShortString b =>
             MemoCond
                 -> MapType (Matrix (Possibility b))
	         -> (Type -> PriorSubsts (RecompT IO) [b]) -- ^ This will be used instead if the entry is not found.
	         -> Type -> PriorSubsts (RecompT IO) [b]
memoPSRTIO policy mt f t = PS $ \subst mx ->
              let (tn, decoder) = encode t mx
              in (fmap (\ (exprs, sub, m) -> (exprs, retrieve decoder sub `plusSubst` subst, mx+m)) $ (memoRTIO policy mt (\u ->  freezePS u (f u)) tn))


memoRTIO :: ShortString b =>
             MemoCond -- IOを返す．つまり，メモリやハードディスクの空きによっても変えられるようにする．
                 -> MapType (Matrix b)     -- ^ Memoization table for the Ram case.
	         -> (Type -> RecompT IO b) -- ^ This will be used instead if the entry is not found.
	         -> Type -> RecompT IO b
memoRTIO policy mt f t = RcT $ memoer policy mt (\ty -> unRcT (f ty)) t
memoer :: ShortString b =>
          MemoCond
          -> MapType (Matrix b)
	  -> (Type -> Int -> IO [b])
	  -> Type -> Int -> IO [b]
memoer policy mt f ty depth
    = do memotype <- policy ty depth
         case memotype of Recompute -> compute
                          Ram       -> return $ unMx (lookupMT mt ty) !! depth
	                  Disk   fp -> let directory = fp++shows depth "/" -- care about Windows later....
                                           filepath  = directory ++ show ty
			               in do createDirectoryIfMissing True directory
                                             memoToFile readsBriefly showsBriefly filepath compute
      where compute = f ty depth
data MemoType = Recompute -- ^ Recompute instead of memoizing.
              | Ram       -- ^ Use the memoization table based on lazy evaluation, like in older versions.
              | Disk FilePath -- ^ Use the directory specified by @FilePath@ as the persistent memoization table.
type MemoCond = Type -> Int -> IO MemoType


-- | General-purposed memoizer (This could be put in a different module.)
memoToFile :: (C.ByteString -> Maybe (a,C.ByteString)) -> (a -> LC.ByteString -> LC.ByteString)
           -> FilePath -- ^ where to memoize
           -> IO a     -- ^ invoked if there is no such file
           -> IO a
memoToFile parser printer filepath compute
    = let write = do result <- compute
                     LC.writeFile filepath (printer result LC.empty)
		     return result
      in do there <- doesFileExist filepath
            if there then do cs <- C.readFile filepath -- Read strictly, and close (not semi-close) it. System.IO.readFile cannot achieve this behavior. 
			     case parser cs of Just (x,_) -> return x
					       _          -> do -- If the file is broken, just fix it. でも誰かが書き込み中だと困る?
                                                              System.IO.hPutStrLn stderr ("File " ++ filepath ++ " was broken.")
                                                              write
	             else write
