# LANGUAGE FlexibleInstances #
import Test.Tasty.QuickCheck
import Test.Tasty

import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B

import Control.Applicative
import Control.Monad

import Data.Git.Storage.Object
import Data.Git.Storage.Loose
import Data.Git.Ref
import Data.Git.Revision
import Data.Git.Types
import Data.Hourglass

import Data.Maybe

-- for arbitrary instance to generate only data that are writable
-- to disk. i.e. no deltas.
data ObjNoDelta hash = ObjNoDelta (Object hash)
    deriving (Eq)

instance Show (ObjNoDelta hash) where
    show (ObjNoDelta o) = show o

arbitraryBS size = B.pack . map fromIntegral <$> replicateM size (choose (0,255) :: Gen Int)
arbitraryBSno0 size = B.pack . map fromIntegral <$> replicateM size (choose (1,255) :: Gen Int)
arbitraryBSasciiNoSpace size = B.pack . map fromIntegral <$> replicateM size (choose (0x21,0x7f) :: Gen Int)
arbitraryBSascii size = B.pack . map fromIntegral <$> replicateM size (choose (0x20,0x7f) :: Gen Int)
arbitraryBSnoangle size = B.pack . map fromIntegral <$> replicateM size (choose (0x40,0x7f) :: Gen Int)

arbitraryEntname size = entName . B.pack . map fromIntegral <$> replicateM size range
  where range :: Gen Int
  remove 0x2f ( slash )
                      , choose (0x30, 0x7f)
                      ]

arbitraryRef :: HashAlgorithm hash => hash -> Gen (Ref hash)
arbitraryRef alg = fromBinary <$> arbitraryBS (hashDigestSize alg)

instance HashAlgorithm hash => Arbitrary (Ref hash) where
    arbitrary = arbitraryRef (error "alg")

arbitraryMsg = arbitraryBSno0 1
arbitraryLazy = L.fromChunks . (:[]) <$> arbitraryBS 40

arbitraryRefList :: Gen [Ref SHA1]
arbitraryRefList = replicateM 2 arbitrary

arbitraryEnt :: Gen (TreeEnt SHA1)
arbitraryEnt = liftM3 (,,) arbitrary (arbitraryEntname 23) arbitrary
arbitraryEnts = choose (1,2) >>= \i -> replicateM i arbitraryEnt

instance Arbitrary TimezoneOffset where
    arbitrary = TimezoneOffset <$> choose (-11*60, 12*60)
instance Arbitrary Elapsed where
    arbitrary = Elapsed . Seconds <$> choose (0,2^32-1)
instance Arbitrary GitTime where
    arbitrary = GitTime <$> arbitrary <*> arbitrary
instance Arbitrary ModePerm where
    arbitrary = ModePerm <$> elements [ 0o644, 0o664, 0o755, 0 ]
instance Arbitrary RevModifier where
    arbitrary = oneof
        [ RevModParent . getPositive <$> arbitrary
        , RevModParentFirstN . getPositive <$> arbitrary
        , RevModAtType <$> arbitraryType
        , RevModAtDate <$> arbitraryDate
        --, RevModAtN . getPositive <$> arbitrary
        ]

arbitraryDate = elements ["yesterday","29-Jan-1982","5 days ago"]
arbitraryType = elements ["commit","tree"]

instance Arbitrary Revision where
    arbitrary = do
        s   <- choose (1,40) >>= flip replicateM (elements ['a'..'z'])
        rms <- choose (1,4) >>= flip replicateM arbitrary
        return $ Revision s rms

arbitraryName = liftM3 Person (arbitraryBSnoangle 16)
                              (arbitraryBSnoangle 16)
                              arbitrary

arbitraryObjTypeNoDelta = oneof [return TypeTree,return TypeBlob,return TypeCommit,return TypeTag]

arbitrarySmallList = frequency [ (2, return []), (1, resize 3 arbitrary) ]

instance Arbitrary (Commit SHA1) where
    arbitrary = Commit <$> arbitrary <*> arbitraryRefList <*> arbitraryName <*> arbitraryName <*> return Nothing <*> arbitrarySmallList <*> arbitraryMsg

instance Arbitrary CommitExtra where
    arbitrary = CommitExtra <$> arbitraryBSasciiNoSpace 80 <*> arbitraryMsg

