{-# OPTIONS_GHC -Wno-orphans #-}
{-# LANGUAGE UndecidableInstances #-}
module Darcs.Test.Patch.WithState where

import Darcs.Prelude

import Darcs.Patch.Apply
import Darcs.Patch.Commute
import Darcs.Patch.Effect
import Darcs.Patch.FromPrim
import Darcs.Patch.Invert
import Darcs.Patch.Prim.Class
import Darcs.Patch.Witnesses.Eq
import Darcs.Patch.Witnesses.Maybe
import Darcs.Patch.Witnesses.Ordered
import Darcs.Patch.Witnesses.Sealed
import Darcs.Patch.Witnesses.Show
import Test.QuickCheck ( Gen, sized, choose )

import Darcs.Test.Patch.RepoModel
import Darcs.Test.Patch.Arbitrary.Sealed
import Darcs.Test.Patch.Arbitrary.Shrink
import Darcs.Test.Patch.Types.Pair ( Pair(..) )

import Data.Maybe

----------------------------------------------------------------------
-- * WithState

data WithState p wX wY = WithState {
                              wsStartState :: (ModelOf p) wX
                            , wsPatch      :: p wX wY
                            , wsEndState   :: (ModelOf p) wY
                            }

type instance ModelOf (WithState p) = ModelOf p

instance (Show1 (ModelOf p), Show2 p) => Show (WithState p wX wY) where
  showsPrec d (WithState s p s')
    = showParen (d > appPrec) $ showString "WithState "
                              . showsPrec1 (appPrec+1) s
                              . showString " "
                              . showsPrec2 (appPrec+1) p
                              . showString " "
                              . showsPrec1 (appPrec+1) s'

instance (Show1 (ModelOf p), Show2 p) => Show1 (WithState p wA)

instance (Show1 (ModelOf p), Show2 p) => Show2 (WithState p)

class ArbitraryWS p where
  arbitraryWS :: Gen (Sealed2 (WithState p))
  shrinkWS :: Sealed2 (WithState p) -> [Sealed2 (WithState p)]
  shrinkWS _ = []

instance ArbitraryWS p => ArbitraryS2 (WithState p) where
  arbitraryS2 = arbitraryWS
  shrinkS2 = shrinkWS

instance (ArbitraryState p1, ArbitraryState p2, ModelOf p1 ~ ModelOf p2, RepoModel (ModelOf p1)) => ArbitraryS2 (p1 :\/: p2) where
  arbitraryS2 = do
    repo <- aSmallRepo
    Sealed (WithEndState p1 _) <- arbitraryState repo
    Sealed (WithEndState p2 _) <- arbitraryState repo
    return (Sealed2 (p1 :\/: p2))

arbitraryWSPair :: (RepoModel (ModelOf p), ArbitraryState p) => Gen (Sealed2 (WithState (Pair p)))
arbitraryWSPair = do
  repo <- aSmallRepo
  Sealed (WithEndState pp repo') <- arbitraryStatePair repo
  return $ seal2 $ WithState repo pp repo'

instance (RepoModel (ModelOf p), ArbitraryState p) => ArbitraryWS (Pair p) where
  arbitraryWS = arbitraryWSPair
  shrinkWS _ = []

instance (RepoModel (ModelOf p), ArbitraryState p) => ArbitraryWS (FL p) where
  arbitraryWS = makeWS2Gen aSmallRepo

-- | This is only used for the legacy 'Tree' based test generator, where the
-- @p@ parameter gets instantiated to @'Tree' p@ (which has no definite end
-- state).
data WithStartState s p wX = WithStartState {
                                 wssStartState :: s wX
                               , wssPatch      :: p wX
                               }
    deriving Eq

instance (Show1 s, Show1 p) => Show (WithStartState s p wX) where
   showsPrec d (WithStartState s p) = showParen (d > appPrec) $ showString "WithStartState " .
                                      showsPrec1 (appPrec + 1) s . showString " " .
                                      showsPrec1 (appPrec + 1) p

instance (Show1 s, Show1 p) => Show1 (WithStartState s p)

-- |'WithStartState2' is like 'WithStartState' but for patches that have both witnesses.
data WithStartState2 p wX wY =
  WithStartState2
  { wss2StartState :: ModelOf p wX
  , wss2Patch      :: p wX wY
  }

instance (Show1 (ModelOf p), Show2 p) => Show (WithStartState2 p wX wY) where
  showsPrec d (WithStartState2 s p) =
    showParen (d > appPrec) $ showString "WithStartState2 " .
    showsPrec1 (appPrec + 1) s . showString " " .
    showsPrec2 (appPrec + 1) p

instance (Show1 (ModelOf p), Show2 p) => Show1 (WithStartState2 p wX)
instance (Show1 (ModelOf p), Show2 p) => Show2 (WithStartState2 p)

-- | A combination of a patch and its final state. The state, in this module, is
--   typically represented by a 'RepoModel' value. The @px@ type is typically a
--   patch type applied to its pre-state, e.g. @Prim x@.
data WithEndState s px wY = WithEndState {
                                wesPatch    :: px wY
                              , wesEndState :: s wY
                              }
    deriving Eq

instance (Show1 s, Show1 p) => Show (WithEndState s p wX) where
   showsPrec d (WithEndState p s) = showParen (d > appPrec) $ showString "WithEndState " .
                                    showsPrec1 (appPrec + 1) p . showString " " .
                                    showsPrec1 (appPrec + 1) s


instance (Show1 s, Show1 p) => Show1 (WithEndState s p)


----------------------------------------------------------------------
-- * ArbitraryState generators

-- | A type class to generate arbitrary values, threading a state through the
--   arbitrary calls. So this can be used to generate a patch that comes after
--   another patch. The post-state of the generated patch is hidden by the
--   'Sealed'.
class ArbitraryState p where
  arbitraryState :: ModelOf p wX -> Gen (Sealed (WithEndState (ModelOf p) (p wX)))

  -- |This member is to allow specialising generation of pairs,
  -- e.g. to increase the frequency of commutable ones.
  arbitraryStatePair :: ModelOf p wX -> Gen (Sealed (WithEndState (ModelOf p) (Pair p wX)))
  -- default implementation doesn't do anything special
  arbitraryStatePair s = do
    -- use the :> instance
    Sealed (WithEndState pair s') <- arbitraryState s
    return $ seal $ WithEndState (Pair pair) s'

instance ArbitraryState p => ArbitraryState (WithState p) where
  arbitraryState s = do Sealed (WithEndState x s') <- arbitraryState s
                        return $ seal $ WithEndState (WithState s x s') s'

-- this instance is only useful if ModelOf p ~ ModelOf q
type instance ModelOf (p :> q) = ModelOf p

instance (ArbitraryState p, ArbitraryState q, ModelOf p ~ ModelOf q) => ArbitraryState (p :> q) where
  arbitraryState s = do
    Sealed (WithEndState p1 s') <- arbitraryState s
    Sealed (WithEndState p2 s'') <- arbitraryState s'
    return $ seal $ WithEndState (p1 :> p2) s'' 

arbitraryFL ::
     ArbitraryState p
  => forall wX. Int -> ModelOf p wX -> Gen (Sealed (WithEndState (ModelOf p) (FL p wX)))
arbitraryFL 0 s = return $ seal $ WithEndState NilFL s
arbitraryFL n s = do Sealed (WithEndState x s') <- arbitraryState s
                     Sealed (WithEndState xs s'') <- arbitraryFL (n-1) s'
                     return $ seal $ WithEndState (x :>: xs) s''

instance ArbitraryState p => ArbitraryState (FL p) where
  arbitraryState s = sized $ \n -> do k <- choose (0, min 2 (n `div` 5))
                                      arbitraryFL k s


makeSGen :: ArbitraryState p => Gen (ModelOf p wX) -> Gen (Sealed (p wX))
makeSGen stGen = do s <- stGen
                    Sealed (WithEndState p _) <- arbitraryState s
                    return $ seal p

makeWS2Gen :: ArbitraryState p => Gen (ModelOf p wX) -> Gen (Sealed2 (WithState p))
makeWS2Gen stGen = do s <- stGen
                      Sealed (WithEndState wsP _) <- arbitraryState s
                      return $ seal2 wsP

makeWSGen :: ArbitraryState p => Gen (ModelOf p wX) -> Gen (Sealed (WithState p wX))
makeWSGen stGen = do s <- stGen
                     Sealed (WithEndState wsP _) <- arbitraryState s
                     return $ seal wsP

-- | A class to help with shrinking complex test cases by simplifying
-- the starting state of the test case. See also 'PropagateShrink'.
class ShrinkModel prim where
  -- |Given a repository state, produce a patch that simplifies the
  -- repository state. The inverse of the patch can be passed as the
  -- "shrinking fixup" to 'propagateShrink'.
  --
  -- Imagine that we start with
  --
  --    s wX1 --p1 wX1 wY1--> s wY1
  --
  -- If we shrink the state to @s wX2@:
  --
  --    s wX2 <--prim wX1 wX2-- s wX1
  --
  -- then we hope that 'propagateShrink' will produce a simpler version of @p1@,
  -- @p2@, that starts from the simpler state @s wX2@:
  --
  --                        p2 wX2 wY2
  --               s wX2 ----------------> s wY2
  --                |                        |
  --                |                        |
  --    invert prim |                        | (discard)
  --                |                        |
  --                V                        V
  --               s wX1 ----------------> s wY1
  --                        p1 wX1 wY1
  shrinkModelPatch :: ModelOf prim wX -> [Sealed (prim wX)]

checkOK :: Fail a -> [a]
checkOK = maybe [] (\x -> [x]) . maybeFail

shrinkModel
  :: forall s prim wX
   . (Apply prim, ApplyState prim ~ RepoState s, ModelOf prim ~ s, RepoModel s, ShrinkModel prim)
  => s wX -> [Sealed (WithEndState s (prim wX))]
shrinkModel s = do
  Sealed prim <- shrinkModelPatch s
  endState <- checkOK $ repoApply s prim
  return $ Sealed $ WithEndState prim endState

-- | A class to help with shrinking complex test cases. The idea is that the
-- "starting state" of the test case is shrunk and this results in a "fixup"
-- primitive that goes from the shrunk starting state to the original starting
-- state. This so-called "shrinking fixup" is then propagated through the test
-- case to generate a new test case that starts at the shrunk starting state.
-- The shrinking fixup is typically generated via the 'ShrinkModel' class.
class PropagateShrink prim p where
  -- Given a test patch (of type @p@) and a shrinking fixup (of type @prim@),
  -- try to propagate the shrinking fixup past the test patch.
  -- The @Maybe2 p@ return type allows the fixup to eliminate the shrinking
  -- patch entirely, and vice versa the @Maybe2 prim@ allows the shrinking fixup
  -- to disappear (for example it might be cancelled out by something in the test
  -- patch).
  -- We don't use @FL p@, because that would only really be useful for a "stuck"
  -- fixup - one that doesn't eliminate or commute - and that implies that
  -- the state shrink isn't actually shrinking the real test case.
  propagateShrink :: (prim :> p) wX wY -> Maybe ((Maybe2 p :> Maybe2 prim) wX wY)

propagateShrinkKeep
  :: PropagateShrink prim p
  => (prim :> p) wX wY
  -> Maybe ((p :> Maybe2 prim) wX wY)
propagateShrinkKeep inp = do
  Just2 p' :> mprim' <- propagateShrink inp
  return (p' :> mprim')

propagateShrinkMaybe
  :: PropagateShrink prim p
  => (Maybe2 prim :> p) wX wY
  -> Maybe ((Maybe2 p :> Maybe2 prim) wX wY)
propagateShrinkMaybe (Nothing2 :> p) = Just (Just2 p :> Nothing2)
propagateShrinkMaybe (Just2 prim :> p) = propagateShrink (prim :> p)

-- |Shrink a test case wrapped with 'WithStartState2' by shrinking the start state
-- of the test case with 'ShrinkModel' and then propagating the shrink through the
-- patch type of the test case.
shrinkState
  :: forall s prim p
   . ( Invert prim, Apply prim, RepoModel s
     , ShrinkModel prim, PropagateShrink prim p
     , ApplyState prim ~ RepoState s
     , ModelOf p ~ s
     , ModelOf prim ~ s
     )
  => Sealed2 (WithStartState2 p)
  -> [Sealed2 (WithStartState2 p)]
shrinkState (Sealed2 (WithStartState2 s p)) = do
  Sealed (WithEndState fixup shrunkState) <- shrinkModel @s @prim s
  p' :> _ <- maybeToList $ propagateShrinkKeep (invert fixup :> p)
  return $ Sealed2 $ WithStartState2 shrunkState p'

shrinkAtStartState
  :: ( Shrinkable p, RepoModel (ModelOf p), Effect p
     , prim ~ PrimOf p, Invert prim, Apply prim
     , ApplyState prim ~ RepoState (ModelOf p)
     )
  => WithStartState2 p wX wY
  -> [FlippedSeal (WithStartState2 p) wY]
shrinkAtStartState (WithStartState2 s p) = do
  FlippedSeal p' <- shrinkAtStart p
  endState <- checkOK $ repoApply s (effect p)
  newState <- checkOK $ repoApply endState (invert (effect p'))
  return $ FlippedSeal (WithStartState2 newState p')

instance
  ( ArbitraryState p, Shrinkable p, RepoModel s
  , s ~ ModelOf p
  , s ~ ModelOf prim
  , Effect p
  , Apply prim, ApplyState prim ~ RepoState s
  , prim ~ PrimOf p, Invert prim, ShrinkModel prim, PropagateShrink prim p
  )
  => ArbitraryS2 (WithStartState2 p) where
  arbitraryS2 = do
    repo <- aSmallRepo @s
    Sealed (WithEndState p _) <- arbitraryState repo
    return (Sealed2 (WithStartState2 repo p))
  shrinkS2 w@(Sealed2 (WithStartState2 repo p)) =
    map (Sealed2 . WithStartState2 repo) (shrinkInternally p) ++
    map (unseal (Sealed2 . WithStartState2 repo)) (shrinkAtEnd p) ++
    map (unsealFlipped Sealed2) (shrinkAtStartState (WithStartState2 repo p)) ++
    shrinkState @s @prim @p w

propagatePrim
  :: (Eq2 prim, PrimCoalesce prim, Invert prim, Commute prim)
  => (prim :> prim) wX wY -> Maybe ((Maybe2 prim :> Maybe2 prim) wX wY)
propagatePrim (p1 :> p2)
  | IsEq <- invert p1 =\/= p2 = Just (Nothing2 :> Nothing2)
  | Just (p2' :> p1') <- commute (p1 :> p2) = Just (Just2 p2' :> Just2 p1')
  | Just p' <- primCoalesce p1 p2 = Just (Just2 p' :> Nothing2)
  | otherwise = Nothing

instance (PropagateShrink prim p, PropagateShrink prim q)
  => PropagateShrink prim (p :> q) where

  propagateShrink (prim :> (p :> q)) = do
    Just2 mp' :> mprim' <- propagateShrink (prim :> p)
    Just2 mq' :> mprim'' <- propagateShrinkMaybe (mprim' :> q)
    return (Just2 (mp' :> mq') :> mprim'')

instance PropagateShrink prim p => PropagateShrink prim (FL p) where
  propagateShrink (prim :> NilFL) = Just (Just2 NilFL :> Just2 prim)
  propagateShrink (prim :> (p :>: ps)) = do
    mp' :> mprim' <- propagateShrink (prim :> p)
    Just2 ps' :> mprim'' <- propagateShrinkMaybe (mprim' :> ps)
    let result = case mp' of
          Nothing2 -> ps'
          Just2 p' -> p' :>: ps'
    return (Just2 result :> mprim'')
