module Data.RefSerialize
(
     module Data.RefSerialize.Parser
    ,Serialize(
        showp
       ,readp
     )
    ,rshowp
    ,rreadp
    ,showps
    ,rshowps
    ,runR
    ,runW
    ,showpText
    ,readpText
    ,showpBinary
    ,readpBinary
    ,insertString
    ,insertChar
    ,rShow
    ,rRead
    ,insertVar
    ,readVar
    ,varName
    ,takep
    ,readHexp
    ,showHexp
    ,Context
    ,getRContext
    ,getWContext
    ,newContext
    ,showContext
    ,runRC
    ,runWC
)
 where
import Data.RefSerialize.Serialize
import Data.RefSerialize.Parser
import Unsafe.Coerce
import Data.Char(isAlpha, isSpace, isAlphaNum)
import Numeric(readHex,showHex)
import Data.ByteString.Lazy.Char8 as B
import Debug.Trace
import Data.Binary
import System.IO.Unsafe
import qualified Data.Map as M
import Data.Monoid
import Data.Maybe
import Debug.Trace
(!>) = flip . trace
newContext :: IO Context
newContext  = Data.RefSerialize.Serialize.empty
class Serialize c where
   showp :: c -> STW ()     
   readp ::  STR c          
rshowp :: Serialize c => c -> STW ()
rshowp  = insertVar  showp
 
 
rreadp ::  Serialize c => STR c
rreadp = readVar  readp
getRContext :: STR (Context, ByteString)
getRContext = STR(\(StatR(c,s,v)) -> Right (StatR (c,s,v), (c,v)))
getWContext :: STW (Context, ByteString)
getWContext = STW(\(StatW(c,s,v)) ->  (StatW (c,s,v), (c,"")))
rShow :: Serialize c => c -> ByteString
rShow c= runW  $  showp c
rRead :: Serialize c => ByteString -> c
rRead str= runR readp $ str
readHexp :: (Num a, Integral a) => STR a
readHexp = STR(\(StatR(c,s,v)) ->
   let us= unpack s
       l=  readHex  us
   in if Prelude.null l then Left . Error $  "readHexp: not readable: " ++ us
         else let ((x,str2):_)= l
              in Right(StatR(c, pack $ Prelude.dropWhile isSpace str2,v),x) )
   <?> "readHexp "
showHexp :: (Num a,Integral a,Show a) => a -> STW ()
showHexp var= STW(\(StatW(c,s,v)) ->  (StatW(c, mappend s [Expr (pack $ showHex var "")],v),()))
showpText :: Show a => a -> STW ()
showpText var= STW(\(StatW(c,s,v)) ->  (StatW(c, s `mappend` [Expr $ snoc (pack $ show var) ' '] ,v),()))
readpText :: Read a => STR a
readpText = STR(\(StatR(c,s,v)) ->
   let us= unpack s
       l=  readsPrec 1 us
   in if Prelude.null l then Left . Error $  "not readable: " ++ us
         else let ((x,str2):_)= l
              in Right(StatR(c, pack $ Prelude.dropWhile isSpace str2,v),x) )
   <?> "readp: readsPrec "
runR:: STR a -> ByteString ->  a
runR p str=unsafePerformIO $ do
    c <- newContext
    let (struct, vars)= readContext whereSep str
    return $ runRC (c, vars) p struct
runRC :: (Context, ByteString) -> STR a -> ByteString ->  a
runRC (c,vars) (STR f) struct=
  case   f (StatR(c,struct,vars) ) of
      Right (StatR _, a) -> a
      Left (Error s) -> error s
whereSep= "\r\nwhere{\r\n "
runW :: STW () -> ByteString
runW  f = unsafePerformIO $ do
      c  <- newContext
      return $ runWC (c,"") f `append` showContext c True
