{-# LANGUAGE UndecidableInstances #-}

module Feldspar.Run.Marshal where



import qualified Prelude
import Prelude hiding (length)

import Data.Typeable

import Feldspar
import Feldspar.Run.Representation
import Feldspar.Run.Compile
import Feldspar.Run.Frontend

import Language.Embedded.Backend.C (ExternalCompilerOpts (..), Default (..))



newtype Parser a = Parser {runParser :: String -> (a, String)}
  deriving (Functor)

instance Applicative Parser
  where
    pure  = return
    (<*>) = ap

instance Monad Parser
  where
    return a = Parser $ \s -> (a,s)
    p >>= k  = Parser $ \s -> let (a,s') = runParser p s in runParser (k a) s'

readParser :: forall a . (Read a, Typeable a) => Parser a
readParser = Parser $ \s -> case reads s of
    [(a,s')] -> (a,s')
    _        -> error $ unwords
        [ "toHaskell: cannot read"
        , show s
        , "as type"
        ,  show (typeOf (undefined :: a))
        ]

parse :: Parser a -> String -> a
parse = (fst .) . runParser

-- | Serialization/deserialization of Haskell values
--
-- The following property must hold for all @a@:
--
-- > a = parse toHaskell (fromHaskell a) Prelude.== a
class MarshalHaskell a
  where
    -- | Serialize a Haskell value
    fromHaskell :: a -> String
    default fromHaskell :: Show a => a -> String
    fromHaskell = show

    -- | Deserialize a Haskell value
    toHaskell :: Parser a
    default toHaskell :: (Read a, Typeable a) => Parser a
    toHaskell = readParser

instance MarshalHaskell Int
instance MarshalHaskell Int8
instance MarshalHaskell Int16
instance MarshalHaskell Int32
instance MarshalHaskell Int64
instance MarshalHaskell Word8
instance MarshalHaskell Word16
instance MarshalHaskell Word32
instance MarshalHaskell Word64
instance MarshalHaskell Float
instance MarshalHaskell Double

instance MarshalHaskell (Complex Float)
  where
    fromHaskell (r :+ i) = fromHaskell (r,i)
    toHaskell = fmap (uncurry (:+)) toHaskell

instance MarshalHaskell (Complex Double)
  where
    fromHaskell (r :+ i) = fromHaskell (r,i)
    toHaskell = fmap (uncurry (:+)) toHaskell

instance (MarshalHaskell a, MarshalHaskell b) => MarshalHaskell (a,b)
  where
    fromHaskell (a,b) = unwords [fromHaskell a, fromHaskell b]
    toHaskell = (,) <$> toHaskell <*> toHaskell

instance (MarshalHaskell a, MarshalHaskell b, MarshalHaskell c) => MarshalHaskell (a,b,c)
  where
    fromHaskell (a,b,c) = unwords [fromHaskell a, fromHaskell b, fromHaskell c]
    toHaskell = (,,) <$> toHaskell <*> toHaskell <*> toHaskell

instance (MarshalHaskell a, MarshalHaskell b, MarshalHaskell c, MarshalHaskell d) => MarshalHaskell (a,b,c,d)
  where
    fromHaskell (a,b,c,d) = unwords [fromHaskell a, fromHaskell b, fromHaskell c, fromHaskell d]
    toHaskell = (,,,) <$> toHaskell <*> toHaskell <*> toHaskell <*> toHaskell

instance MarshalHaskell a => MarshalHaskell [a]
  where
    fromHaskell as = unwords $ show (Prelude.length as) : map fromHaskell as

    toHaskell = do
        len <- toHaskell
        replicateM len toHaskell

-- | Serialization/deserialization of Feldspar values
class (MarshalHaskell (HaskellRep a)) => MarshalFeld a
  where
    -- | The Haskell representation of a Feldspar value
    type HaskellRep a

    -- | Serialize a Feldspar value to a handle
    fwrite :: Handle -> a -> Run ()

    default fwrite :: (PrimType b, Formattable b, a ~ Data b) =>
        Handle -> a -> Run ()
    fwrite hdl i = fput hdl "" i ""

    -- | Deserialize a Feldspar value from a handle
    fread :: Handle -> Run a

    default fread :: (PrimType b, Formattable b, a ~ Data b) => Handle -> Run a
    fread = fget

-- | Write a value to @stdout@
writeStd :: MarshalFeld a => a -> Run ()
writeStd = fwrite stdout

-- | Read a value from @stdin@
readStd :: MarshalFeld a => Run a
readStd = fread stdin

instance MarshalFeld (Data Int8)   where type HaskellRep (Data Int8)   = Int8
instance MarshalFeld (Data Int16)  where type HaskellRep (Data Int16)  = Int16
instance MarshalFeld (Data Int32)  where type HaskellRep (Data Int32)  = Int32
instance MarshalFeld (Data Int64)  where type HaskellRep (Data Int64)  = Int64
instance MarshalFeld (Data Word8)  where type HaskellRep (Data Word8)  = Word8
instance MarshalFeld (Data Word16) where type HaskellRep (Data Word16) = Word16
instance MarshalFeld (Data Word32) where type HaskellRep (Data Word32) = Word32
instance MarshalFeld (Data Word64) where type HaskellRep (Data Word64) = Word64
instance MarshalFeld (Data Float)  where type HaskellRep (Data Float)  = Float
instance MarshalFeld (Data Double) where type HaskellRep (Data Double) = Double

instance MarshalFeld (Data (Complex Float))
  where
    type HaskellRep (Data (Complex Float)) = Complex Float
    fwrite hdl c = fwrite hdl (realPart c, imagPart c)
    fread = fmap (uncurry complex) . fread

instance MarshalFeld (Data (Complex Double))
  where
    type HaskellRep (Data (Complex Double)) = Complex Double
    fwrite hdl c = fwrite hdl (realPart c, imagPart c)
    fread = fmap (uncurry complex) . fread

instance (MarshalFeld a, MarshalFeld b) => MarshalFeld (a,b)
  where
    type HaskellRep (a,b) = (HaskellRep a, HaskellRep b)
    fwrite hdl (a,b) = fwrite hdl a >> fprintf hdl " " >> fwrite hdl b
    fread hdl = (,) <$> fread hdl <*> fread hdl

instance (MarshalFeld a, MarshalFeld b, MarshalFeld c) => MarshalFeld (a,b,c)
  where
    type HaskellRep (a,b,c) = (HaskellRep a, HaskellRep b, HaskellRep c)
    fwrite hdl (a,b,c)
        =  fwrite hdl a >> fprintf hdl " "
        >> fwrite hdl b >> fprintf hdl " "
        >> fwrite hdl c
    fread hdl = (,,) <$> fread hdl <*> fread hdl <*> fread hdl

instance (MarshalFeld a, MarshalFeld b, MarshalFeld c, MarshalFeld d) => MarshalFeld (a,b,c,d)
  where
    type HaskellRep (a,b,c,d) = (HaskellRep a, HaskellRep b, HaskellRep c, HaskellRep d)
    fwrite hdl (a,b,c,d)
        =  fwrite hdl a >> fprintf hdl " "
        >> fwrite hdl b >> fprintf hdl " "
        >> fwrite hdl c >> fprintf hdl " "
        >> fwrite hdl d
    fread hdl = (,,,) <$> fread hdl <*> fread hdl <*> fread hdl <*> fread hdl

instance (MarshalHaskell (Internal a), MarshalFeld a, Syntax a) =>
    MarshalFeld (Arr a)
  where
    type HaskellRep (Arr a) = [Internal a]

    fwrite hdl arr = do
        len <- shareM $ length arr
        fput hdl "" len " "
        for (0,1,Excl len) $ \i -> do
            a <- getArr arr i
            fwrite hdl a
            fprintf hdl " "

    fread hdl = do
        len <- fget hdl
        arr <- newArr len
        for (0,1,Excl len) $ \i -> do
            a <- fread hdl
            setArr arr i a
        return arr

instance (MarshalHaskell (Internal a), MarshalFeld a, Syntax a) =>
    MarshalFeld (IArr a)
  where
    type HaskellRep (IArr a) = [Internal a]

    fwrite hdl arr = do
        len <- shareM $ length arr
        fput hdl "" len " "
        for (0,1,Excl len) $ \i -> do
            fwrite hdl $ arrIx arr i
            fprintf hdl " "

    fread hdl = do
        len <- fget hdl
        arr <- newArr len
        for (0,1,Excl len) $ \i -> do
            a <- fread hdl
            setArr arr i a
        iarr <- unsafeFreezeArr arr
        return iarr

-- | Connect a Feldspar function between serializable types to @stdin@/@stdout@
connectStdIO :: (MarshalFeld a, MarshalFeld b) => (a -> Run b) -> Run ()
connectStdIO f = (readStd >>= f) >>= writeStd

-- | Connect a Feldspar function between serializable types to @stdin@/@stdout@.
-- The input/output will be in the form of a list as recognized by 'toHaskell' /
-- 'fromHaskell' (i.e. the length followed by the elements in sequence).
--
-- The function will be mapped over the input list in a lazy manner.
streamStdIO :: (MarshalFeld a, MarshalFeld b) => (a -> Run b) -> Run ()
streamStdIO f = do
    n :: Data Length <- readStd
    writeStd n
    for (0,1,Excl n) $ \_ ->
      connectStdIO $ \a -> printf " " >> f a
  -- TODO Better to continue until EOF, but I wasn't able to make this work.
  -- Presumably, one needs to check for EOF inside each `fread` and signal that
  -- somehow.

-- | A version of 'marshalled' that takes 'ExternalCompilerOpts' as additional
-- argument
marshalled' :: (MarshalFeld a, MarshalFeld b)
    => CompilerOpts
    -> ExternalCompilerOpts
    -> (a -> Run b)  -- ^ Function to compile
    -> ((HaskellRep a -> IO (HaskellRep b)) -> IO c)
         -- ^ Function that has access to the compiled executable as a function
    -> IO c
marshalled' opts eopts f body =
    withCompiled' opts eopts (connectStdIO f) $ \g ->
      body (fmap (parse toHaskell) . g . fromHaskell)

-- | Compile a function and make it available as an 'IO' function. Note that
-- compilation only happens once, even if the function is used many times in the
-- body.
--
-- For example, given the following Feldspar function:
--
-- > sumArr :: DIArr Int32 -> Run (Data Int32)
-- > sumArr arr = do
-- >     r <- initRef 0
-- >     for (0,1,Excl $ length arr) $ \i -> modifyRef r (+ arrIx arr i)
-- >     unsafeFreezeRef r
--
-- 'marshalled' can be used as follows:
--
-- > *Main> marshalled sumArr $ \f -> (f [3,4,5] >>= print) >> (f [6,7,8,9] >>= print)
marshalled :: (MarshalFeld a, MarshalFeld b)
    => (a -> Run b)  -- ^ Function to compile
    -> ((HaskellRep a -> IO (HaskellRep b)) -> IO c)
         -- ^ Function that has access to the compiled executable as a function
    -> IO c
marshalled = marshalled' def def

-- | A version of 'marshalledStream' that takes 'ExternalCompilerOpts' as
-- additional argument
marshalledStream' :: (MarshalFeld a, MarshalFeld b)
    => CompilerOpts
    -> ExternalCompilerOpts
    -> (a -> Run b)  -- ^ Function to compile
    -> (([HaskellRep a] -> IO [HaskellRep b]) -> IO c)
         -- ^ Function that has access to the compiled executable as a function
    -> IO c
marshalledStream' opts eopts f body =
    withCompiled' opts eopts (streamStdIO f) $ \g ->
      body $ \is -> do
        parse toHaskell <$> g (fromHaskell is)

-- | Compile a function and make it available as an 'IO' function. The compiled
-- function will be applied repeatedly over the list of inputs producing a list
-- of outputs. Note that compilation only happens once, even if the function is
-- used many times in the body.
marshalledStream :: (MarshalFeld a, MarshalFeld b)
    => (a -> Run b)  -- ^ Function to compile
    -> (([HaskellRep a] -> IO [HaskellRep b]) -> IO c)
         -- ^ Function that has access to the compiled executable as a function
    -> IO c
marshalledStream = marshalledStream' def def