instance Arbitrary (Tree SHA1) where
    arbitrary = Tree <$> arbitraryEnts

instance Arbitrary (Blob SHA1) where
    arbitrary = Blob <$> arbitraryLazy

instance Arbitrary (Tag SHA1) where
    arbitrary = Tag <$> arbitrary <*> arbitraryObjTypeNoDelta <*> arbitraryBSascii 20 <*> arbitraryName <*> arbitraryMsg

instance Arbitrary (ObjNoDelta SHA1) where
    arbitrary = ObjNoDelta <$> oneof
        [ toObject <$> (arbitrary :: Gen (Commit SHA1))
        , toObject <$> (arbitrary :: Gen (Tree SHA1))
        , toObject <$> (arbitrary :: Gen (Blob SHA1))
        , toObject <$> (arbitrary :: Gen (Tag SHA1))
        ]

prop_object_marshalling_id :: ObjNoDelta SHA1 -> Bool
prop_object_marshalling_id (ObjNoDelta obj) =
    let unmarshall = looseUnmarshall :: L.ByteString -> Object SHA1
     in obj `assertEq` (unmarshall $ looseMarshall obj)
    where assertEq a b
            | show a == show b    = True
            | otherwise = error ("not equal:\n"  ++ show a ++ "\ngot: " ++ show b)

refTests =
    [ testProperty "hexadecimal" (marshEqual (fromHex . toHex :: Ref SHA1 -> Ref SHA1))
    , testProperty "binary" (marshEqual (fromBinary . toBinary :: Ref SHA1 -> Ref SHA1))
    , testProperty "ref" $ marshEqual (fromString . show :: Revision -> Revision)
    ]
    where
        marshEqual t ref = ref `assertEq` t ref
        assertEq a b
            | a == b    = True
            | otherwise = error ("expecting: " ++ show a ++ " got: " ++ show b)

objTests =
    [ testProperty "unmarshall.marshall==id" prop_object_marshalling_id
    ]

main = defaultMain $ testGroup "hit"
    [ testGroup "ref marshalling" refTests
    , testGroup "object marshalling" objTests
    ]

 for arbitrary instance to generate only data that are writable
 to disk. i.e. no deltas.
, RevModAtN . getPositive <$> arbitrary
# LANGUAGE FlexibleInstances #
import Test.Tasty.QuickCheck
import Test.Tasty

import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString as B

import Control.Applicative
import Control.Monad

import Data.Git.Storage.Object
import Data.Git.Storage.Loose
import Data.Git.Ref
import Data.Git.Revision
import Data.Git.Types
import Data.Hourglass

import Data.Maybe

data ObjNoDelta hash = ObjNoDelta (Object hash)
    deriving (Eq)

instance Show (ObjNoDelta hash) where
    show (ObjNoDelta o) = show o

arbitraryBS size = B.pack . map fromIntegral <$> replicateM size (choose (0,255) :: Gen Int)
arbitraryBSno0 size = B.pack . map fromIntegral <$> replicateM size (choose (1,255) :: Gen Int)
arbitraryBSasciiNoSpace size = B.pack . map fromIntegral <$> replicateM size (choose (0x21,0x7f) :: Gen Int)
arbitraryBSascii size = B.pack . map fromIntegral <$> replicateM size (choose (0x20,0x7f) :: Gen Int)
arbitraryBSnoangle size = B.pack . map fromIntegral <$> replicateM size (choose (0x40,0x7f) :: Gen Int)

arbitraryEntname size = entName . B.pack . map fromIntegral <$> replicateM size range
  where range :: Gen Int
  remove 0x2f ( slash )
                      , choose (0x30, 0x7f)
                      ]

arbitraryRef :: HashAlgorithm hash => hash -> Gen (Ref hash)
arbitraryRef alg = fromBinary <$> arbitraryBS (hashDigestSize alg)

instance HashAlgorithm hash => Arbitrary (Ref hash) where
    arbitrary = arbitraryRef (error "alg")

arbitraryMsg = arbitraryBSno0 1
arbitraryLazy = L.fromChunks . (:[]) <$> arbitraryBS 40

arbitraryRefList :: Gen [Ref SHA1]
arbitraryRefList = replicateM 2 arbitrary

