{-# LANGUAGE
    DataKinds
  , ScopedTypeVariables
  , FlexibleContexts
  , FlexibleInstances
  , KindSignatures
  , GeneralizedNewtypeDeriving
  , ConstraintKinds
  , UndecidableInstances
  , DeriveDataTypeable
  , DeriveGeneric
  , DeriveFunctor
  , DeriveFoldable
  , DeriveTraversable
  #-}

module Test.QuickCheck.Combinators where

import GHC.TypeLits
import Data.Proxy

import Data.Maybe (fromMaybe)
import Data.Unfoldable.Restricted (UnfoldableR, fromList)
import Control.Monad (replicateM)

import Test.QuickCheck

import qualified Data.List as L (sort)

import Data.Data
import GHC.Generics



-- | Generate with a minimum, inclusive size as @n :: Nat@
newtype AtLeast (n :: Nat) t a = AtLeast
  { getAtLeast :: t a
  } deriving (Show, Read, Eq, Ord, Enum, Data, Typeable, Generic, Functor
             , Applicative, Monad, Foldable, Traversable, Monoid)

instance
         ( UnfoldableR p t
         , Monoid (t a)
         , Arbitrary a
         , KnownNat n
         , p a
         ) => Arbitrary (AtLeast (n :: Nat) t a) where
  arbitrary = sized $ \s -> do
    let n' = fromIntegral (natVal (Proxy :: Proxy n))
    k  <- choose (n', s)
    ts <- fromMaybe mempty . fromList <$> replicateM k arbitrary
    return . AtLeast $ ts

instance {-# OVERLAPPING #-}
         ( Arbitrary a
         , Ord a
         , UnfoldableR p []
         , p a
         , KnownNat n) => Arbitrary (AtLeast (n :: Nat) OrderedList a) where
  arbitrary = sized $ \s -> do
    let n' = fromIntegral (natVal (Proxy :: Proxy n))
        mkOrd = Ordered . L.sort . fromMaybe mempty . fromList
    k  <- choose (n', s)
    ts <- mkOrd <$> replicateM k arbitrary
    return . AtLeast $ ts

-- | Generate with a maximum, inclusive size as @n :: Nat@
newtype AtMost (n :: Nat) t a = AtMost
  { getAtMost :: t a
  } deriving (Show, Read, Eq, Ord, Enum, Data, Typeable, Generic, Functor
             , Applicative, Monad, Foldable, Traversable, Monoid)

instance ( UnfoldableR p t
         , Monoid (t a)
         , Arbitrary a
         , KnownNat m
         , p a
         ) => Arbitrary (AtMost (m :: Nat) t a) where
  arbitrary = sized $ \s -> do
    let m' = fromIntegral (natVal (Proxy :: Proxy m))
    k <- choose (0, min m' s)
    ts <- fromMaybe mempty . fromList <$> replicateM k arbitrary
    return . AtMost $ ts

instance {-# OVERLAPPING #-}
         ( Arbitrary a
         , Ord a
         , UnfoldableR p []
         , p a
         , KnownNat n) => Arbitrary (AtMost (n :: Nat) OrderedList a) where
  arbitrary = sized $ \s -> do
    let m' = fromIntegral $ natVal (Proxy :: Proxy n)
        mkOrd = Ordered . L.sort . fromMaybe mempty . fromList
    k <- choose (0, min m' s)
    ts <- mkOrd <$> replicateM k arbitrary
    return . AtMost $ ts

-- | Generate between the inclusive range of @n :: Nat@ and @m :: Nat@
newtype Between (n :: Nat) (m :: Nat) t a = Between
  { getBetween :: t a
  } deriving (Show, Read, Eq, Ord, Enum, Data, Typeable, Generic, Functor
             , Applicative, Monad, Foldable, Traversable, Monoid)

instance ( UnfoldableR p t
         , Monoid (t a)
         , Arbitrary a
         , KnownNat n
         , KnownNat m
         , p a
         ) => Arbitrary (Between (n :: Nat) (m :: Nat) t a) where
  arbitrary = sized $ \s -> do
    let n' = fromIntegral (natVal (Proxy :: Proxy n))
        m' = fromIntegral (natVal (Proxy :: Proxy m))
    k <- choose (n', min m' s)
    ts <- fromMaybe mempty . fromList <$> replicateM k arbitrary
    return . Between $ ts

instance {-# OVERLAPPING #-}
         ( Arbitrary a
         , Ord a
         , KnownNat n
         , UnfoldableR p []
         , p a
         , KnownNat m) => Arbitrary (Between (n :: Nat) (m :: Nat) OrderedList a) where
  arbitrary = sized $ \s -> do
    let n' = fromIntegral (natVal (Proxy :: Proxy n))
        m' = fromIntegral (natVal (Proxy :: Proxy m))
        mkOrd = Ordered . L.sort . fromMaybe mempty . fromList
    k <- choose (n', min m' s)
    ts <- mkOrd <$> replicateM k arbitrary
    return . Between $ ts

-- | Convenience for @AtLeast 1@
type NonMempty = AtLeast 1