{-# LANGUAGE CPP #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}

import Data.WideWord
import Test.QuickCheck.Arbitrary
import Test.QuickCheck.Classes
import Data.Semiring hiding ((+),(*))
import Data.Proxy (Proxy (Proxy))
import Data.Bits
import Foreign.Storable
import Data.Primitive.Types (Prim)
import Data.Maybe (catMaybes)
import Data.Word (Word64)

#if ! MIN_VERSION_base (4,11,0)
import Data.Semigroup
#endif

main :: IO ()
main = lawsCheckMany allPropsApplied

allPropsApplied :: [(String, [Laws])]
allPropsApplied =
  [ ("Int128", allLaws (Proxy :: Proxy Int128))
  , ("Word64", allLaws (Proxy :: Proxy Word64))
  , ("Word128", allLaws (Proxy :: Proxy Word128))
  , ("Word256", allLaws (Proxy :: Proxy Word256))
  ]

allLaws
  :: ( Arbitrary a
     , Bits a
     , Bounded a
     , Enum a
     , Eq a
     , FiniteBits a
     , Integral a
     , Ord a
     , Prim a
     , Read a
     , Semiring a
     , Semigroup a
     , Show a
     , Storable a
     )
  => Proxy a -> [Laws]
allLaws p =
  map ($ p)
    [ bitsLaws
    , boundedEnumLaws
    , eqLaws
    , integralLaws
    , ordLaws
    , semiringLaws
    , semigroupLaws
    , storableLaws
    , primLaws
    , numLaws
    ]

instance Arbitrary Word128 where
  arbitrary =
    Word128 <$> arbitraryBoundedIntegral <*> arbitraryBoundedIntegral

instance Arbitrary Word256 where
  arbitrary =
    Word256
      <$> arbitraryBoundedIntegral <*> arbitraryBoundedIntegral
      <*> arbitraryBoundedIntegral <*> arbitraryBoundedIntegral
  shrink x
    | x == 0 = []
    | x == 1 = [0]
    | x == 2 = [0,1]
    | x == 3 = [0,1,2]
    | otherwise =
        let y = x `shiftR` 1
            z = y + 1
            w = div (x * 9) 10
            p = div (x * 7) 8
         in catMaybes
              [ if y < x then Just y else Nothing
              , if z < x then Just z else Nothing
              , if w < x then Just w else Nothing
              , if p < x then Just p else Nothing
              ]

instance Arbitrary Int128 where
  arbitrary = Int128 <$> arbitrary <*> arbitrary

-- These are used to make sure that 'Num' behaves properly.
instance Semiring Word128 where
  zero = 0
  one  = 1
  plus = (+)
  times = (*)

instance Semiring Word256 where
  zero = 0
  one  = 1
  plus = (+)
  times = (*)

instance Semiring Int128 where
  zero = 0
  one  = 1
  plus = (+)
  times = (*)

-- These are used to make sure that plus is associative
instance Semigroup Word128 where
  (<>) = (+)

instance Semigroup Word64 where
  (<>) = (+)

instance Semigroup Word256 where
  (<>) = (+)

instance Semigroup Int128 where
  (<>) = (+)