    -- |
    -- Module      : Basement.Endianness
    -- License     : BSD-style
    -- Maintainer  : Haskell Foundation
    -- Stability   : experimental
    -- Portability : portable
    --
    -- Set endianness tag to a given primitive. This will help for serialising
    -- data for protocols (such as the network protocols).
    --

    {-# LANGUAGE CPP #-}
    {-# LANGUAGE GeneralizedNewtypeDeriving #-}

    module Basement.Endianness
        (
          ByteSwap
          -- * Big Endian
        , BE(..), toBE, fromBE
          -- * Little Endian
        , LE(..), toLE, fromLE
          -- * System Endianness
        , Endianness(..)
        , endianness
        ) where

    import Basement.Compat.Base
    import Data.Word (byteSwap16, byteSwap32, byteSwap64)

    #if defined(ARCH_IS_LITTLE_ENDIAN) || defined(ARCH_IS_BIG_ENDIAN)
    #else
    import Foreign.Marshal.Alloc (alloca)
    import Foreign.Ptr (castPtr)
    import Foreign.Storable (poke, peek)
    import Data.Word (Word8, Word32)
    import System.IO.Unsafe (unsafePerformIO)
    #endif

    import Data.Bits

    -- #if !defined(ARCH_IS_LITTLE_ENDIAN) && !defined(ARCH_IS_BIG_ENDIAN)
    -- import Foundation.System.Info (endianness, Endianness(..))
    -- #endif

    data Endianness =
          LittleEndian
        | BigEndian
        deriving (Eq, Show)

    -- | Little Endian value
    newtype LE a = LE { unLE :: a }
      deriving (Show, Eq, Typeable, Bits)
    instance (ByteSwap a, Ord a) => Ord (LE a) where
        compare e1 e2 = compare (fromLE e1) (fromLE e2)

    -- | Big Endian value
    newtype BE a = BE { unBE :: a }
      deriving (Show, Eq, Typeable, Bits)
    instance (ByteSwap a, Ord a) => Ord (BE a) where
        compare e1 e2 = compare (fromBE e1) (fromBE e2)

    -- | Convert a value in cpu endianess to big endian
    toBE :: ByteSwap a => a -> BE a
    #ifdef ARCH_IS_LITTLE_ENDIAN
    toBE = BE . byteSwap
    #elif ARCH_IS_BIG_ENDIAN
    toBE = BE
    #else
    toBE = BE . (if endianness == LittleEndian then byteSwap else id)
    #endif
    {-# INLINE toBE #-}

    -- | Convert from a big endian value to the cpu endianness
    fromBE :: ByteSwap a => BE a -> a
    #ifdef ARCH_IS_LITTLE_ENDIAN
    fromBE (BE a) = byteSwap a
    #elif ARCH_IS_BIG_ENDIAN
    fromBE (BE a) = a
    #else
    fromBE (BE a) = if endianness == LittleEndian then byteSwap a else a
    #endif
    {-# INLINE fromBE #-}

    -- | Convert a value in cpu endianess to little endian
    toLE :: ByteSwap a => a -> LE a
    #ifdef ARCH_IS_LITTLE_ENDIAN
    toLE = LE
    #elif ARCH_IS_BIG_ENDIAN
    toLE = LE . byteSwap
    #else
    toLE = LE . (if endianness == LittleEndian then id else byteSwap)
    #endif
    {-# INLINE toLE #-}

    -- | Convert from a little endian value to the cpu endianness
    fromLE :: ByteSwap a => LE a -> a
    #ifdef ARCH_IS_LITTLE_ENDIAN
    fromLE (LE a) = a
    #elif ARCH_IS_BIG_ENDIAN
    fromLE (LE a) = byteSwap a
    #else
    fromLE (LE a) = if endianness == LittleEndian then a else byteSwap a
    #endif
    {-# INLINE fromLE #-}

    -- | endianness of the current architecture
    endianness :: Endianness
    #ifdef ARCH_IS_LITTLE_ENDIAN
    endianness = LittleEndian
    #elif ARCH_IS_BIG_ENDIAN
    endianness = BigEndian
    #else
    -- ! ARCH_IS_UNKNOWN_ENDIAN
    endianness = unsafePerformIO $ bytesToEndianness <$> word32ToByte input
      where
        input :: Word32
        input = 0x01020304
    {-# NOINLINE endianness #-}

    word32ToByte :: Word32 -> IO Word8
    word32ToByte word = alloca $ \wordPtr -> do
             poke wordPtr word
             peek (castPtr wordPtr)

    bytesToEndianness :: Word8 -> Endianness
    bytesToEndianness 1 = BigEndian
    bytesToEndianness _ = LittleEndian
    #endif

    -- | Class of types that can be byte-swapped.
    --
    -- e.g. Word16, Word32, Word64
    class ByteSwap a where
        byteSwap :: a -> a
    instance ByteSwap Word16 where
        byteSwap = byteSwap16
    instance ByteSwap Word32 where
        byteSwap = byteSwap32
    instance ByteSwap Word64 where
        byteSwap = byteSwap64