arbitraryEnt :: Gen (TreeEnt SHA1)
arbitraryEnt = liftM3 (,,) arbitrary (arbitraryEntname 23) arbitrary
arbitraryEnts = choose (1,2) >>= \i -> replicateM i arbitraryEnt

instance Arbitrary TimezoneOffset where
    arbitrary = TimezoneOffset <$> choose (-11*60, 12*60)
instance Arbitrary Elapsed where
    arbitrary = Elapsed . Seconds <$> choose (0,2^32-1)
instance Arbitrary GitTime where
    arbitrary = GitTime <$> arbitrary <*> arbitrary
instance Arbitrary ModePerm where
    arbitrary = ModePerm <$> elements [ 0o644, 0o664, 0o755, 0 ]
instance Arbitrary RevModifier where
    arbitrary = oneof
        [ RevModParent . getPositive <$> arbitrary
        , RevModParentFirstN . getPositive <$> arbitrary
        , RevModAtType <$> arbitraryType
        , RevModAtDate <$> arbitraryDate
        ]

arbitraryDate = elements ["yesterday","29-Jan-1982","5 days ago"]
arbitraryType = elements ["commit","tree"]

instance Arbitrary Revision where
    arbitrary = do
        s   <- choose (1,40) >>= flip replicateM (elements ['a'..'z'])
        rms <- choose (1,4) >>= flip replicateM arbitrary
        return $ Revision s rms

arbitraryName = liftM3 Person (arbitraryBSnoangle 16)
                              (arbitraryBSnoangle 16)
                              arbitrary

arbitraryObjTypeNoDelta = oneof [return TypeTree,return TypeBlob,return TypeCommit,return TypeTag]

arbitrarySmallList = frequency [ (2, return []), (1, resize 3 arbitrary) ]

instance Arbitrary (Commit SHA1) where
    arbitrary = Commit <$> arbitrary <*> arbitraryRefList <*> arbitraryName <*> arbitraryName <*> return Nothing <*> arbitrarySmallList <*> arbitraryMsg

instance Arbitrary CommitExtra where
    arbitrary = CommitExtra <$> arbitraryBSasciiNoSpace 80 <*> arbitraryMsg

instance Arbitrary (Tree SHA1) where
    arbitrary = Tree <$> arbitraryEnts

instance Arbitrary (Blob SHA1) where
    arbitrary = Blob <$> arbitraryLazy

instance Arbitrary (Tag SHA1) where
    arbitrary = Tag <$> arbitrary <*> arbitraryObjTypeNoDelta <*> arbitraryBSascii 20 <*> arbitraryName <*> arbitraryMsg

instance Arbitrary (ObjNoDelta SHA1) where
    arbitrary = ObjNoDelta <$> oneof
        [ toObject <$> (arbitrary :: Gen (Commit SHA1))
        , toObject <$> (arbitrary :: Gen (Tree SHA1))
        , toObject <$> (arbitrary :: Gen (Blob SHA1))
        , toObject <$> (arbitrary :: Gen (Tag SHA1))
        ]

prop_object_marshalling_id :: ObjNoDelta SHA1 -> Bool
prop_object_marshalling_id (ObjNoDelta obj) =
    let unmarshall = looseUnmarshall :: L.ByteString -> Object SHA1
     in obj `assertEq` (unmarshall $ looseMarshall obj)
    where assertEq a b
            | show a == show b    = True
            | otherwise = error ("not equal:\n"  ++ show a ++ "\ngot: " ++ show b)

refTests =
    [ testProperty "hexadecimal" (marshEqual (fromHex . toHex :: Ref SHA1 -> Ref SHA1))
    , testProperty "binary" (marshEqual (fromBinary . toBinary :: Ref SHA1 -> Ref SHA1))
    , testProperty "ref" $ marshEqual (fromString . show :: Revision -> Revision)
    ]
    where
        marshEqual t ref = ref `assertEq` t ref
        assertEq a b
            | a == b    = True
            | otherwise = error ("expecting: " ++ show a ++ " got: " ++ show b)

objTests =
    [ testProperty "unmarshall.marshall==id" prop_object_marshalling_id
    ]

main = defaultMain $ testGroup "hit"
    [ testGroup "ref marshalling" refTests
    , testGroup "object marshalling" objTests
    ]
