{-# LANGUAGE CPP #-}
{-# LANGUAGE MagicHash #-}
{-# LANGUAGE UnboxedTuples #-}
module Main where

#include "MachDeps.h"

import GHC.Ptr(Ptr(..), nullPtr, plusPtr, minusPtr)
import GHC.Stable(
  StablePtr(..), castStablePtrToPtr, castPtrToStablePtr, newStablePtr)
import GHC.Exts
import Data.Char(ord)
import GHC.Int
import GHC.Word

assertEqual :: (Show a, Eq a) => String -> a -> a -> IO ()
assertEqual msg a b
  | a /= b = putStrLn (msg ++ " " ++ show a ++ " /= " ++ show b)
  | otherwise = return ()

readBytes :: MutableByteArray# s -> State# s -> Int# -> (# State# s, [Word8] #)
readBytes marr s0 len = go s0 len []
 where
  go s 0# bs = (# s, bs #)
  go s i  bs = case readWord8Array# marr (i -# 1#) s of
    (# s', b #) -> go s' (i -# 1#) (W8# b : bs)

indexBytes :: ByteArray# -> Int# -> [Word8]
indexBytes arr len  =
  [W8# (indexWord8Array# arr i) | I# i <- [0..I# len - 1]]

fillByteArray :: MutableByteArray# s -> Int# -> Word8 -> State# s -> State# s
fillByteArray arr len (W8# a) = go len
 where
  go 0# s = s
  go i s = go (i -# 1#) (writeWord8Array# arr (i -# 1#) a s)

test :: (Eq a, Show a)
  => String
  -> (ByteArray# -> Int# -> a)
  -> (MutableByteArray# RealWorld -> Int# -> State# RealWorld
      -> (# State# RealWorld, a #))
  -> (MutableByteArray# RealWorld -> Int# -> a -> State# RealWorld
      -> State# RealWorld)
  -> a
  -> [Word8]
  -> Int
  -> IO ()
test name index read write val valBytes len = do
  putStrLn name
  mapM_ testAtOffset [0..16]
 where
  arrLen :: Int#
  arrLen = 24#

  fillerByte :: Word8
  fillerByte = 0x34

  expectedArrayBytes :: Int -> [Word8]
  expectedArrayBytes offset =
       replicate offset fillerByte
    ++ valBytes
    ++ replicate (fromIntegral $ I# arrLen - len - offset) fillerByte

  testAtOffset :: Int -> IO ()
  testAtOffset offset@(I# offset#) = runRW# (\s0 -> let
    (# s1, marr #) = newByteArray# arrLen s0
    s2 = fillByteArray marr arrLen fillerByte s1
    s3 = write marr offset# val s2
    (# s4, actual0 #) = read marr offset# s3
    (# s5, actualBytes0 #) = readBytes marr s4 arrLen
    (# _, arr #) = unsafeFreezeByteArray# marr s5
    actual1 = index arr offset#
    actualBytes1 = indexBytes arr arrLen
   in do
       assertEqual "actual val 0" actual0 val
       assertEqual "actual val 1" actual1 val
       assertEqual "actualBytes0 indexed" actualBytes0 (expectedArrayBytes offset)
       assertEqual "actualButes1 indexed" actualBytes1 (expectedArrayBytes offset)
   )

intToBytes :: Word -> Int -> [Word8]
intToBytes (W# val0) (I# len0) = let
    result = go val0 len0
    go v 0# = []
    go v len =
      W8# (wordToWord8# v) : go (v `uncheckedShiftRL#` 8#) (len -# 1#)
  in
#if defined(WORDS_BIGENDIAN)
    reverse result
#else
    result
#endif

testIntArray :: (Eq a, Show a, Integral a, Num a)
  => String
  -> (ByteArray# -> Int# -> a)
  -> (MutableByteArray# RealWorld -> Int# -> State# RealWorld
      -> (# State# RealWorld, a #))
  -> (MutableByteArray# RealWorld -> Int# -> a -> State# RealWorld
      -> State# RealWorld)
  -> a
  -> Int
  -> IO ()
testIntArray name0 index read write val0 len = do
  doOne (name0 ++ " positive") val0
  doOne (name0 ++ " negative") (negate val0)
 where
  doOne name val = test name index read write
    val (intToBytes (fromIntegral val) len) len

testWordArray :: (Eq a, Show a, Integral a)
  => String
  -> (ByteArray# -> Int# -> a)
  -> (MutableByteArray# RealWorld -> Int# -> State# RealWorld
        -> (# State# RealWorld, a #))
  -> (MutableByteArray# RealWorld -> Int# -> a -> State# RealWorld
        -> State# RealWorld)
  -> a
  -> Int
  -> IO ()
testWordArray name index read write val len =
  test name index read write
    val (intToBytes (fromIntegral val) len) len

wordSizeInBytes :: Int
wordSizeInBytes = WORD_SIZE_IN_BITS `div` 8

int :: Int
int
  | WORD_SIZE_IN_BITS == 32 = 12345678
  | otherwise = 1234567890123

word :: Word
word = fromIntegral int

float :: Float
float = 123.456789

-- Test pattern generated by this python code:
-- >>> import struct
-- >>> import binascii
-- >>> binascii.hexlify(struct.pack('>f', 123.456789))
floatBytes :: Word
floatBytes = 0x42f6e9e0

double :: Double
double = 123.45678901234

-- Test pattern generated by this python code:
-- >>> import struct
-- >>> import binascii
-- >>> binascii.hexlify(struct.pack('>d', 123.45678901234))
doubleBytes :: Word
doubleBytes = 0x405edd3c07fb4b09

main :: IO ()
main = do
  testIntArray "Int8#"
    (\arr i -> I8# (indexInt8Array# arr i))
    (\arr i s -> case readInt8Array# arr i s of (# s', a #) -> (# s', I8# a #))
    (\arr i (I8# a) s -> writeInt8Array# arr i a s)
    123 1
  testIntArray "Int16#"
    (\arr i -> I16# (indexWord8ArrayAsInt16# arr i))
    (\arr i s -> case readWord8ArrayAsInt16# arr i s of (# s', a #) -> (# s', I16# a #))
    (\arr i (I16# a) s -> writeWord8ArrayAsInt16# arr i a s)
    12345 2
  testIntArray "Int32#"
    (\arr i -> I32# (indexWord8ArrayAsInt32# arr i))
    (\arr i s -> case readWord8ArrayAsInt32# arr i s of (# s', a #) -> (# s', I32# a #))
    (\arr i (I32# a) s -> writeWord8ArrayAsInt32# arr i a s)
    12345678 4
  testIntArray "Int64#"
    (\arr i -> I64# (indexWord8ArrayAsInt64# arr i))
    (\arr i s -> case readWord8ArrayAsInt64# arr i s of (# s', a #) -> (# s', I64# a #))
    (\arr i (I64# a) s -> writeWord8ArrayAsInt64# arr i a s)
    1234567890123 8
  testIntArray "Int#"
    (\arr i -> I# (indexWord8ArrayAsInt# arr i))
    (\arr i s -> case readWord8ArrayAsInt# arr i s of (# s', a #) -> (# s', I# a #))
    (\arr i (I# a) s -> writeWord8ArrayAsInt# arr i a s)
    int wordSizeInBytes

  testWordArray "Word8#"
    (\arr i -> W8# (indexWord8Array# arr i))
    (\arr i s -> case readWord8Array# arr i s of (# s', a #) -> (# s', W8# a #))
    (\arr i (W8# a) s -> writeWord8Array# arr i a s)
    123 1
  testWordArray "Word16#"
    (\arr i -> W16# (indexWord8ArrayAsWord16# arr i))
    (\arr i s -> case readWord8ArrayAsWord16# arr i s of (# s', a #) -> (# s', W16# a #))
    (\arr i (W16# a) s -> writeWord8ArrayAsWord16# arr i a s)
    12345 2
  testWordArray "Word32#"
    (\arr i -> W32# (indexWord8ArrayAsWord32# arr i))
    (\arr i s -> case readWord8ArrayAsWord32# arr i s of (# s', a #) -> (# s', W32# a #))
    (\arr i (W32# a) s -> writeWord8ArrayAsWord32# arr i a s)
    12345678 4
  testWordArray "Word64#"
    (\arr i -> W64# (indexWord8ArrayAsWord64# arr i))
    (\arr i s -> case readWord8ArrayAsWord64# arr i s of (# s', a #) -> (# s', W64# a #))
    (\arr i (W64# a) s -> writeWord8ArrayAsWord64# arr i a s)
    1234567890123 8
  testWordArray "Word#"
    (\arr i -> W# (indexWord8ArrayAsWord# arr i))
    (\arr i s -> case readWord8ArrayAsWord# arr i s of (# s', a #) -> (# s', W# a #))
    (\arr i (W# a) s -> writeWord8ArrayAsWord# arr i a s)
    word wordSizeInBytes

  test
    "Char#"
    (\arr i -> C# (indexWord8ArrayAsChar# arr i))
    (\arr i s ->
        case readWord8ArrayAsChar# arr i s of (# s', a #) -> (# s', C# a #))
    (\arr i (C# a) s -> writeWord8ArrayAsChar# arr i a s)
    'z'
    [fromIntegral $ ord 'z']
    1
  test
    "WideChar#"
    (\arr i -> C# (indexWord8ArrayAsWideChar# arr i))
    (\arr i s ->
        case readWord8ArrayAsWideChar# arr i s of (# s', a #) -> (# s', C# a #))
    (\arr i (C# a) s -> writeWord8ArrayAsWideChar# arr i a s)
    '𠜎'  -- See http://www.i18nguy.com/unicode/supplementary-test.html
    (intToBytes (fromIntegral $ ord '𠜎') 4)
    4
  test
    "Addr#"
    (\arr i -> Ptr (indexWord8ArrayAsAddr# arr i))
    (\arr i s ->
        case readWord8ArrayAsAddr# arr i s of (# s', a #) -> (# s', Ptr a #))
    (\arr i (Ptr a) s -> writeWord8ArrayAsAddr# arr i a s)
    (nullPtr `plusPtr` int)
    (intToBytes word wordSizeInBytes)
    wordSizeInBytes

  stablePtr <- newStablePtr ()
  test
    "StablePtr#"
    (\arr i ->
        castStablePtrToPtr (StablePtr (indexWord8ArrayAsStablePtr# arr i)))
    (\arr i s -> case readWord8ArrayAsStablePtr# arr i s of
                   (# s', a #) -> (# s', castStablePtrToPtr (StablePtr a) #))
    (\arr i p s -> case castPtrToStablePtr p of
                     (StablePtr a) -> writeWord8ArrayAsStablePtr# arr i a s)
    (castStablePtrToPtr stablePtr)
    (intToBytes (fromIntegral $ castStablePtrToPtr stablePtr `minusPtr` nullPtr)
                wordSizeInBytes)
    wordSizeInBytes

  test
    "Float#"
    (\arr i -> F# (indexWord8ArrayAsFloat# arr i))
    (\arr i s ->
        case readWord8ArrayAsFloat# arr i s of (# s', a #) -> (# s', F# a #))
    (\arr i (F# a) s -> writeWord8ArrayAsFloat# arr i a s)
    float
    (intToBytes floatBytes 4)
    4
  test
    "Double#"
    (\arr i -> D# (indexWord8ArrayAsDouble# arr i))
    (\arr i s ->
        case readWord8ArrayAsDouble# arr i s of (# s', a #) -> (# s', D# a #))
    (\arr i (D# a) s -> writeWord8ArrayAsDouble# arr i a s)
    double
    (intToBytes doubleBytes 8)
    8
