module Wingman.KnownStrategies where

import Data.Foldable (for_)
import Development.IDE.GHC.Compat.Core
import Refinery.Tactic
import Wingman.Judgements (jGoal)
import Wingman.KnownStrategies.QuickCheck (deriveArbitrary)
import Wingman.Machinery (tracing, getKnownInstance, getCurrentDefinitions)
import Wingman.Tactics
import Wingman.Types


knownStrategies :: TacticsM ()
knownStrategies = choice
  [ known "fmap" deriveFmap
  , known "mempty" deriveMempty
  , known "arbitrary" deriveArbitrary
  , known "<>" deriveMappend
  , known "mappend" deriveMappend
  ]


known :: String -> TacticsM () -> TacticsM ()
known name t = do
  getCurrentDefinitions >>= \case
    [(def, _)] | def == mkVarOcc name ->
      tracing ("known " <> name) t
    _ -> failure NoApplicableTactic


deriveFmap :: TacticsM ()
deriveFmap = do
  try intros
  overAlgebraicTerms homo
  choice
    [ overFunctions (apply Saturated) >> auto' 2
    , assumption
    , recursion
    ]


------------------------------------------------------------------------------
-- | We derive mappend by binding the arguments, introducing the constructor,
-- and then calling mappend recursively. At each recursive call, we filter away
-- any binding that isn't in an analogous position.
--
-- The recursive call first attempts to use an instace in scope. If that fails,
-- it fals back to trying a theta method from the hypothesis with the correct
-- name.
deriveMappend :: TacticsM ()
deriveMappend = do
  try intros
  destructAll
  split
  g <- goal
  minst <- getKnownInstance (mkClsOcc "Semigroup")
         . pure
         . unCType
         $ jGoal g
  for_ minst $ \(cls, df) -> do
    restrictPositionForApplication
      (applyMethod cls df $ mkVarOcc "<>")
      assumption
  try $
    restrictPositionForApplication
      (applyByName $ mkVarOcc "<>")
      assumption


------------------------------------------------------------------------------
-- | We derive mempty by introducing the constructor, and then trying to
-- 'mempty' everywhere. This smaller 'mempty' might come from an instance in
-- scope, or it might come from the hypothesis theta.
deriveMempty :: TacticsM ()
deriveMempty = do
  split
  g <- goal
  minst <- getKnownInstance (mkClsOcc "Monoid") [unCType $ jGoal g]
  for_ minst $ \(cls, df) -> do
    applyMethod cls df $ mkVarOcc "mempty"
  try assumption

