content: {-# LANGUAGE DataKinds #-}
  {-# LANGUAGE RankNTypes #-}
  {-# LANGUAGE KindSignatures #-}
  {-# LANGUAGE OverloadedStrings #-}
  {-# LANGUAGE ScopedTypeVariables #-}
  {-# LANGUAGE TypeOperators #-}
  
  module Language.JavaSpec where
  
  import Control.Concurrent (runInBoundThread)
  import Control.Monad (join)
  import Data.Int
  import qualified Data.Text as Text
  import Data.Text (Text, isInfixOf)
  import Data.Text.Arbitrary ()
  import Data.Text.Encoding (encodeUtf8)
  import Foreign.JNI (getArrayLength, runInAttachedThread)
  import qualified Foreign.JNI.String as JNI
  import Language.Java
  import Test.Hspec
  import Test.Hspec.QuickCheck
  import Test.QuickCheck
  
  spec :: Spec
  spec = around_ (runInBoundThread . runInAttachedThread) $ do
      describe "callStatic" $ do
        it "can call double-returning static functions" $ do
          jstr <- reflect ("1.2345" :: Text)
          callStatic "java.lang.Double" "parseDouble" jstr
            `shouldReturn` (1.2345 :: Double)
  
        it "can call int-returning static functions" $ do
          jstr <- reflect ("12345" :: Text)
          callStatic "java.lang.Integer" "parseInt" jstr
            `shouldReturn` (12345 :: Int32)
  
        it "can call String-returning static functions" $ do
          jstr <-
            callStatic
              "java.lang.Integer"
              "toString"
  
              (12345 :: Int32)
          reify jstr `shouldReturn` ("12345" :: Text)
  
        it "can get static fields" $ do
          getStaticField "java.lang.Math" "PI"
            `shouldReturn` (pi :: Double)
  
        it "can get enum values" $ do
          monday :: J ('Class "java.time.DayOfWeek") <-
            getStaticField "java.time.DayOfWeek" "MONDAY"
          call monday "getValue"
            `shouldReturn` (1 :: Int32)
  
        it "short doesn't under- or overflow" $ do
          maxshort <- reflect (Text.pack (show (maxBound :: Int16)))
          minshort <- reflect (Text.pack (show (minBound :: Int16)))
          callStatic "java.lang.Short" "parseShort" maxshort
            `shouldReturn` (maxBound :: Int16)
          callStatic "java.lang.Short" "parseShort" minshort
            `shouldReturn` (minBound :: Int16)
  
        it "int doesn't under- or overflow" $ do
          maxint <- reflect (Text.pack (show (maxBound :: Int32)))
          minint <- reflect (Text.pack (show (minBound :: Int32)))
          callStatic "java.lang.Integer" "parseInt" maxint
            `shouldReturn` (maxBound :: Int32)
          callStatic "java.lang.Integer" "parseInt" minint
            `shouldReturn` (minBound :: Int32)
  
        it "long doesn't under- or overflow" $ do
          maxlong <- reflect (Text.pack (show (maxBound :: Int64)))
          minlong <- reflect (Text.pack (show (minBound :: Int64)))
          callStatic "java.lang.Long" "parseLong" maxlong
            `shouldReturn` (maxBound :: Int64)
          callStatic "java.lang.Long" "parseLong" minlong
            `shouldReturn` (minBound :: Int64)
  
      describe "newArray" $ do
        it "Supports object arrays" $ do
          jxs <- newArray 10
          getArrayLength (jxs :: J ('Array ('Class "java.lang.Object")))
            `shouldReturn` 10
  
        it "supports generics" $ do
          jxs <- newArray 10
          getArrayLength (jxs ::
              J ('Array ('Class "java.util.List" <> '[ 'Class "java.lang.Long"]))
              )
            `shouldReturn` 10
  
      describe "reify" $ do
        -- Applications need extra conversions if the following doesn't hold.
        it "can get Integer when Long is expected" $ do
          let i = maxBound :: Int32
          j <- new i :: IO (J ('Class "java.lang.Integer"))
          reify (unsafeCast j) `shouldReturn` (fromIntegral i :: Int64)
  
        it "correctly encodes characters outside ASCII range" $ do
          let incorrect :: Text = "आपकी उम्र लंबी हो और आप समृद्ध बने 👋"
          let correct = Text.replace "👋" "🖖" incorrect
          hello <- reflect ("👋" :: Text)
          spock <- reflect ("🖖" :: Text)
          jincorrect <- reflect incorrect
          jcorrect :: JString <- call jincorrect "replaceFirst" hello spock
          reify jcorrect `shouldReturn` correct
  
      describe "strings" $ do
        -- It is known that the current implementation of JNI.String does not support embedded NULL
        prop "correctly convert back and forth from Prelude.String to JNI.String" $
          \s -> (notElem '\NUL' s) ==> (s === (JNI.toChars . JNI.fromChars) s)
        prop "correctly convert back and forth from JNI.String to Prelude.String" $
          \t -> (not $ isInfixOf "\NUL" t) ==>
            let s = JNI.fromByteString $ encodeUtf8 t
            in s === (JNI.fromChars . JNI.toChars) s
  
        prop "correctly convert back and forth from Data.Text to java.lang.String" $
          \(textBefore :: Text) -> ioProperty $ do
            withLocalRef (reflect textBefore) $ \jTextBefore ->
              -- call substring() to force returning a new object
              withLocalRef (call jTextBefore "substring" (0 :: Int32)) $ \(jTextAfter :: JString) -> do
                textAfter :: Text <- reify jTextAfter
                return $ textBefore === textAfter
  
        prop "correctly convert back and forth from java.lang.String to Data.Text" $
          \(textBefore :: Text) -> ioProperty $ do
            withLocalRef (reflect textBefore) $ \jTextBefore ->
              withLocalRef (join $ (reflect . Text.copy) <$> reify jTextBefore) $ \jTextAfter -> do
                isEqual :: Bool <- call jTextBefore "equals" (upcast jTextAfter)
                return $ isEqual === True
  