{-# LANGUAGE
  CPP,
  OverloadedStrings,
  RecordWildCards,
  ScopedTypeVariables
  #-}
module LLVM.Test.Target where

import           Test.Tasty
import           Test.Tasty.HUnit
import           Test.Tasty.QuickCheck
import           Test.QuickCheck

import           Control.Applicative
import           Control.Monad
import           Control.Monad.IO.Class
import qualified Data.ByteString.Char8 as ByteString
import           Data.Char
import           Data.Map (Map)
import           Data.Monoid (mempty)
import           Foreign.C.String

import qualified LLVM.CodeGenOpt as CodeGenOpt
import qualified LLVM.CodeModel as CodeModel
import           LLVM.Context
import           LLVM.Internal.Coding
import           LLVM.Internal.DecodeAST
import           LLVM.Internal.EncodeAST
import qualified LLVM.Relocation as Reloc
import           LLVM.Target
import           LLVM.Target.LibraryFunction
import           LLVM.Target.Options

instance Arbitrary FloatABI where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary FloatingPointOperationFusionMode where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary ThreadModel where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary EABIVersion where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary DebuggerKind where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary FloatingPointDenormalMode where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary ExceptionHandling where
  arbitrary = elements [minBound .. maxBound]

instance Arbitrary Options where
  arbitrary = do
    printMachineCode <- arbitrary
    unsafeFloatingPointMath <- arbitrary
    noInfinitiesFloatingPointMath <- arbitrary
    noNaNsFloatingPointMath <- arbitrary
    noTrappingFloatingPointMath <- arbitrary
    noSignedZeroesFloatingPointMath <- arbitrary
    honorSignDependentRoundingFloatingPointMathOption <- arbitrary
    noZerosInBSS <- arbitrary
    guaranteedTailCallOptimization <- arbitrary
    stackSymbolOrdering <- arbitrary
    enableFastInstructionSelection <- arbitrary
    useInitArray <- arbitrary
    disableIntegratedAssembler <- arbitrary
    compressDebugSections <- arbitrary
    relaxELFRelocations <- arbitrary
    functionSections <- arbitrary
    dataSections <- arbitrary
    uniqueSectionNames <- arbitrary
    trapUnreachable <- arbitrary
    emulatedThreadLocalStorage <- arbitrary
    enableInterProceduralRegisterAllocation <- arbitrary
    stackAlignmentOverride <- arbitrary
    floatABIType <- arbitrary
    allowFloatingPointOperationFusion <- arbitrary
    threadModel <- arbitrary
    eabiVersion <- arbitrary
    debuggerTuning <- arbitrary
    floatingPointDenormalMode <- arbitrary
    exceptionModel <- arbitrary
    machineCodeOptions <- arbitrary
    return Options { .. }

instance Arbitrary MachineCodeOptions where
  arbitrary = do
    sanitizeAddresses <- arbitrary
    relaxAll <- arbitrary
    noExecutableStack <- arbitrary
    fatalWarnings <- arbitrary
    noWarnings <- arbitrary
    noDeprecatedWarning <- arbitrary
    saveTemporaryLabels <- arbitrary
    useDwarfDirectory <- arbitrary
    incrementalLinkerCompatible <- arbitrary
    pieCopyRelocations <- arbitrary
    showMachineCodeEncoding <- arbitrary
    showMachineCodeInstructions <- arbitrary
    verboseAssembly <- arbitrary
    preserveComentsInAssembly <- arbitrary
    return MachineCodeOptions { .. }

instance Arbitrary DebugCompressionType where
  arbitrary = elements [CompressNone, CompressGNU, CompressZ]

arbitraryASCIIString :: Gen String
#if MIN_VERSION_QuickCheck(2,10,0)
arbitraryASCIIString = getASCIIString <$> arbitrary
#else
arbitraryASCIIString = arbitrary
#endif

instance Arbitrary CPUFeature where
  arbitrary = CPUFeature . ByteString.pack <$>
    suchThat arbitraryASCIIString (\s -> not (null s) && all isAlphaNum s)

tests = testGroup "Target" [
  testGroup "Options" [
     testProperty "basic" $ \options -> ioProperty $ do
       withTargetOptions $ \to -> do
         pokeTargetOptions options to
         options' <- peekTargetOptions to
         return $ options === options',
     testProperty "target machine" $ \options -> ioProperty $ do
         withTargetOptions $ \to -> do
           pokeTargetOptions options to
           let triple = "i386-linux-gnu"
               cpu = ""
               features = mempty
               reloc = Reloc.Default
               codeModel = CodeModel.Default
               codeGenOpt = CodeGenOpt.Default
           (target, _) <- lookupTarget Nothing triple
           withTargetMachine target triple cpu features to reloc codeModel codeGenOpt $ \tm -> do
             options' <- peekTargetOptions =<< targetMachineOptions tm
             return $ options === options'
   ],
  testGroup "LibraryFunction" [
    testGroup "set-get" [
       testCase (show lf) $ do
         triple <- getDefaultTargetTriple
         withTargetLibraryInfo triple $ \tli -> do
           setLibraryFunctionAvailableWithName tli lf "foo"
           nm <- getLibraryFunctionName tli lf
           nm @?= "foo"
       | lf <- [minBound, maxBound]
     ],
    testCase "get" $ do
      triple <- getDefaultTargetTriple
      withTargetLibraryInfo triple $ \tli -> do
        lf <- getLibraryFunction tli "printf"
        lf @?= Just LF__printf
   ],
  testCase "Host" $ do
    features <- getHostCPUFeatures
    return (),
  testGroup "CPUFeature" [
    testProperty "roundtrip" $ \cpuFeatures -> ioProperty $
        withContext $ \context -> runEncodeAST context $ do
          encodedFeatures :: CString <- (encodeM cpuFeatures)
          decodedFeatures :: Map CPUFeature Bool <- liftIO $ runDecodeAST (decodeM encodedFeatures)
          return (cpuFeatures == decodedFeatures)

   ]
 ]