module Data.Byte
    ( ByteOrder (..)
    , hostByteOrder
    , networkByteOrder
    , byteShow
    , word8ToChar
    , flipEndian
    , toWord8s
    , wordConcat )
    where


import Data.Bits
import Data.Char
import Data.Word
import System.Info
import System.IO.Unsafe
import Control.Exception
import Foreign.Marshal.Utils ( with )
import Foreign.Storable ( peekByteOff )


data ByteOrder = BigEndian | LittleEndian
                 deriving ( Eq, Show, Read )


hostByteOrder :: ByteOrder
hostByteOrder = let test = (wordConcat [1,2]) :: Word16
                    answer = (unsafePerformIO $ with test firstByte) :: Word8 in
                case answer of
                  1 -> LittleEndian
                  2 -> BigEndian
                  otherwise -> throw $ ErrorCall $ "Unexpected result when checking byte order"
    where firstByte = (flip peekByteOff) 0

networkByteOrder :: ByteOrder
networkByteOrder = BigEndian


changeByteOrder :: Integral a => ByteOrder -> ByteOrder -> a -> a
changeByteOrder bo1 bo2 x = if bo1 == bo2
                            then x
                            else flipEndian x


byteShow :: Integral a => a -> String
byteShow = (map word8ToChar) . toWord8s


word8ToChar :: Word8 -> Char
word8ToChar = chr . fromEnum


flipEndian :: Integral a => a -> a
flipEndian = wordConcat . reverse . toWord8s



-- Returns a list of Word8s in little-endian byte order
toWord8s :: Integral a => a -> [Word8]
toWord8s x = let n = x `div` (2^8)
                 d = x `mod` (2^8) in
             case n of
               0 -> [toWord8 d]
               otherwise -> (toWord8 d) : (toWord8s n)
    where toWord8 = fromInteger . toInteger


-- Concats a list of Word8s in little-endian byte order.
-- For big-endian byte order reverse the Word8s first.
wordConcat :: Integral a => [Word8] -> a
wordConcat = sumBytes . shiftBytes . expand
    where shiftBytes = zipWith (flip shift) [0,8..]
          sumBytes = fromInteger . sum
          expand = (map toInteger)


