{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards   #-}
{-# LANGUAGE TypeFamilies      #-}

{-# LANGUAGE NoMonoLocalBinds  #-}

module Wingman.EmptyCase where

import           Control.Applicative (empty)
import           Control.Monad
import           Control.Monad.Except (runExcept)
import           Control.Monad.Trans
import           Control.Monad.Trans.Maybe
import           Data.Generics.Aliases (mkQ, GenericQ)
import           Data.Generics.Schemes (everything)
import           Data.Maybe
import           Data.Monoid
import qualified Data.Text as T
import           Data.Traversable
import           Development.IDE (hscEnv, realSrcSpanToRange)
import           Development.IDE.Core.RuleTypes
import           Development.IDE.Core.Shake (IdeState (..))
import           Development.IDE.Core.UseStale
import           Development.IDE.GHC.Compat hiding (empty)
import           Development.IDE.GHC.ExactPrint
import           Development.IDE.Spans.LocalBindings (getLocalScope)
import           Ide.Types
import           Language.LSP.Server
import           Language.LSP.Types
import           Prelude hiding (span)
import           Wingman.AbstractLSP.Types
import           Wingman.CodeGen (destructionFor)
import           Wingman.GHC
import           Wingman.Judgements
import           Wingman.LanguageServer
import           Wingman.Types


data EmptyCaseT = EmptyCaseT

instance IsContinuationSort EmptyCaseT where
  toCommandId _ = CommandId "wingman.emptyCase"

instance IsTarget EmptyCaseT where
  type TargetArgs EmptyCaseT = ()
  fetchTargetArgs _ = pure ()

emptyCaseInteraction :: Interaction
emptyCaseInteraction = Interaction $
  Continuation @EmptyCaseT @EmptyCaseT @WorkspaceEdit EmptyCaseT
    (SynthesizeCodeLens $ \LspEnv{..} _ -> do
      let FileContext{..} = le_fileContext
      nfp <- getNfp fc_uri

      let stale a = runStaleIde "codeLensProvider" le_ideState nfp a

      ccs <- lift getClientCapabilities
      TrackedStale pm _ <- mapMaybeT liftIO $ stale GetAnnotatedParsedSource
      TrackedStale binds bind_map <- mapMaybeT liftIO $ stale GetBindings
      holes <- mapMaybeT liftIO $ emptyCaseScrutinees le_ideState nfp

      for holes $ \(ss, ty) -> do
        binds_ss <- liftMaybe $ mapAgeFrom bind_map ss
        let bindings = getLocalScope (unTrack binds) $ unTrack binds_ss
            range = realSrcSpanToRange $ unTrack ss
        matches <-
          liftMaybe $
            destructionFor
              (foldMap (hySingleton . occName . fst) bindings)
              ty
        edits <- liftMaybe $ hush $
              mkWorkspaceEdits le_dflags ccs fc_uri (unTrack pm) $
                graftMatchGroup (RealSrcSpan (unTrack ss) Nothing) $
                  noLoc matches
        pure
          ( range
          , Metadata
              (mkEmptyCaseLensDesc ty)
              (CodeActionUnknown "refactor.wingman.completeEmptyCase")
              False
          , edits
          )
    )
    (\ _ _ _ we -> pure $ pure $ RawEdit we)


scrutinzedType :: EmptyCaseSort Type -> Maybe Type
scrutinzedType (EmptyCase ty) = pure  ty
scrutinzedType (EmptyLamCase ty) =
  case tacticsSplitFunTy ty of
    (_, _, tys, _) -> listToMaybe $ fmap scaledThing tys


------------------------------------------------------------------------------
-- | The description for the empty case lens.
mkEmptyCaseLensDesc :: Type -> T.Text
mkEmptyCaseLensDesc ty =
  "Wingman: Complete case constructors (" <> T.pack (unsafeRender ty) <> ")"


------------------------------------------------------------------------------
-- | Silence an error.
hush :: Either e a -> Maybe a
hush (Left _) = Nothing
hush (Right a) = Just a


------------------------------------------------------------------------------
-- | Graft a 'RunTacticResults' into the correct place in an AST. Correctly
-- deals with top-level holes, in which we might need to fiddle with the
-- 'Match's that bind variables.
graftMatchGroup
    :: SrcSpan
    -> Located [LMatch GhcPs (LHsExpr GhcPs)]
    -> Graft (Either String) ParsedSource
graftMatchGroup ss l =
  hoistGraft (runExcept . runExceptString) $ graftExprWithM ss $ \case
    L span (HsCase ext scrut mg) -> do
      pure $ Just $ L span $ HsCase ext scrut $ mg { mg_alts = l }
    L span (HsLamCase ext mg) -> do
      pure $ Just $ L span $ HsLamCase ext $ mg { mg_alts = l }
    (_ :: LHsExpr GhcPs) -> pure Nothing


fromMaybeT :: Functor m => a -> MaybeT m a -> m a
fromMaybeT def = fmap (fromMaybe def) . runMaybeT


------------------------------------------------------------------------------
-- | Find the last typechecked module, and find the most specific span, as well
-- as the judgement at the given range.
emptyCaseScrutinees
    :: IdeState
    -> NormalizedFilePath
    -> MaybeT IO [(Tracked 'Current RealSrcSpan, Type)]
emptyCaseScrutinees state nfp = do
    let stale a = runStaleIde "emptyCaseScrutinees" state nfp a

    TrackedStale tcg tcg_map <- fmap (fmap tmrTypechecked) $ stale TypeCheck
    let tcg' = unTrack tcg
    hscenv <- stale GhcSessionDeps

    let scrutinees = traverse (emptyCaseQ . tcg_binds) tcg
    fmap catMaybes $ for scrutinees $ \aged@(unTrack -> (ss, scrutinee)) -> do
      ty <- MaybeT
          . fmap (scrutinzedType <=< sequence)
          . traverse (typeCheck (hscEnv $ untrackedStaleValue hscenv) tcg')
          $ scrutinee
      case null $ tacticsGetDataCons ty of
        True -> pure empty
        False ->
          case ss of
            RealSrcSpan r _ -> do
              rss' <- liftMaybe $ mapAgeTo tcg_map $ unsafeCopyAge aged r
              pure $ Just (rss', ty)
            UnhelpfulSpan _ -> empty

data EmptyCaseSort a
  = EmptyCase a
  | EmptyLamCase a
  deriving (Eq, Ord, Show, Functor, Foldable, Traversable)

------------------------------------------------------------------------------
-- | Get the 'SrcSpan' and scrutinee of every empty case.
emptyCaseQ :: GenericQ [(SrcSpan, EmptyCaseSort (HsExpr GhcTc))]
emptyCaseQ = everything (<>) $ mkQ mempty $ \case
  L new_span (Case scrutinee []) -> pure (new_span, EmptyCase scrutinee)
  L new_span expr@(LamCase []) -> pure (new_span, EmptyLamCase expr)
  (_ :: LHsExpr GhcTc) -> mempty