runWC ::(Context, ByteString) -> STW () -> ByteString
runWC (c,vars) (STW f) =
      let
          (StatW(c',str,_), _) = f (StatW(c,[],vars))
      in  showExpr  str  c'
showContext :: Context -> Bool -> ByteString
showContext c False=
     let  scontext= assocs c
     in   B.concat $ Prelude.map (\(n,(_,_,v,_))->"v" `append`  (pack $ show n)  `append`  "= "  `append`  showExpr v c  `append`  ";\r\n ")  scontext
showContext c True=
          let vars= showContext c False
          in if B.null vars  then "" else  whereSep `append` vars  `append`  "\r\n}"
showExpr :: [ShowF] -> Context -> ByteString
showExpr [] _ = B.empty
showExpr (Expr s:xs) c = s `mappend`  (cons ' ' $ showExpr  xs c)
showExpr ex@(Var v:xs) c=
   case Data.RefSerialize.Serialize.lookup  v  c  of
           Nothing -> error $ "showp: not found first variable in "++ show ex
           Just (_,_,exp,1)  -> delete v c `seq` showExpr exp c `mappend` (cons ' ' $ showExpr xs c)
           Just (_,_,exp,n)  ->  pack ('v':show v)  `mappend` (cons ' ' $ showExpr xs c)
 
showps :: Serialize a =>  a -> STW ByteString
showps x= STW(\(StatW(c,s,v))->
 let
    STW f= showp x
    (StatW (c',str,_), _) = f (StatW(c,[],v))
 in (StatW(c',s ,v), showExpr str c'))
rshowps x= STW(\(StatW(c,s,v))->
 let
    STW f= rshowp x
    (StatW (c',str,_), _) = f (StatW(c,[],v))
 in (StatW(c',s ,v), showExpr str c'))
insertVar :: (a -> STW ()) -> a -> STW ()
insertVar parser x= STW(\(StatW(c,s,v))->
 let mf = x `seq`findVar x c in
 case mf of
   True ->  (StatW(c,s `mappend` [Var hash],v),())
   False ->
         let
            STW f= parser x
            (StatW (c',str,_), _) = f  (StatW(c,[],v))
         in (StatW(addc str c',s `mappend` [Var hash] ,v), ()))
 where
  addc str c=  insert ( hash) (st,unsafeCoerce x,  str,1) c
  (hash,st) = hasht x
  findVar x c=
         case  Data.RefSerialize.Serialize.lookup  hash  c  of
           Nothing -> False
           Just (x,y,z,n)  ->  insert hash (x,y,z,n+1) c  `seq`  True
isInVars :: (a -> STW ()) -> a -> STW (Either ByteString ByteString)
isInVars parser x= STW(\(StatW(c,s,v))->
 let mf = trytofindEntireObject x c in
 case mf of
   Just  var ->  (StatW(c,s,v),Right var)
   Nothing ->
         let
            STW f= parser x
            (StatW (c',str,_), _) = f  (StatW(c,[],v))
         in (StatW(addc str c',s ,v), Left varname))
 where
  addc str c= insert ( hash) (st,unsafeCoerce x,  str,1) c
  (hash,st) = hasht x
  varname=  pack$ "v" ++ show hash
  trytofindEntireObject x c=
         case Data.RefSerialize.Serialize.lookup  hash  c  of
           Nothing -> Nothing
           Just(x,y,z,n)  -> insert hash (x,y,z,n+1) `seq` Just varname
readVar :: Serialize c => STR c -> STR c
readVar (STR f)=  STR(\stat@(StatR(c,s,v))->
     let
       s1= B.dropWhile isSpace s
       (var, str2) = B.span isAlphaNum s1
       str3= B.dropWhile isSpace str2
       mnvar= numVar $ unpack var
       nvar= fromJust mnvar
     in  if isNothing mnvar then f stat
         else
          case  trytofindEntireObject nvar c of
           Just  (_,x,_,_) ->  Right(StatR(c,str3,v),unsafeCoerce x)
           Nothing ->
            let
               (_, rest)= readContext (var `append` "= ") v
            in if B.null rest then Left (Error ( "RedSerialize: readVar: " ++ unpack var ++ "value not found" ))
               else  case f  (StatR(c,rest,v)) of
                 Right (StatR(c',s',v'),x) ->
                   let c''= insert nvar ( undefined, unsafeCoerce x,  [],0) c'
                   in  Right (StatR(c'', str3,v),x)
                 err -> err)
  where
  trytofindEntireObject x c=
         case Data.RefSerialize.Serialize.lookup   x  c  of
           Nothing -> Nothing
           justx   -> justx
insertString :: ByteString -> STW ()
insertString s1= STW(\(StatW(c,s,v)) ->  (StatW(c, s  `mappend` [ Expr  s1 ],v),()))
insertChar :: Char -> STW()
insertChar car= STW(\(StatW(c, s,v)) -> (StatW(c, s `mappend` [Expr $ pack [car]],v),()))
instance Serialize String where
    showp = showpText
    readp = readpText
instance  Serialize a => Serialize [a] where
   showp []= insertString "[]"
   showp (x:xs)= do
           insertChar '['
           rshowp x
           mapM f xs
           insertString "]"
           where
           f :: Serialize a => a -> STW ()
           f x= do
              insertChar ','
              rshowp x
   readp = (brackets . commaSep $ rreadp)   <?> "readp:: [] "
instance (Serialize a, Serialize b) => Serialize (a, b) where
    showp (x, y)= do
            insertString  "("
            rshowp x
            insertString ","
            rshowp y
            insertString ")"
    readp =  parens (do
            x <- rreadp
            comma
            y <- rreadp
            return (x,y))
            <?> "rreadp:: (,) "
instance (Serialize a, Serialize b, Serialize c) => Serialize (a, b,c) where
    showp (x, y, z)= do
            insertString "("
            rshowp x
            insertString ","
            rshowp y
            insertString ","
            rshowp z
            insertString ")"
    readp =  parens (do
            x <- rreadp
            comma
            y <- rreadp
            comma
            z <- rreadp
            return (x,y,z))
            <?> "rreadp:: (,,) "
instance (Serialize a, Serialize b, Serialize c, Serialize d) => Serialize (a, b,c, d) where
    showp (x, y, z, t)= do
            insertString "("
            rshowp x
            insertString ","
            rshowp y
            insertString ","
            rshowp z
            insertString ","
            rshowp t
            insertString ")"
    readp =  parens (do
            x <- rreadp
            comma
            y <- rreadp
            comma
            z <- rreadp
            comma
            t <- rreadp
            return (x,y,z,t))
            <?> "rreadp:: (,,,) "
instance (Serialize a, Ord a, Serialize b) => Serialize (M.Map a b) where
    showp m= showp $ M.toList m
    readp= do
           list <- readp  
           return $ M.fromList list
instance Serialize a => Serialize (Maybe a) where
    showp Nothing = insertString "Nothing"
    showp (Just x) =do
          insertString "Just"
          showp x
    readp =  choice [rNothing, rJust] where
      rNothing = symbol "Nothing" >> return Nothing
      rJust =  do
         symbol "Just"
         x <- readp
         return $ Just x
instance (Serialize a, Serialize b) => Serialize (Either a b) where
    showp (Left x) = do
       insertString "Left"
       rshowp x
    showp (Right x) = do
       insertString "Right"
       rshowp x
    readp =  choice [rLeft, rRight] where
      rLeft = symbol "Left" >> rreadp >>= \x -> return $ Left x
      rRight = symbol "Right" >> rreadp >>= \x -> return $ Right x
binPrefix=   "Bin "
binPrefixSp= append (pack binPrefix) " "
showpBinary :: Binary a => a -> STW ()
showpBinary x = do
    let s = encode x
    let n = pack . show $ B.length s
    insertString $  binPrefixSp `append` n `append` " " `append` s
readpBinary :: Binary a => STR a
readpBinary = do
      symbol binPrefix
      n     <- integer
      str   <- takep $ fromIntegral n
      let x = decode str
      return x
takep :: Int -> STR ByteString
takep n=   take1 "" n
  where
  take1 s 0= return  s
  take1 s n=  anyChar >>= \x -> take1 (snoc s x ) (n1)
instance (Show a, Read a )=> Serialize a where
  showp= showpText
  readp= readpText