content: {-# OPTIONS_HADDOCK hide       #-}
  {-# LANGUAGE FlexibleInstances #-}
  {-# LANGUAGE DefaultSignatures #-}
  {-# LANGUAGE FlexibleContexts  #-}
  
  -- | Internal module that has the encode class and some utility functions.
  module Raaz.Core.Encode.Internal
         ( Encodable(..), Format(..)
         ) where
  
  import Data.Maybe
  
  import           Data.ByteString              (ByteString)
  import           Data.ByteString.Internal     (unsafeCreate)
  import           Data.String
  import           Data.Word
  import           Foreign.Ptr
  import Prelude hiding               (length)
  import System.IO.Unsafe   (unsafePerformIO)
  
  import Raaz.Core.Types.Endian
  import Raaz.Core.Types.Pointer
  import Raaz.Core.Types.Pointer.Internal
  import Raaz.Core.Util.ByteString(length, withByteString)
  
  
  -- | The type class `Encodable` captures all the types that can be
  -- encoded into a stream of bytes. For a user defined type say @Foo@,
  -- defining an instance `Encodable` is all that is required to make
  -- use of `encode` and `decode` for any of the supported encoding
  -- formats (i.e. instances of the class `Format`).
  --
  -- Minimum complete definition for this class is `toByteString` and
  -- `fromByteString`. Instances of `EndianStore` have default
  -- definitions for both these functions and hence a trivial instance
  -- declaration is sufficient for such types.
  --
  -- > newtype Foo = Foo (LE Word64) deriving (Storable, EndianStore)
  -- >
  -- > instance EndianStore Foo where
  -- >   ...
  -- >
  -- > instance Encodable Foo
  -- >
  --
  -- In particular, all the endian encoded versions of Haskell's word,
  -- i.e types like @`LE` Word32@, @`LE` Word64@ etc, are instances of
  -- `Encodable`. Note that the corresponding plain type is /not/ an
  -- instance of `Encodable` because encoding of say `Word32` without
  -- specifying whether the endianness is meaningless.
  --
  -- === Laws for encoding
  --
  -- Note that not all byte strings can be valid encodings and
  -- `fromByteString` might fail and return `Nothing` on certain
  -- inputs. However, we require the minimum guarantee that
  -- `fromByteString` for actual encodings of elements, i.e. we require
  -- the instances to satisfy the law.
  --
  -- > fromByteString . toByteString = Just
  --
  -- TODO: Write test cases for encoding laws.
  
  class Encodable a where
    -- | Convert stuff to bytestring
    toByteString          :: a           -> ByteString
  
    -- | Try parsing back a value. Returns nothing on failure.
    fromByteString        :: ByteString  -> Maybe a
  
    -- | Unsafe version of `fromByteString`
    unsafeFromByteString  :: ByteString  -> a
  
    default toByteString :: EndianStore a => a -> ByteString
    toByteString w    = unsafeCreate (fromEnum $ sizeOf (pure w)) putit
      where putit ptr = store (castPtr ptr) w
  
  
    default fromByteString :: EndianStore a => ByteString -> Maybe a
    fromByteString bs  | sizeOf proxy == length bs   = Just w
                       | otherwise                   = Nothing
           where w     = unsafePerformIO $ withByteString bs (load . castPtr)
                 proxy = pure w
  
    unsafeFromByteString = fromMaybe (error "fromByteString error") . fromByteString
  
  instance Encodable Word8
  instance Encodable (LE Word32)
  instance Encodable (LE Word64)
  instance Encodable (BE Word32)
  instance Encodable (BE Word64)
  instance Encodable ()
  
  instance Encodable ByteString where
    toByteString         = id
    {-# INLINE toByteString #-}
    fromByteString       = Just
    {-# INLINE fromByteString #-}
    unsafeFromByteString = id
    {-# INLINE unsafeFromByteString #-}
  
  instance Encodable a => Encodable (BYTES a) where
    toByteString         (BYTES a) = toByteString a
    fromByteString        = fmap BYTES . fromByteString
    unsafeFromByteString  = BYTES      . unsafeFromByteString
  
  
  
  -- | A binary format is a representation of binary data often in
  -- printable form. We distinguish between various binary formats at
  -- the type level and each supported format corresponds to an instance
  -- of the the class `Format`. The `encodeByteString` and
  -- `decodeFormat` are required to satisfy the laws
  --
  -- > decodeFormat . encodeByteString = id
  --
  -- For type safety, the formats themselves are opaque types and hence
  -- it is not possible to obtain the underlying binary data directly.
  -- We require binary formats to be instances of the class `Encodable`,
  -- with the combinators `toByteString` and `fromByteString` of the
  -- `Encodable` class performing the actual encoding and decoding.
  --
  -- Instances of `Format` are required to be instances of `Show` and so
  -- that the encoded format can be easily printed. They are also
  -- required to be instances of `IsString` so that they can be easily
  -- represented in Haskell source using the @OverloadedStrings@
  -- extension.  However, be careful when using this due to the fact
  -- that invalid encodings can lead to runtime errors.
  --
  class (IsString fmt, Show fmt, Encodable fmt) => Format fmt where
  
    -- | Encode binary data into the format. The return type gurantees
    -- that any binary data can indeed be encoded into a format.
    encodeByteString :: ByteString -> fmt
  
    -- | Decode the format to its associated binary
    -- representation. Notice that this function always succeeds: we
    -- assume that elements of the type `fmt` are valid encodings and
    -- hence the return type is `ByteString` instead of @`Maybe`
    -- ByteString@.
    decodeFormat     :: fmt        -> ByteString
  
  -- | Bytestring itself is an encoding format (namely binary format).
  instance Format ByteString where
    encodeByteString = id
    {-# INLINE encodeByteString #-}
    decodeFormat     = id
    {-# INLINE decodeFormat     #-}
  