{-# OPTIONS -fglasgow-exts -fallow-undecidable-instances -fallow-overlapping-instances  #-}
module Data.RefSerialize 
(    --module Data.Serialize
     module Data.Parser  -- export the complete set of Parsec.Token parsers adapted for composing readp parsers
    ,Serialize(
        showp  -- :: c -> ST String  -- shows the content of a expression, must be user defined
   
       ,readp  -- ::  ST c           -- read the content of a expression, must be user defined

       ,rshowp -- :: c -> ST String  -- insert a reference (a variable in the where section) 
       --rshowp -- = insertVar  showp -- default definition
   
       ,rreadp -- :: ST c            -- read a variable in the where section (to use for deserializing rshowp output)
       --rreadp -- = readVar  readp   -- default definition
    
    
    )
    ,rShow    -- ::  Serialize c => c -> String         -- use the rshowp parser to serialize the object
    ,rRead    -- ::  Serialize c => String ->c          -- deserialize  trough the rreadp parser
    ,insertVar-- :: (a -> ST String) -> a -> ST String  -- insert a variable, its value generated by a showp parser, will be inserted in the where section
    ,readVar  -- ::  read a variable referenced int the where section
    ,varName  -- ::  two variables that point to the same address will have identical varname (derived from import System.Mem.StableName)
    ,runR     -- :: ST a -> String ->  a    runR parser string  -- deserialize the string with the parser
    ,runW     -- :: ST String -> String     runW $ parser x     -- serialize x with the parser
)   
 where
import qualified Data.Map as M
import Data.Serialize
import Data.Parser
import Unsafe.Coerce
import Data.Char(isAlpha, isSpace, isAlphaNum)
--import Token

class Serialize c where
   showp :: c -> ST String
   
   readp ::  ST c

   rshowp :: c -> ST String
   rshowp  = insertVar  showp
   
   rreadp :: ST c
   rreadp = readVar  readp 
{-
#ifdef Axioms

   serializeAxioms: Axioms c
   serializeAxioms= axioms{
         unary=   [Axiom "reverse" 
                            (\x ->  let str= rShow x
                                        y = rRead xtr
                                    in  y== x)
                                       
                   AxioM "pointer equality"
                           (\x ->   let str= rShow[x,x]
                                        [y,z] = rRead str
                                    in varName y== varName z)
                  ]
        }
#endif                                          
-}   

         
rShow :: Serialize c => c -> String
rShow c= runW  $  rshowp c
    
rRead :: Serialize c => String ->c
rRead str= runR rreadp $ str


readSR :: Read a => ST a
readSR = ST(\(Stat(c,s,v)) -> let ((x,str2):_)= readsPrec 1 s in Right(Stat(c,str2,v),x) )

runR:: ST a -> String ->  a
runR   (ST f) str= 
    let (struct, vars)= readContext "where " str 
    in  case   f (Stat(M.empty,struct,vars) ) of
          Right (Stat _, a) -> a
          Left (Error s) -> error s
  

  
showSR :: Show a => a -> ST String
showSR var= ST(\(Stat(c,s,v)) ->  Right(Stat(c,s,v),show var))

runW :: ST String -> String
runW (ST f) = case  f (Stat(M.empty,"",""))  of
              Right (Stat (c,_,_), str) ->
                let scontext= M.assocs c
                    show1 c= concatMap (\(n,(_,v))->"v"++ show n++"= "++v++"; ")  scontext  
                    vars= show1  c
                    strContext= if null vars  then "" else  " where {"++vars ++ "}"
                    
                in  str ++ strContext
            
              Left (Error s) -> error s
      


instance  Serialize a => Serialize [a] where
  showp []= return "[]"
  showp (x:xs)=  do
           s1<-   showp x
           sn<- mapM f xs
           return $ "["++ s1++ concat sn ++"]"
           where
           f x=do  str <- showp (x:: a)
                   return $ ", "++str
  

  readp =  brackets $ commaSep $ readp 

  rshowp  = insertVar rshowp1 where
    rshowp1 []= insertVar return "[]"
    rshowp1 (x:xs)= do
           s1<-   rshowp x
           sn<- mapM f xs
           return $ "["++ s1++ concat sn ++"]"
           where
           f x= do str <- rshowp (x:: a)
                   return $ ", "++str
  

  rreadp = readVar $ brackets $ commaSep $ rreadp   

{-
instance Serialize Int where
  showp = showSR
  readp = readSR

instance Serialize Char where
  showp = showSR
  readp = readSR
-}
instance (Show a, Read a) => Serialize a where
    showp = showSR
    readp = readSR
                
{-      
instance Serialize String where
    showp = showSR 
    readp = readSR 
    
instance Serialize Integer where
    showp = showSR
    readp = readSR                  
-}

-- | insert a variable at this position. and the expression value in the where part.
--   runW rshowp (1::Int) -> "1"
--   runW (insertVar rshowp) (1::Int) -> v1 where { v1=1}
--   This is useful when the object is referenced many times

insertVar :: (a -> ST String) -> a -> ST String
insertVar parser x= ST(\(Stat(c,s,v))->
 let mf = trytofindEntireObject x c in
 case mf of
   Just  var ->  Right(Stat(c,s,v),var)
   Nothing -> 
         let 
            ST f= parser x
            Right (Stat (c',_,_), str) = f  (Stat(c,s,v))
            
         in Right(Stat(addc str c',s,v), ' ':varname))
 where
  addc str c= M.insert ( hash) (unsafeCoerce x,  str) c 
  hash = hasht x
  varname= "v" ++ show hash 

  trytofindEntireObject x c= 
         case M.lookup  hash  c  of
           Nothing -> Nothing
           Just _  -> Just varname
           
 

readVar :: Serialize c => ST c -> ST c
readVar parser=  ST(\(Stat(c,s,v))->
          let
               s1= dropWhile isSpace s
               (var, str2) = span isAlphaNum s1

          in case trytofindEntireObject (numVar var) c  of
 
           Just  (x,_) ->  Right(Stat(c,str2,v),unsafeCoerce x)
           Nothing ->              
            let   
               (_, rest)= readContext (var++"= ") v
               ST f= parser 
            in  case f  (Stat(c,rest,v)) --`debug` ("s="++s++"var="++var++"rest="++rest )
            of
                 Right (Stat(c',s',v'),x) ->
                   let c''= M.insert (numVar var) (unsafeCoerce x,  "") c' 
                   in  Right (Stat(c'',str2,v),x)  
                   
                 err -> err)
  where
  trytofindEntireObject x c= 
         case M.lookup   x  c  of
           Nothing -> Nothing
           justx   -> justx