{-# LANGUAGE AllowAmbiguousTypes #-}
{-# LANGUAGE UndecidableInstances #-} -- for CBLen

module Binrep.BLen
  ( module Binrep.BLen
  , module Binrep.BLen.Internal.AsBLen
  ) where

import Binrep.BLen.Internal.AsBLen
import Binrep.Util ( natVal'' )

import GHC.TypeLits

import Data.ByteString qualified as B

import Data.Word
import Data.Int

import Data.Void ( Void, absurd )

type BLenT = Int

{- | The length in bytes of a value of the given type can be known on the cheap
     e.g. by reading a length field, or using compile time information.

Some binary representation building blocks require the notion of length in bytes
in order to handle, e.g. null padding. One may always obtain this by serializing
the value, then reading out the length of the output bytestring. But in most
cases, we can be much more efficient.

  * Certain primitives have a size known at compile time, irrelevant of the
    value. A 'Word64' is always 8 bytes; some data null-padded to @n@ bytes is
    exactly @n@ bytes long.
  * For simple ADTs, it's often possible to calculate length in bytes via
    pattern matching and some numeric operations. Very little actual work.

This type class enables each type to implement its own efficient method of byte
length calculation. Aim to write something that plainly feels more efficient
than full serialization. If that doesn't feel possible, you might be working
with a type ill-suited for binary representation.

A thought: Some instances could be improved by reifying 'CBLen'. But it would
mess up all the deriving, and it feels like too minor an improvement to be
worthwhile supporting, writing a bunch of newtype wrappers, etc.
-}
class BLen a where
    -- | The length in bytes of the serialized value.
    --
    -- The default implementation reifies the constant length for the type. If a
    -- type-wide constant length is not defined, it will fail at compile time.
    blen :: a -> BLenT
    default blen :: KnownNat (CBLen a) => a -> BLenT
    blen a
_ = forall a (n :: Natural). (n ~ CBLen a, KnownNat n) => BLenT
cblen @a

    -- | The length in bytes of any value of the given type is constant.
    --
    -- Many binary representation primitives are constant, or may be designed to
    -- "store" their size in their type. This is a stronger statement about
    -- their length than just 'blen'.
    --
    -- This is now an associated type family of the 'BLen' type class in hopes
    -- of simplifying the binrep framework.
    type CBLen a :: Natural
    type CBLen a =
        TypeError
            (       'Text "No CBLen associated family instance defined for "
              ':<>: 'ShowType a
            )

typeNatToBLen :: forall n. KnownNat n => BLenT
typeNatToBLen :: forall (n :: Natural). KnownNat n => BLenT
typeNatToBLen = forall a. AsBLen a => Natural -> a
natToBLen forall a b. (a -> b) -> a -> b
$ forall (a :: Natural). KnownNat a => Natural
natVal'' @n
{-# INLINE typeNatToBLen #-}

-- | Reify a type's constant byte length to the term level.
cblen :: forall a n. (n ~ CBLen a, KnownNat n) => BLenT
cblen :: forall a (n :: Natural). (n ~ CBLen a, KnownNat n) => BLenT
cblen = forall (n :: Natural). KnownNat n => BLenT
typeNatToBLen @n
{-# INLINE cblen #-}

-- | Impossible to put a byte length to 'Void'.
instance BLen Void where
    blen :: Void -> BLenT
blen = forall a. Void -> a
absurd

-- | @O(n)@
instance BLen a => BLen [a] where
    blen :: [a] -> BLenT
blen = forall (t :: * -> *) a. (Foldable t, Num a) => t a -> a
sum forall b c a. (b -> c) -> (a -> b) -> a -> c
. forall a b. (a -> b) -> [a] -> [b]
map forall a. BLen a => a -> BLenT
blen

instance (BLen a, BLen b) => BLen (a, b) where
    blen :: (a, b) -> BLenT
blen (a
a, b
b) = forall a. BLen a => a -> BLenT
blen a
a forall a. Num a => a -> a -> a
+ forall a. BLen a => a -> BLenT
blen b
b

instance BLen B.ByteString where
    blen :: ByteString -> BLenT
blen = forall a. AsBLen a => BLenT -> a
posIntToBLen forall b c a. (b -> c) -> (a -> b) -> a -> c
. ByteString -> BLenT
B.length

instance BLen Word8  where type CBLen Word8  = 1
instance BLen  Int8  where type CBLen  Int8  = 1
instance BLen Word16 where type CBLen Word16 = 2
instance BLen  Int16 where type CBLen  Int16 = 2
instance BLen Word32 where type CBLen Word32 = 4
instance BLen  Int32 where type CBLen  Int32 = 4
instance BLen Word64 where type CBLen Word64 = 8
instance BLen  Int64 where type CBLen  Int64 = 8

--------------------------------------------------------------------------------

-- | Newtype wrapper for defining 'BLen' instances which are allowed to assume
--   the existence of a valid 'CBLen' family instance.
newtype WithCBLen a = WithCBLen { forall a. WithCBLen a -> a
unWithCBLen :: a }

instance KnownNat (CBLen a) => BLen (WithCBLen [a]) where
    blen :: WithCBLen [a] -> BLenT
blen (WithCBLen [a]
l) = forall a (n :: Natural). (n ~ CBLen a, KnownNat n) => BLenT
cblen @a forall a. Num a => a -> a -> a
* forall (t :: * -> *) a. Foldable t => t a -> BLenT
length [a]
l
instance KnownNat (CBLen a + CBLen b) => BLen (WithCBLen (a, b)) where
    type CBLen (WithCBLen (a, b)) = CBLen a + CBLen b