module Main where
import Test.QuickCheck
import qualified Data.ByteString as BS
import Data.Word
import Data.CBOR
import Data.CBOR.Util
import Data.Binary.CBOR
import Test.Framework
-- import Test.QuickCheck.Property
import Test.Framework.Providers.QuickCheck2
import Data.Binary.Get (runGet)
import Data.Binary.Put (runPut)
import Control.Applicative
import Data.Monoid


main :: IO ()
main = defaultMain tests
-- main = defaultMainWithOpts tests runnerOptions

runnerOptions :: RunnerOptions' Maybe
runnerOptions = mempty {
    ropt_test_options = Just testOptions
  }

testOptions :: TestOptions' Maybe
testOptions = mempty {
      topt_maximum_generated_tests = Just 5
    , topt_maximum_unsuitable_generated_tests = Just 1
    , topt_maximum_test_size = Just 1
    , topt_maximum_test_depth = Just 1
  }

tests :: [Test]
tests = [testGroup "Group1" [
    testProperty "round_trip" prop_roundtrip
  ]]

prop_roundtrip :: CBOR -> Bool
prop_roundtrip x = x == runGet getCBOR (runPut $ putCBOR x)

instance Arbitrary HalfFloat where
  arbitrary = HF <$> arbitrary
--instance Arbitrary BS.ByteString where
--  arbitrary :: Gen BS.ByteString
--  arbitrary = 

instance Arbitrary CBOR where
  arbitrary = sized sizedCBOR

sizedCBOR :: Int -> Gen CBOR
sizedCBOR 0 = 
  oneof [
      CBOR_UInt <$> choose (0, toInt (maxBound :: Word64))
      , CBOR_SInt <$> (-) (-1) <$> choose (0, toInt (maxBound :: Word64))
      , CBOR_BS <$> arbitraryByteString
      , CBOR_TS <$> arbitraryTextString
      , CBOR_HalfFloat <$> arbitrary
      , CBOR_Float <$> arbitrary
      , CBOR_Double <$> arbitrary
      , return CBOR_NULL
      , return CBOR_Undefined
      , CBOR_Reserved <$> oneof [return 28,return 29,return 30]
      , CBOR_Unassigned <$> oneof (map return $ [0..19])
      , return CBOR_True
      , return CBOR_False
      , CBOR_Byte <$> arbitrary
  ]
sizedCBOR n = 
  oneof [
        CBOR_UInt <$> choose (0, toInt (maxBound :: Word64))
      , CBOR_SInt <$> (-) (-1) <$> choose (0, toInt (maxBound :: Word64))
      , CBOR_BS <$> arbitraryByteString
      , CBOR_TS <$> arbitraryTextString
      , CBOR_Array <$> listOf1 (sizedCBOR $ n `div` 2)
      , CBOR_Map <$> listOf1 ((,) <$> (sizedCBOR $ n `div` 2) `suchThat` allowedKeyTypes <*> (sizedCBOR $ n `div` 2))
      , CBOR_Tag <$> choose (0, toInt (maxBound :: Word64)) <*> (sizedCBOR $ n `div` 2)
      , CBOR_HalfFloat <$> arbitrary
      , CBOR_Float <$> arbitrary
      , CBOR_Double <$> arbitrary
      , return CBOR_NULL
      , return CBOR_Undefined
      , CBOR_Reserved <$> oneof [return 28,return 29,return 30]
      , CBOR_Unassigned <$> oneof (map return $ [0..19])
      , return CBOR_True
      , return CBOR_False
      , CBOR_Byte <$> arbitrary
    ]

allowedKeyTypes :: CBOR -> Bool
allowedKeyTypes x = case x of
  (CBOR_UInt _) -> True
  (CBOR_SInt _) -> True
  (CBOR_BS _) -> True
  (CBOR_TS _) -> True
  (CBOR_Tag _ y) -> allowedKeyTypes y
  (CBOR_HalfFloat _) -> True
  (CBOR_Float _) -> True
  (CBOR_Double _) -> True
  (CBOR_Reserved _) -> True
  (CBOR_Unassigned _) -> True
  CBOR_True -> True
  CBOR_False -> True
  CBOR_NULL -> True
  (CBOR_Byte _) -> True
  _ -> False

arbitraryByteString :: Gen BS.ByteString
arbitraryByteString = BS.pack <$> listOf1 arbitrary

arbitraryTextString :: Gen BS.ByteString
arbitraryTextString = BS.pack <$> listOf1 (choose (0, 0x79))
  -- Technically it can be higher than this, but then it must be encoded as multiple bytes 