{-# LANGUAGE ScopedTypeVariables #-}
module Data.Geometry.Geos.Raw.Serialize
  ( createReader
  , createWriter
  , createWktReader
  , createWktWriter
  , read
  , readHex
  , readWkt
  , write
  , writeHex
  , writeWkt
  )
where
import           Prelude                 hiding ( read )
import qualified Data.Geometry.Geos.Raw.Internal
                                               as I
import           Data.Geometry.Geos.Raw.Base
import           Data.Geometry.Geos.Raw.Geometry
import           Foreign                 hiding ( throwIfNull )
import           Foreign.C.String
import           Foreign.C.Types
import qualified Data.ByteString.Char8         as BC


newtype Reader = Reader { _unReader :: ForeignPtr I.GEOSWKBReader }
newtype Writer = Writer { _unWriter :: ForeignPtr I.GEOSWKBWriter }

newtype WktReader = WktReader { _unWktReader :: ForeignPtr I.GEOSWKTReader }
newtype WktWriter = WktWriter { _unWktWriter :: ForeignPtr I.GEOSWKTWriter }

createReader :: Geos Reader
createReader = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "Create Reader" $ I.geos_WKBReaderCreate h
  traverse (fmap Reader . newForeignPtrEnv I.geos_WKBReaderDestroy h) eitherPtr

createWktReader :: Geos WktReader
createWktReader = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "Create WKT Reader" $ I.geos_WKTReaderCreate h
  traverse (fmap WktReader . newForeignPtrEnv I.geos_WKTReaderDestroy h)
           eitherPtr

read_
  :: (  I.GEOSContextHandle_t
     -> Ptr I.GEOSWKBReader
     -> CString
     -> CSize
     -> IO (Ptr I.GEOSGeometry)
     )
  -> Reader
  -> BC.ByteString
  -> Geos Geom
read_ f (Reader r) bs = withGeos' $ \h -> do
  eitherPtr <- readBlock h
  traverse (constructGeometry h) eitherPtr
 where
  readBlock h = throwIfNull' "read_" $ withForeignPtr r $ \rp ->
    BC.useAsCStringLen bs $ \(cs, l) -> f h rp cs $ fromIntegral l

read :: Reader -> BC.ByteString -> Geos Geom
read = readWkb

readWkb :: Reader -> BC.ByteString -> Geos Geom
readWkb = read_ I.geos_WKBReaderRead

readHex :: Reader -> BC.ByteString -> Geos Geom
readHex = read_ I.geos_WKBReaderReadHex

readWkt :: WktReader -> BC.ByteString -> Geos Geom
readWkt (WktReader r) bs = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "readWKT" $ withForeignPtr r $ \rp ->
    BC.useAsCString bs $ \cs -> I.geos_WKTReaderRead h rp cs
  traverse (constructGeometry h) eitherPtr

createWriter :: Geos Writer
createWriter = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "CreateWriter" $ I.geos_WKBWriterCreate h
  traverse (create h) eitherPtr
 where
  create h ptr = do
    I.geos_WKBWriterSetIncludeSRID h ptr $ fromBool True
    fp <- newForeignPtrEnv I.geos_WKBWriterDestroy h ptr
    pure $ Writer fp


createWktWriter :: Geos WktWriter
createWktWriter = withGeos' $ \h -> do
  eitherPtr <- throwIfNull' "CreateWktWriter" $ I.geos_WKTWriterCreate h
  traverse (fmap WktWriter . newForeignPtrEnv I.geos_WKTWriterDestroy h)
           eitherPtr

write_
  :: Geometry a
  => (  I.GEOSContextHandle_t
     -> Ptr I.GEOSWKBWriter
     -> Ptr I.GEOSGeometry
     -> Ptr CSize
     -> IO CString
     )
  -> Writer
  -> a
  -> Geos BC.ByteString
write_ f (Writer w) g = withGeos $ \h -> do
  clen <- withForeignPtr w $ \wp -> withGeometry g $ \gp -> alloca $ \lp -> do
    cs <- f h wp gp lp
    vl <- fromIntegral `fmap` peek lp
    pure (cs, vl)
  BC.packCStringLen clen

write :: Geometry a => Writer -> a -> Geos BC.ByteString
write = write_ I.geos_WKBWriterWrite

writeHex :: Geometry a => Writer -> a -> Geos BC.ByteString
writeHex = write_ I.geos_WKBWriterWriteHex

writeWkt :: Geometry a => WktWriter -> a -> Geos BC.ByteString
writeWkt (WktWriter w) g = withGeos $ \h -> do
  wkt <- withForeignPtr w
    $ \wp -> withGeometry g $ \gp -> I.geos_WKTWriterWrite h wp gp
  BC.packCString wkt