-- 
-- (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 => TyVar -> PriorSubsts m es -> m (es,[(TyVar, Type)],TyVar)
fps mxty (PS f) = do (exprs, sub, m) <- f emptySubst (mxty+1)
                     return (exprs, filterSubst sub mxty, m)
    where filterSubst :: Subst -> TyVar -> [(TyVar, 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 (\ty depth -> return $ unMx (lookupMT mt ty) !! depth) (\u ->  freezePS u (f u)) tn))


memoRTIO :: ShortString b =>
             MemoCond
                 -> (Type -> Int -> IO [b]) -- ^ look up the memoization table in the RAM.
                 -> (Type -> RecompT IO b) -- ^ This will be used instead if the entry is not found.
                 -> Type -> RecompT IO b
memoRTIO policy lor f t = RcT $ memoer policy lor (\ty -> unRcT (f ty)) t
memoer :: ShortString b =>
          MemoCond
          -> (Type -> Int -> IO [b])
          -> (Type -> Int -> IO [b])
          -> Type -> Int -> IO [b]
memoer policy lor f ty depth
    = do memotype <- policy ty depth
         case memotype of Recompute -> compute
                          Ram       -> lor ty depth
                          Disk   fp | Prelude.length filepath < 250 -> do -- If I remember correctly, UNIX does not permit filenames longer than 255 letters.
                                             -- System.IO.putStrLn "Hit!"
                                             -- System.IO.putStrLn ("Directory name: "++directory)
                                             -- System.IO.putStrLn ("FilePath: "++ filepath)
                                             createDirectoryIfMissing True directory
                                             memoToFile readBriefly showBriefly filepath compute
                                    | otherwise -> compute -- This is safer than Ram. Still this behavior can be overridden by specifying the MemoCond accordingly
                                                           -- (though that can be unsafe).
                              where
                                directory = fp++shows depth "/" -- care about Windows later....
                                filepath  = directory ++ show ty
      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 -- IOを返す.つまり,メモリやハードディスクの空きによっても変えられるようにする.


-- | General-purposed memoizer (This could be put in a different module.)
memoToFile :: (C.ByteString -> Maybe a) -- ^ parser
           -> (a -> LC.ByteString)      -- ^ printer
           -> 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)
                     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