{-# LANGUAGE OverloadedStrings #-}

module Main (main) where

import Control.Monad
import Data.Binary (encode)
import qualified Data.ByteString.Lazy.Char8 as LBS
import Data.Int
import qualified Data.Text as T
import qualified Data.Vector.Storable as SVec
import Data.Word
import Futhark.Data
import Futhark.Data.Compare
import Futhark.Data.Parser
import Futhark.Data.Reader
import Test.Tasty
import Test.Tasty.HUnit
import Test.Tasty.QuickCheck
import Text.Megaparsec
import Text.Megaparsec.Char (space)

-- A hack to get around the default Eq instance for values, which does
-- not handle NaNs the way we need.
newtype TestValue = TestValue {unTestValue :: Value}

instance Eq TestValue where
  TestValue x == TestValue y =
    null $ compareValues (Tolerance 0) x y

instance Show TestValue where
  show (TestValue x) = show x

instance Arbitrary TestValue where
  arbitrary =
    TestValue
      <$> oneof
        [ mk I8Value,
          mk I16Value,
          mk I32Value,
          mk I64Value,
          mk U8Value,
          mk U16Value,
          mk U32Value,
          mk U64Value,
          mk F32Value,
          mk F64Value,
          mk BoolValue
        ]
    where
      mk f = do
        -- Careful not to create enormous arrays.
        shape <- listOf $ choose (0, 3)
        f (SVec.fromList shape) . SVec.fromList <$> replicateM (product shape) arbitrary

scalar :: (SVec.Storable a) => (Vector Int -> Vector a -> Value) -> a -> Value
scalar f x = f mempty (SVec.singleton x)

readerTests :: TestTree
readerTests =
  testGroup
    "Reader"
    [ test "1" [scalar I32Value 1],
      test "2i32" [scalar I32Value 2],
      test "3i64" [scalar I64Value 3],
      test "[1, 2, 3]" [I32Value (SVec.fromList [3]) (SVec.fromList [1, 2, 3])],
      test
        "2i32 [1, 2, 3]"
        [ scalar I32Value 2,
          I32Value (SVec.fromList [3]) (SVec.fromList [1, 2, 3])
        ],
      test
        "[[1,-- comment\n 2], [3,4]]"
        [I32Value (SVec.fromList [2, 2]) (SVec.fromList [1, 2, 3, 4])],
      test
        "b\STX\SOH i32\SOH\NUL\NUL\NUL\NUL\NUL\NUL\NUL\154\238\179u"
        [I32Value (SVec.fromList [1]) (SVec.fromList [1974726298])],
      test
        "0.9597767951851629f64\nb\STX\SOH i32\SOH\NUL\NUL\NUL\NUL\NUL\NUL\NUL\DC1\ETX\NUL\NUL2"
        [ scalar F64Value 0.9597767951851629,
          I32Value (SVec.fromList [1]) (SVec.fromList [785]),
          scalar I32Value 2
        ],
      testProperty "read random binary data" $
        \v ->
          (map TestValue <$> readValues (encode $ unTestValue v))
            == Just [v],
      testProperty "read random text data" $
        \v ->
          (map TestValue <$> readValues (LBS.pack $ T.unpack $ valueText $ unTestValue v))
            == Just [v]
    ]
  where
    maxlen = 40

    shorten s
      | length s < maxlen = s
      | otherwise = take (maxlen - 3) s <> "..."

    test s x =
      testCase ("Reading " <> shorten (show s)) $
        readValues s @?= Just x

parserTests :: TestTree
parserTests =
  testGroup
    "Parser"
    [ test "1" $ scalar I32Value 1,
      negtest "_1",
      test "2i32" $ scalar I32Value 2,
      test "3i64" $ scalar I64Value 3,
      test "-2_3i32" $ scalar I32Value (-23),
      test "0b1_0_0_1" $ scalar I32Value 9,
      test "0x12_34" $ scalar I32Value 0x1234,
      test "3.1_4" $ scalar F64Value 3.14,
      test "1.0" $ scalar F64Value 1,
      negtest "_1.0",
      test "2f32" $ scalar F32Value 2,
      test "2f16" $ scalar F16Value 2,
      test "3.0f64" $ scalar F64Value 3.0,
      test "3.1f64" $ scalar F64Value 3.1,
      test "3.1_e-2f64" $ scalar F64Value 3.1e-2,
      test "f32.nan" $ scalar F32Value (0 / 0),
      test "f16.nan" $ scalar F16Value (0 / 0),
      test "f64.nan" $ scalar F64Value (0 / 0),
      test "f64.inf" $ scalar F64Value (1 / 0),
      test "-f64.inf" $ scalar F64Value (-1 / 0),
      test "true" $ scalar BoolValue True,
      test "false" $ scalar BoolValue False,
      test "\"foo\"" $ putValue1 ("foo" :: T.Text),
      test "\"\\\"foo\\\"\"" $ putValue1 ("\"foo\"" :: T.Text),
      negtest "tr_ue",
      test "[1,2,3,]" $ I32Value (SVec.fromList [3]) (SVec.fromList [1, 2, 3]),
      negtest "[1i32, 2i8]",
      test "[1i8, 0]" $ I8Value (SVec.fromList [2]) (SVec.fromList [1, 0]),
      negtest "[0, 1i8]",
      testProperty "parse random data" $
        \v ->
          (TestValue <$> parseMaybe (parseValue space) (valueText $ unTestValue v))
            == Just v
    ]
  where
    test s x =
      testCase ("Parsing " <> show s) $
        (TestValue <$> runParser (parseValue space <* eof) "" s) @?= Right (TestValue x)
    negtest s =
      testCase ("Parsing " <> show s) $
        either
          (const Nothing)
          Just
          (TestValue <$> runParser (parseValue space <* eof) "" s)
          @?= Nothing

getValueTests :: TestTree
getValueTests =
  testGroup
    "GetValue"
    [ test (putValue1 (1 :: Int32)) (Nothing :: Maybe [Word8]),
      test (putValue1 (1 :: Int32)) (Just (1 :: Int32))
    ]
  where
    test v expected =
      testCase (unwords ["getValue", v', "==", show expected]) $
        getValue v @?= expected
      where
        v' = T.unpack (valueText v)

allTests :: TestTree
allTests =
  testGroup "" [readerTests, parserTests, getValueTests]

main :: IO ()
main = defaultMain allTests
