{-# LANGUAGE Rank2Types, Trustworthy #-}

module Data.Columbia.SeekableWriter where

import Foreign.Marshal.Utils
import Foreign.Storable
import Data.Functor.Invariant
import Data.List
import Data.Word
import Data.Char
import Data.Bits
import Data.IORef
import System.IO
import System.FileLock
import Control.Monad.Reader
import Control.Monad.State
import Control.Monad
import Control.Arrow
import Control.Parallel.Strategies
import Data.Columbia.SeekableStream
import Data.Columbia.MyEndianness
import Data.Columbia.Mapper

data SeekableWriter m c = SeekableWriter
	{ _putToken :: !(c -> m()), _putIntegralToken :: !(Word32 -> m()), stream :: !(SeekableStream m c) }

instance (Functor m) => Invariant(SeekableWriter m) where
	invmap f g sw = SeekableWriter(_putToken sw.g) (_putIntegralToken sw) (fmap f(stream sw))

hoistWriter :: (forall t. m t -> m2 t) -> SeekableWriter m c -> SeekableWriter m2 c
hoistWriter f sw = SeekableWriter(\x -> f(_putToken sw x)) (\x->f(_putIntegralToken sw x)) (hoistStream f(stream sw))

putWord8 :: Handle -> Word8 -> IO()
putWord8 h x = with x(\p -> hPutBuf h p 1)

makeIoWriter :: IORef(Pointer,Pointer) -> Table -> FileLock -> SeekableWriter IO Word8
makeIoWriter ref table lock = SeekableWriter
	(\x->readIORef ref>>= \(n,sz)->mapBlock table n>>=(`poke` x)>>(writeIORef ref$!(succ n,max(succ n) sz)))
	{-
	TODO: not complete yet
	(\x->readIORef ref>>= \(n,sz)->mapBlock table n>>=(`poke` swapEndian' x)>>(writeIORef ref$!(n+4,max(n+4) sz)))-}
	(\_->fail"makeIoWriter: unimplemented")
	(makeIoStream ref table lock)

makeIoWriterChar :: IORef(Pointer,Pointer) -> Table -> FileLock -> SeekableWriter IO Char
makeIoWriterChar ref t = invmap(chr.fromIntegral) (fromIntegral.ord).makeIoWriter ref t

unshimmedIOWriter :: Handle -> FileLock -> SeekableWriter IO Word8
unshimmedIOWriter handle lock = SeekableWriter(putWord8 handle)
	(\n->putWord8 handle(fromIntegral$shiftR n 24)>>
		putWord8 handle(fromIntegral$shiftR n 16)>>
		putWord8 handle(fromIntegral$shiftR n 8)>>
		putWord8 handle(fromIntegral n))
	(unshimmedIOStream handle lock)

makeGenericWriter :: SeekableWriter(State(Word32, [t], [t])) t
makeGenericWriter = SeekableWriter
	(\x -> modify(\(n, ls, ls2) -> using(succ n, x:ls, drop 1 ls2) (evalTuple3 rseq rseq rseq)))
	(\_->fail"makeGenericWriter: unimplemented")
	makeGenericStream

putToken :: (Monad m) => c -> ReaderT(SeekableWriter m c) m ()
putToken c = ask>>=lift.(`_putToken` c)

putIntegralToken :: (Monad m) => Word32 -> ReaderT(SeekableWriter m c) m ()
putIntegralToken n = ask>>=lift.(`_putIntegralToken` n)

consumeTokenW :: (Monad m) => ReaderT(SeekableWriter m c) m (Maybe c)
consumeTokenW = ask>>=lift._consumeToken.stream

consumeIntegralTokenW :: (Monad m) => ReaderT(SeekableWriter m c) m (Maybe Word32)
consumeIntegralTokenW = ask>>=lift._consumeIntegralToken.stream

getWriterPosition :: (Monad m) => ReaderT(SeekableWriter m c) m Word32
getWriterPosition = ask>>=lift._getPosition.stream

seekWriter :: (Monad m) => Word32 -> ReaderT(SeekableWriter m c) m ()
seekWriter n = ask>>=lift.(`_seek` n).stream

seekWriterAtEnd :: (Monad m) => ReaderT(SeekableWriter m c) m ()
seekWriterAtEnd = ask>>=lift._seekAtEnd.stream

relSeekWriter :: (Monad m) => Word32 -> ReaderT(SeekableWriter m c) m ()
relSeekWriter n = ask>>=lift.runReaderT(relSeek n).stream