{-# LANGUAGE FlexibleContexts         #-}
{-# LANGUAGE FlexibleInstances        #-}
{-# LANGUAGE OverlappingInstances     #-}
{-# LANGUAGE ScopedTypeVariables      #-}
{-# LANGUAGE TypeOperators            #-}
{-# LANGUAGE CPP                      #-}

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

-----------------------------------------------------------------------------
-- |
-- Module      :  Generics.Regular.Functions.Arbitrary
-- Copyright   :  (c) 2009 Universiteit Utrecht
-- License     :  BSD3
--
-- Maintainer  :  generics@haskell.org
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Summary: Generic "Test.QuickCheck" instances.
-----------------------------------------------------------------------------

module Generics.Regular.Functions.Arbitrary (
  
    -- * Generic arbitrary functionality
    FrequencyTable, Arbitrary(..), arbitraryWith, arbitrary,
    
    -- * Generic coarbitrary functionality
    CoArbitrary(..), corbitrary
  
  ) where

import Generics.Regular.Functions.Fixpoints
import Generics.Regular.Functions.ConNames
import Generics.Regular.Base

import Test.QuickCheck (Gen, frequency, sized, variant)
import qualified Test.QuickCheck as Q
import Data.Maybe (fromJust)

-- | A frequency table detailing how often certain constructors should be
-- picked. The 'String' corresponds to the constructor name, as returned by
-- 'Generics.Regular.Functions.ConNames.conNames'.
type FrequencyTable = [(String,Int)]

frequencies :: [String] -> FrequencyTable -> Int
frequencies [] _ = 0
frequencies (s:ss) ft = let freqs = case lookup s ft of
                                      Just f  -> f
                                      Nothing -> 1
                        in freqs + frequencies ss ft

-- | Generic Arbitrary class
class Arbitrary f where 
  harbitrary :: (Int -> Gen a) -> FrequencyTable -> Int -> Int
             -> Maybe (Gen (f a))

instance (Fixpoints f, Fixpoints g, ConNames f, Arbitrary f,
            ConNames g, Arbitrary g) => Arbitrary (f :+: g) where
  harbitrary r ft _ n = 
    let (Node ff fg)   = hFixpoints (undefined :: (f :+: g) a)
        fConNames      = hconNames (undefined :: f a)
        gConNames      = hconNames (undefined :: g a)
        fFrequency     = calcFreq n (sumTree ff) (frequencies fConNames ft)
        gFrequency     = calcFreq n (sumTree fg) (frequencies gConNames ft)
        calcFreq 0 0 _ = 1
        calcFreq 0 _ _ = 0
        calcFreq _ _ d = d
        rl = maybe [] (\x -> [(fFrequency,fmap L x)]) 
               (harbitrary r ft (sumTree ff) n)
        rr = maybe [] (\x -> [(gFrequency,fmap R x)])
               (harbitrary r ft (sumTree fg) n)
    in if null (rl ++ rr) then Nothing else return $ frequency $ rl ++ rr

instance (Arbitrary f, Constructor c) => Arbitrary (C c f) where
  harbitrary r ft m n = fmap (fmap C) (harbitrary r ft m n)
                       
instance Arbitrary I where
  harbitrary r _ m n = return $ fmap I $ r (n `div` m)

instance Arbitrary U where
  harbitrary _ _ _ _ = return $ return U

instance (Q.Arbitrary a) => Arbitrary (K a) where
  harbitrary _ _ _ _ = return $ fmap K $ Q.arbitrary

instance (Arbitrary f, Arbitrary g) => Arbitrary (f :*: g) where
  harbitrary r ft m n = do rl <- harbitrary r ft m n
                           rr <- harbitrary r ft m n
                           return $ do
                             x <- rl
                             y <- rr
                             return (x :*: y)

-- | Generic arbitrary function, sized and with custom constructor frequencies.

-- This function does not require any particular
-- nesting order of the sums of the generic representation, but it does require
-- every constructor to be properly tagged with C. Representations generated
-- with the supplied Template Haskell code are compliant.
arbitraryWith :: (Regular a, Arbitrary (PF a))
           => FrequencyTable -> Int -> Gen a
arbitraryWith ft = fmap to . fromJust . harbitrary (arbitraryWith ft) ft 1

-- | Generic arbitrary function with default sizes and constructor frequencies.
arbitrary :: (Regular a, Arbitrary (PF a)) => Gen a
arbitrary = sized (arbitraryWith [])


-- | Generic CoArbitrary class
class CoArbitrary f where 
  hcoarbitrary :: (b -> Gen a -> Gen a) -> Int -> f b -> Gen a -> Gen a

instance (CoArbitrary f, CoArbitrary g, ConNames g)
            => CoArbitrary (f :+: g) where
  hcoarbitrary r n (L x) = hcoarbitrary r n x
  hcoarbitrary r n (R x) = hcoarbitrary r (n + length (hconNames x)) x

instance (CoArbitrary f, Constructor c) => CoArbitrary (C c f) where
  hcoarbitrary r n (C x) = variant n . hcoarbitrary r n x

instance CoArbitrary I where
  hcoarbitrary r _ (I x) = r x

instance CoArbitrary U where
  hcoarbitrary _ _ _ = id

#if MIN_VERSION_QuickCheck(2,1,0)
instance (Q.CoArbitrary a) => CoArbitrary (K a) where
#else
instance (Q.Arbitrary a) => CoArbitrary (K a) where
#endif
  hcoarbitrary _ _ (K a) = Q.coarbitrary a

instance (CoArbitrary f, CoArbitrary g) => CoArbitrary (f :*: g) where
  hcoarbitrary r n (x1 :*: x2) = hcoarbitrary r n x1 . hcoarbitrary r n x2

-- | Generic coarbitrary function.
corbitrary :: (Regular b, CoArbitrary (PF b))
           => b -> Gen a -> Gen a
corbitrary = hcoarbitrary corbitrary 0 . from