{-# OPTIONS_GHC -Wunused-imports #-}

-- | Implementation of the primitives relating to Cubical identity types.

module Agda.TypeChecking.Primitive.Cubical.Id
  ( -- * General elimination form
    primIdElim'
  -- * Introduction form
  , primConId'
  -- * Projection maps (primarily used internally)
  , primIdFace'
  , primIdPath'
  -- * Kan operations
  , doIdKanOp
  )
  where

import qualified Data.IntMap as IntMap
import Data.Traversable
import Data.Maybe

import Agda.Syntax.Common
  ( Cubical(..), Arg(..), defaultArgInfo, defaultArg )
import Agda.Syntax.Internal

import Agda.TypeChecking.Monad.Builtin
import Agda.TypeChecking.Monad.Base
import Agda.TypeChecking.Monad.Debug (__IMPOSSIBLE_VERBOSE__)

import Agda.TypeChecking.Names
  ( runNamesT, runNames, cl, lam, ilam, open )
import Agda.TypeChecking.Primitive.Base
  ( (-->), nPi', hPi', el, el', el's, (<@>), (<#>), (<..>), argN )
import Agda.TypeChecking.Primitive.Cubical.Base
import Agda.TypeChecking.Reduce
  ( reduceB' )
import Agda.TypeChecking.Substitute
  ( apply, sort, listS, applySubst )

import Agda.Utils.Impossible (__IMPOSSIBLE__)

-- | Primitive elimination rule for the cubical identity types. Unlike
-- J, @idElim@ makes explicit the structure of Swan's identity types as
-- being pairs of a cofibration and a path. Moreover, it records that
-- the path is definitionally @refl@ under that cofibration.
primIdElim' :: TCM PrimitiveImpl
primIdElim' = do
  -- The implementation here looks terrible but most of it is actually
  -- the type.
  requireCubical CErased ""
  t <- runNamesT [] $
    hPi' "a" (el $ cl primLevel) $ \ a ->
    hPi' "c" (el $ cl primLevel) $ \ c ->
    hPi' "A" (sort . tmSort <$> a) $ \ bA ->
    hPi' "x" (el' a bA) $ \ x ->
    nPi' "C" (nPi' "y" (el' a bA) $ \ y ->
              el' a (cl primId <#> a <#> bA <@> x <@> y) --> (sort . tmSort <$> c)) $ \ bC ->
    -- To construct (C : (y : A) → Id A x y → Type c), it suffices to:

    -- For all cofibrations φ,
    nPi' "φ" primIntervalType (\ phi ->
      -- For all y : A [ φ → (λ _ → x) ]
      nPi' "y" (el's a $ cl primSub <#> a <@> bA <@> phi <@> lam "o" (const x)) $ \ y ->
      let pathxy = cl primPath <#> a <@> bA <@> x <@> outSy
          outSy  = cl primSubOut <#> a <#> bA <#> phi <#> lam "o" (const x) <@> y
          reflx  = lam "o" $ \ _ -> lam "i" $ \ _ -> x -- TODO Andrea, should block on o
      -- For all w : (Path A x (outS y)) [ φ (λ _ → refl {x = outS y} ]
      in nPi' "w" (el's a $ cl primSub <#> a <@> pathxy <@> phi <@> reflx) $ \ w ->
      let outSw = (cl primSubOut <#> a <#> pathxy <#> phi <#> reflx <@> w)
      in el' c $ bC <@> outSy <@> (cl primConId <#> a <#> bA <#> x <#> outSy <@> phi <@> outSw))
      -- Construct an inhabitant of (C (outS y) (conid φ (outS w)))
    -->
    hPi' "y" (el' a bA) (\ y ->
      nPi' "p" (el' a $ cl primId <#> a <#> bA <@> x <@> y) $ \ p ->
      el' c $ bC <@> y <@> p)

  -- Implementation starts here:
  conid <- primConId
  sin <- primSubIn
  path <- primPath
  return $ PrimImpl t $ primFun __IMPOSSIBLE__ 8 $ \case
    [a,c,bA,x,bC,f,y,p] -> do
      sp <- reduceB' p
      cview <- conidView'
      case cview (unArg x) $ unArg $ ignoreBlocking sp of
        -- Record that the right endpoint and the path definitionally
        -- agree with x φ holds. This is guaranteed internally by the
        -- typing rule for @conId@ but can't be recovered from
        -- @primIdPath@ and @primIdFace@ (see #2598)
        Just (phi, w) -> do
          let y' = sin `apply` [a, bA, phi, argN (unArg y)]
          let w' = sin `apply` [a, argN (path `apply` [a, bA, x, y]), phi, argN (unArg w)]
          redReturn $ unArg f `apply` [phi, defaultArg y', defaultArg w']
        _ -> return $ NoReduction $ map notReduced [a,c,bA,x,bC,f,y] ++ [reduced sp]
    _ -> __IMPOSSIBLE_VERBOSE__ "implementation of primIdElim called with wrong arity"

-- | Introduction form for the cubical identity types.
primConId' :: TCM PrimitiveImpl
primConId' = do
  requireCubical CErased ""
  t <- runNamesT [] $
    hPi' "a" (el $ cl primLevel) $ \ a ->
    hPi' "A" (sort . tmSort <$> a) $ \ bA ->
    hPi' "x" (el' a bA) $ \ x ->
    hPi' "y" (el' a bA) $ \ y ->
    primIntervalType -- Cofibration
    --> (el' a $ cl primPath <#> a <#> bA <@> x <@> y)
    --> (el' a $ cl primId <#> a <#> bA <@> x <@> y)

  -- Implementation note: conId, as the name implies, is a constructor.
  -- It's not represented as a constructor because users can't match on
  -- it (but we, internally, can: see createMissingConIdClause).

  return $ PrimImpl t $ primFun __IMPOSSIBLE__ 6 $ \case
    [l,bA,x,y,phi,p] -> do
      sphi <- reduceB' phi
      view <- intervalView'
      case view $ unArg $ ignoreBlocking sphi of
        -- But even though it's a constructor, it does reduce, in some
        -- cases: If the cofibration is definitely true, then we return
        -- reflId.  TODO: Handle this in the conversion checker instead?
        IOne -> do
          reflId <- getTerm (getBuiltinId builtinConId) builtinReflId
          redReturn $ reflId
        _ -> return $ NoReduction $ map notReduced [l,bA,x,y] ++ [reduced sphi, notReduced p]
    _ -> __IMPOSSIBLE_VERBOSE__ "implementation of primConId called with wrong arity"

-- | Extract the underlying cofibration from an inhabitant of the
-- cubical identity types.
--
-- TODO (Amy, 2022-08-17): Projecting a cofibration from a Kan type
-- violates the cubical phase distinction.
primIdFace' :: TCM PrimitiveImpl
primIdFace' = do
  requireCubical CErased ""
  t <- runNamesT [] $
    hPi' "a" (el $ cl primLevel) $ \ a ->
    hPi' "A" (sort . tmSort <$> a) $ \ bA ->
    hPi' "x" (el' a bA) $ \ x ->
    hPi' "y" (el' a bA) $ \ y ->
    el' a (cl primId <#> a <#> bA <@> x <@> y)
    --> primIntervalType

  return $ PrimImpl t $ primFun __IMPOSSIBLE__ 5 $ \case
    [l,bA,x,y,t] -> do
      st <- reduceB' t
      mConId <- getName' builtinConId
      cview <- conidView'
      case cview (unArg x) $ unArg (ignoreBlocking st) of
        Just (phi, _) -> redReturn (unArg phi)
        _ -> return $ NoReduction $ map notReduced [l,bA,x,y] ++ [reduced st]
    _ -> __IMPOSSIBLE__

-- | Extract the underlying path from an inhabitant of the
-- cubical identity types.
primIdPath' :: TCM PrimitiveImpl
primIdPath' = do
  requireCubical CErased ""
  t <- runNamesT [] $
    hPi' "a" (el $ cl primLevel) $ \ a ->
    hPi' "A" (sort . tmSort <$> a) $ \ bA ->
    hPi' "x" (el' a bA) $ \ x ->
    hPi' "y" (el' a bA) $ \ y ->
    el' a (cl primId <#> a <#> bA <@> x <@> y)
    --> el' a (cl primPath <#> a <#> bA <@> x <@> y)

  return $ PrimImpl t $ primFun __IMPOSSIBLE__ 5 $ \case
    [l,bA,x,y,t] -> do
      st <- reduceB' t
      mConId <- getName' builtinConId
      cview <- conidView'
      case cview (unArg x) $ unArg (ignoreBlocking st) of
        Just (_, w) -> redReturn (unArg w)
        _ -> return $ NoReduction $ map notReduced [l,bA,x,y] ++ [reduced st]
    _ -> __IMPOSSIBLE__

-- | Check that a term matches a given predicate on every consistent
-- substitution of interval variables which makes the given cofibration
-- hold.
onEveryFace
  :: Term -- ^ The cofibration @φ@
  -> Term -- ^ The term to test
  -> (Term -> Bool)
  -- ^ The predicate to test with.
  -> ReduceM Bool
onEveryFace phi u p = do
  unview <- intervalUnview'
  let boolToI b = if b then unview IOne else unview IZero
  as <- decomposeInterval phi
  bools <- for as $ \ (bs,ts) -> do
    let u' = listS (IntMap.toAscList $ IntMap.map boolToI bs) `applySubst` u
    t <- reduce2Lam u'
    return $! p $ ignoreBlocking t
  pure (and bools)

doIdKanOp
  :: KanOperation           -- ^ Are we composing or transporting?
  -> FamilyOrNot (Arg Term) -- ^ Level argument
  -> FamilyOrNot (Arg Term, Arg Term, Arg Term)
    -- ^ Domain, left and right endpoints of the identity type
  -> ReduceM (Maybe (Reduced t Term))
doIdKanOp kanOp l bA_x_y = do
  let getTermLocal :: IsBuiltin a => a -> ReduceM Term
      getTermLocal = getTerm $ kanOpName kanOp ++ " for " ++ getBuiltinId builtinId

  unview <- intervalUnview'
  mConId <- getName' builtinConId
  cview <- conidView'
  let isConId t = isJust $ cview __DUMMY_TERM__ t

  sa0 <- reduceB' (kanOpBase kanOp)
  -- TODO: wasteful to compute b even when cheaper checks might fail
  --
  -- Should we go forward with the Kan operation? This is the case when
  -- doing transport always, and when every face fo the partial element
  -- has reduced to @conid@ otherwise. Note that @conidView@ treats
  -- @reflId@ as though it were @conid i1 refl@.
  b <- case kanOp of
    TranspOp{}    -> return True
    HCompOp _ u _ ->
      onEveryFace (unArg . ignoreBlocking . kanOpCofib $ kanOp) (unArg u) isConId

  case mConId of
    Just conid | isConId (unArg . ignoreBlocking $ sa0), b -> (Just <$>) . (redReturn =<<) $ do
      tHComp    <- getTermLocal builtinHComp
      tTrans    <- getTermLocal builtinTrans
      tIMin     <- getTermLocal builtinDepIMin
      idFace    <- getTermLocal builtinIdFace
      idPath    <- getTermLocal builtinIdPath
      tPathType <- getTermLocal builtinPath
      tConId    <- getTermLocal builtinConId

      runNamesT [] $ do
        let
          io = pure $ unview IOne
          iz = pure $ unview IZero
          conId = pure tConId

          eval TranspOp{} l bA phi _ u0 =
            pure tTrans <#> l <@> bA <@> phi <@> u0
          eval HCompOp{} l bA phi u u0 =
            pure tHComp <#> (l <@> io) <#> (bA <@> io) <#> phi <@> u <@> u0

        -- Compute a line of levels. So we can invoke 'eval' uniformly.
        l <- case l of
          IsFam l -> open . unArg $ l
          IsNot l -> open (Lam defaultArgInfo $ NoAbs "_" $ unArg l)

        p0 <- open . unArg $ kanOpBase kanOp

        -- p is the partial element we are extending against. This is
        -- used to compute the resulting cofibration, so we fake a
        -- partial element when doing transport.
        p <- case kanOp of
          HCompOp _ u _ -> do
            u <- open . unArg $ u
            pure $ \i o -> u <@> i <..> o
          TranspOp{} -> do
            pure $ \i o -> p0

        phi <- open . unArg . ignoreBlocking $ kanOpCofib kanOp

        -- Similarly to the fake line of levels above, fake lines of
        -- everything even when we're doing composition, for uniformity
        -- of eval.
        [bA, x, y] <- case bA_x_y of
          IsFam (bA, x, y) -> for [bA, x, y] $ \a ->
            open $ runNames [] $ lam "i" (const (pure $ unArg a))
          IsNot (bA, x, y) -> for [bA, x, y] $ \a ->
            open (Lam defaultArgInfo $ NoAbs "_" $ unArg a)

        -- The resulting path is constant when when
        --    @Σ φ λ o → -- primIdFace p i1 o@
        -- holds. That's why cofibrations have to be closed under Σ,
        -- c.f. primDepIMin.
        cof <- pure tIMin
          <@> phi
          <@> ilam "o" (\o ->
            pure idFace <#> (l <@> io) <#> (bA <@> io) <#> (x <@> io) <#> (y <@> io) <@> (p io o))

        -- Do the Kan operation for our faces in the Path type.
        path <- eval kanOp l
          (lam "i" $ \i -> pure tPathType <#> (l <@> i) <#> (bA <@> i) <@> (x <@> i) <@> (y <@> i))
          phi
          (lam "i" $ \i -> ilam "o" $ \o ->
            pure idPath <#> (l <@> i) <#> (bA <@> i) <#> (x <@> i) <#> (y <@> i) <@> (p i o))
          (pure idPath <#> (l <@> iz) <#> (bA <@> iz) <#> (x <@> iz) <#> (y <@> iz) <@> p0)

        conId <#> (l <@> io) <#> (bA <@> io) <#> (x <@> io) <#> (y <@> io)
          <@> pure cof
          <@> pure path
    _ -> return $ Nothing
