{-# LANGUAGE EmptyCase             #-}
{-# LANGUAGE FlexibleContexts      #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE GADTs                 #-}
{-# LANGUAGE KindSignatures        #-}
{-# LANGUAGE LambdaCase            #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PatternSynonyms       #-}
{-# LANGUAGE RankNTypes            #-}
{-# LANGUAGE ScopedTypeVariables   #-}
{-# LANGUAGE StandaloneDeriving    #-}
{-# LANGUAGE TupleSections         #-}
{-# LANGUAGE TypeApplications      #-}
{-# LANGUAGE TypeInType            #-}
{-# LANGUAGE TypeOperators         #-}
{-# LANGUAGE ViewPatterns          #-}

-- |
-- Module      : Data.Type.List.Sublist
-- Copyright   : (c) Justin Le 2018
-- License     : BSD3
--
-- Maintainer  : justin@jle.im
-- Stability   : experimental
-- Portability : non-portable
--
-- Witnesses regarding sublists of lists.
module Data.Type.List.Sublist (
  -- * Prefix and Suffix
  -- ** Prefix
    Prefix(..), IsPrefix, autoPrefix
  , takeRec, prefixLens, takeIndex, weakenIndex
  , prefixShape
  -- ** Suffix
  , Suffix(..), IsSuffix, autoSuffix
  , dropRec, suffixLens, dropIndex, shiftIndex
  -- * Append
  , Append(..), IsAppend, autoAppend, withAppend
  , prefixToAppend, suffixToAppend
  , appendToPrefix, appendToSuffix, splitAppend
  , appendShape
  -- ** Application
  , splitRec, appendRec, splitRecIso
  , splitIndex
  -- ** Witnesses
  -- *** Singletons
  , pattern AppendWit
  , appendWit, implyAppend, unAppendWit
  -- *** Vinyl
  , pattern AppendWitV
  , appendWitV, implyAppendV, unAppendWitV
  -- *** Both
  , pattern AppendWit'
  , convertAppends
  , AppendedTo
  -- * Interleave
  , Interleave(..), IsInterleave, autoInterleave
  , interleaveRec, unweaveRec, interleaveRecIso
  , injectIndexL, injectIndexR, unweaveIndex
  , interleavedIxes, swapInterleave
  , interleaveShapes
  -- * Subset
  , Subset(..), IsSubset, autoSubset
  , subsetComplement
  , interleaveRToSubset, interleaveLToSubset
  , subsetToInterleaveLeft, subsetToInterleaveRight
  , subsetRec, getSubset
  , subsetShapes
  , subsetIxes
  , weakenSubsetIndex, strengthenSubsetIndex
  ) where

import           Data.Bifunctor
import           Data.Functor.Compose
import           Data.Kind
import           Data.Profunctor
import           Data.Singletons
import           Data.Singletons.Decide
import           Data.Singletons.Prelude.List
import           Data.Singletons.Sigma
import           Data.Type.Functor.Product
import           Data.Type.Predicate
import           Data.Type.Predicate.Auto
import           Data.Type.Predicate.Param
import           Data.Vinyl hiding            ((:~:))
import           GHC.Generics                 ((:+:)(..))
import           Lens.Micro hiding            ((%~))
import           Lens.Micro.Extras
import qualified Data.Vinyl.Recursive         as VR
import qualified Data.Vinyl.TypeLevel         as V

-- | A @'Prefix' as bs@ witnesses that @as@ is a prefix of @bs@.
--
-- Some examples:
--
-- @
-- PreZ                    :: Prefix '[]      '[1,2,3]
-- PreS PreZ               :: Prefix '[1]     '[1,2,3]
-- PreS (PreS PreZ)        :: Prefix '[1,2]   '[1,2,3]
-- PreS (PreS (PreS PreZ)) :: Prefix '[1,2,3] '[1,2,3]
-- @
--
-- Rule of thumb for construction: the number of 'PreS' is the number of
-- items in the prefix.
--
-- This is essentially the first half of an 'Append', but is conceptually
-- easier to work with.
data Prefix :: [k] -> [k] -> Type where
    PreZ :: Prefix '[] as
    PreS :: Prefix  as bs -> Prefix  (a ': as) (a ': bs)

deriving instance Show (Prefix as bs)

-- | A lens into the prefix of a 'Rec'.
prefixLens :: Prefix as bs -> Lens' (Rec f bs) (Rec f as)
prefixLens p = prefixToAppend p $ \a -> splitRecIso a . _1

-- | Take items from a 'Rec' corresponding to a given 'Prefix'.
takeRec :: Prefix as bs -> Rec f bs -> Rec f as
takeRec p = view (prefixLens p)

-- | A type-level predicate that a given list has @as@ as a prefix.
--
-- @since 0.1.2.0
type IsPrefix as = TyPred (Prefix as)

instance Auto (IsPrefix '[]) bs where
    auto = PreZ

instance Auto (IsPrefix as) bs => Auto (IsPrefix (a ': as)) (a ': bs) where
    auto = PreS (auto @_ @(IsPrefix as) @bs)

instance (SDecide k, SingI (as :: [k])) => Decidable (IsPrefix as) where
    decide = case sing @as of
      SNil         -> \_ -> Proved PreZ
      x `SCons` (Sing :: Sing as') -> \case
        SNil -> Disproved $ \case {}
        y `SCons` (ys :: Sing bs') -> case x %~ y of
          Proved Refl -> case decide @(IsPrefix as') ys of
            Proved p -> Proved (PreS p)
            Disproved v -> Disproved $ \case
              PreS p -> v p
          Disproved v -> Disproved $ \case
            PreS _ -> v Refl

-- | Automatically generate a 'Prefix' if @as@ and @bs@ are known
-- statically.
--
-- @since 0.1.2.0
autoPrefix :: forall as bs. Auto (IsPrefix as) bs => Prefix as bs
autoPrefix = auto @_ @(IsPrefix as) @bs

-- | Get the 'Shape' associated with a 'Prefix'.
--
-- @since 0.1.3.0
prefixShape
    :: Prefix as bs
    -> Shape [] as
prefixShape = \case
    PreZ   -> RNil
    PreS p -> Proxy :& prefixShape p

-- | A @'Suffix' as bs@ witnesses that @as@ is a suffix of @bs@.
--
-- Some examples:
--
-- @
-- SufZ                    :: Suffix '[1,2,3] '[1,2,3]
-- SufS SufZ               :: Suffix   '[2,3] '[1,2,3]
-- SufS (SufS SufZ)        :: Suffix     '[3] '[1,2,3]
-- SufS (SufS (SufS SufZ)) :: Suffix      '[] '[1,2,3]
-- @
--
-- Rule of thumb for construction: the number of 'SufS' is the number of
-- items to "drop" before getting the suffix.
--
-- This is essentially the second half of an 'Append', but is conceptually
-- easier to work with.
data Suffix :: [k] -> [k] -> Type where
    SufZ :: Suffix as as
    SufS :: Suffix as bs -> Suffix as (b ': bs)

deriving instance Show (Suffix as bs)

-- | A type-level predicate that a given list has @as@ as a suffix.
--
-- @since 0.1.2.0
type IsSuffix as = TyPred (Suffix as)

instance Auto (IsSuffix as) as where
    auto = SufZ

instance Auto (IsSuffix as) bs => Auto (IsSuffix as) (b ': bs) where
    auto = SufS (auto @_ @(IsSuffix as) @bs)

instance (SDecide k, SingI (as :: [k])) => Decidable (IsSuffix as) where
    decide = \case
      SNil -> case sing @as of
        SNil -> Proved SufZ
        _ `SCons` _ -> Disproved $ \case {}
      _ `SCons` ys -> case decide @(IsSuffix as) ys of
        Proved s    -> Proved $ SufS s
        Disproved v -> Disproved $ \case
          SufZ   -> error "help me"
          SufS s -> v s

-- | Automatically generate a 'Suffix' if @as@ and @bs@ are known
-- statically.
--
-- @since 0.1.2.0
autoSuffix :: forall as bs. Auto (IsSuffix as) bs => Suffix as bs
autoSuffix = auto @_ @(IsSuffix as) @bs

-- | A lens into the suffix of a 'Rec'.
suffixLens :: Suffix as bs -> Lens' (Rec f bs) (Rec f as)
suffixLens p = suffixToAppend p $ \a -> splitRecIso a . _2

-- | Drop items from a 'Rec' corresponding to a given 'Suffix'.
dropRec :: Suffix as bs -> Rec f bs -> Rec f as
dropRec p = view (suffixLens p)

-- | An @'Append' as bs cs@ witnesses that @cs@ is the result of appending
-- @as@ and @bs@.
--
-- Some examples:
--
-- @
-- AppZ                     :: Append '[]  '[1,2]   '[1,2]
-- AppZ                     :: Append '[]  '[1,2,3] '[1,2,3]
-- AppS AppZ                :: Append '[0] '[1,2]   '[0,1,2]
-- @
--
-- Rule of thumb for construction: the number of 'AppS' is the number of
-- items in the /first/ list.
--
-- This basically combines 'Prefix' and 'Suffix'.
data Append :: [k] -> [k] -> [k] -> Type where
    AppZ :: Append '[] as as
    AppS :: Append as bs cs -> Append (a ': as) bs (a ': cs)

deriving instance Show (Append as bs cs)

-- | A type-level predicate that a given list is the result of appending of
-- @as@ and @bs@.
--
-- @since 0.1.2.0
type IsAppend as bs = TyPred (Append as bs)

-- | A parameterized predicate that you can use with 'select': With an
-- @'AppendedTo' as@, you can give @bs@ and get @cs@ in return, where @cs@
-- is the appending of @as@ and @bs@.
--
-- Run it with:
--
-- @
-- 'selectTC' :: SingI as => Sing bs -> 'Σ' [k] ('IsAppend' as bs)
-- @
--
-- 'select' for 'AppendedTo' is pretty much just 'withAppend'.
--
-- @since 0.1.2.0
type AppendedTo as = TyPP (Append as)

instance Auto (IsAppend '[] as) as where
    auto = AppZ

instance Auto (IsAppend as bs) cs => Auto (IsAppend (a ': as) bs) (a ': cs) where
    auto = AppS (auto @_ @(IsAppend as bs) @cs)

instance (SDecide k, SingI (as :: [k]), SingI bs) => Decidable (IsAppend as bs) where
    decide = case sing @as of
      SNil         -> \cs -> case sing @bs %~ cs of
        Proved Refl -> Proved AppZ
        Disproved v -> Disproved $ \case
          AppZ -> v Refl
      x `SCons` (Sing :: Sing as') -> \case
        SNil -> Disproved $ \case {}
        y `SCons` (ys :: Sing bs') -> case x %~ y of
          Proved Refl -> case decide @(IsAppend as' bs) ys of
            Proved p -> Proved (AppS p)
            Disproved v -> Disproved $ \case
              AppS p -> v p
          Disproved v -> Disproved $ \case
            AppS _ -> v Refl

instance SingI as => Decidable (Found (AppendedTo as))
instance SingI as => Provable (Found (AppendedTo as)) where
    prove ys = withAppend (singProd (sing @as)) (singProd ys) $ \s x -> prodSing s :&: x

-- | Automatically generate an 'Append' if @as@, @bs@ and @cs@ are known
-- statically.
--
-- @since 0.1.2.0
autoAppend :: forall as bs cs. Auto (IsAppend as bs) cs => Append as bs cs
autoAppend = auto @_ @(IsAppend as bs) @cs

-- | Witness that @'Append' as bs cs@ implies @(as ++ bs) ~ cs@, using
-- @++@ from "Data.Singletons.Prelude.List".
--
-- @since 0.1.2.0
appendWit :: Append as bs cs -> (as ++ bs) :~: cs
appendWit = \case
    AppZ   -> Refl
    AppS a -> case appendWit a of
      Refl -> Refl

-- | The inverse of 'appendWit': if we know @(as ++ bs) ~ cs@ (using @++@
-- from "Data.Singletons.Prelude.List"), we can create an @'Append' as bs
-- cs@ given structure witnesses 'Sing'.
--
-- @since 0.1.2.0
unAppendWit
    :: (as ++ bs) ~ cs
    => Rec f as
    -> Rec f bs
    -> Append as bs cs
unAppendWit = \case
    RNil    -> \_   -> AppZ
    _ :& xs -> AppS . unAppendWit xs

-- | A useful pattern synonym for using 'Append' with @++@ from
-- "Data.Singletons.Prelude.List".
--
-- As a /pattern/, this brings @(as ++ bs) ~ cs@ into the context whenever
-- you use it to match on an @'Append' as bs cs@.
--
-- As an /expression/, this constructs an @'Append' as bs cs@ as long as
-- you have @(as ++ bs) ~ cs@ in the context.
--
-- @since 0.1.2.0
pattern AppendWit :: forall as bs cs. (RecApplicative as, RecApplicative bs) => (as ++ bs) ~ cs => Append as bs cs
pattern AppendWit <- (appendWit @as @bs @cs -> Refl)
  where
    AppendWit = unAppendWit @as @bs @cs pureShape pureShape
{-# COMPLETE AppendWit #-}

-- | 'appendWit' stated as a 'Predicate' implication.
--
-- @since 0.1.2.0
implyAppend :: IsAppend as bs --> EqualTo (as ++ bs)
implyAppend _ = appendWit

-- | Witness that @'Append' as bs cs@ implies @(as ++ bs) ~ cs@, using
-- @++@ from "Data.Vinyl.TypeLevel".
--
-- @since 0.1.2.0
appendWitV :: Append as bs cs -> (as V.++ bs) :~: cs
appendWitV = \case
    AppZ -> Refl
    AppS a -> case appendWitV a of
      Refl -> Refl

-- | The inverse of 'appendWitV': if we know @(as ++ bs) ~ cs@ (using @++@
-- from "Data.Vinyl.TypeLevel"), we can create an @'Append' as bs cs@ given
-- structure witnesses 'Sing'.
--
-- @since 0.1.2.0
unAppendWitV
    :: (as V.++ bs) ~ cs
    => Rec f as
    -> Rec f bs
    -> Append as bs cs
unAppendWitV = \case
    RNil     -> \_   -> AppZ
    _ :& xs -> AppS . unAppendWitV xs

-- | A useful pattern synonym for using 'Append' with @++@ from
-- "Data.Vinyl.TypeLevel".
--
-- As a /pattern/, this brings @(as ++ bs) ~ cs@ into the context whenever
-- you use it to match on an @'Append' as bs cs@.
--
-- As an /expression/, this constructs an @'Append' as bs cs@ as long as
-- you have @(as ++ bs) ~ cs@ in the context.
--
-- @since 0.1.2.0
pattern AppendWitV :: forall as bs cs. (RecApplicative as, RecApplicative bs) => (as V.++ bs) ~ cs => Append as bs cs
pattern AppendWitV <- (appendWitV @as @bs @cs -> Refl)
  where
    AppendWitV = unAppendWitV @as @bs @cs pureShape pureShape
{-# COMPLETE AppendWitV #-}

-- | Combine the powers of 'AppendWit' and 'AppendWitV' by matching on an
-- 'Append' to witness @(as ++ bs) ~ cs@ for /both/ @++@ from
-- "Data.Singletons.Prelude.List" and "Data.Vinyl.TypeLevel".  This also
-- witnesses that @(as ++ bs) ~ (as ++ bs)@ (for the two different @++@s)
-- by transitive property.
--
-- @since 0.1.2.0
pattern AppendWit' :: forall as bs cs. (RecApplicative as, RecApplicative bs) => ((as ++ bs) ~ cs, (as V.++ bs) ~ cs) => Append as bs cs
pattern AppendWit' <- ((\a -> (a,a)) -> (AppendWit, AppendWitV))
  where
    AppendWit' = AppendWit
{-# COMPLETE AppendWitV #-}

-- | 'appendWitV' stated as a 'Predicate' implication.
--
-- @since 0.1.2.0
implyAppendV :: IsAppend as bs --> EqualTo (as V.++ bs)
implyAppendV _ = appendWitV

-- | Given a witness @'Append' as bs cs@, prove that singleton's @++@ from
-- "Data.Singletons.Prelude.List" is the same as vinyl's @++@
-- "Data.Vinyl.TypeLevel".
convertAppends :: Append as bs cs -> (as ++ bs) :~: (as V.++ bs)
convertAppends a = case appendWit a of
    Refl -> case appendWitV a of
      Refl -> Refl

-- | Given @as@ and @bs@, create an @'Append' as bs cs@ with, with @cs@
-- existentially quantified
withAppend :: Rec f as -> Rec f bs -> (forall cs. Rec f cs -> Append as bs cs -> r) -> r
withAppend = \case
    RNil -> \ys f -> f ys AppZ
    x :& xs -> \ys f -> withAppend xs ys $ \zs a ->
      f (x :& zs) (AppS a)

-- | Get the 'Shape' associated with an 'Append''s prefix.
--
-- @since 0.1.3.0
appendShape
    :: Append as bs cs
    -> Shape [] as
appendShape = \case
    AppZ   -> RNil
    AppS a -> Proxy :& appendShape a

-- | Witness an isomorphism between 'Rec' and two parts that compose it.
--
-- Read this type signature as:
--
-- @
-- 'splitRecIso'
--     :: Append as  bs  cs
--     -> Iso' (Rec f cs) (Rec f as, Rec f bs)
-- @
--
-- This can be used with the combinators from the lens library.
--
-- The 'Append' tells the point to split the 'Rec' at.
splitRecIso
    :: (Profunctor p, Functor f)
    => Append as bs cs
    -> p (Rec g as, Rec g bs) (f (Rec g as, Rec g bs))
    -> p (Rec g cs)           (f (Rec g cs))
splitRecIso a = dimap (splitRec a) ((fmap . uncurry) (appendRec a))

-- | Split a 'Rec' into a prefix and suffix.  Basically 'takeRec'
-- and 'dropRec' combined.
splitRec
    :: Append as bs cs
    -> Rec f cs
    -> (Rec f as, Rec f bs)
splitRec = \case
    AppZ   -> (RNil,)
    AppS a -> \case
      x :& xs -> first (x :&) . splitRec a $ xs

-- | Append two 'Rec's together according to an 'Append'.
appendRec
    :: Append as bs cs
    -> Rec f as
    -> Rec f bs
    -> Rec f cs
appendRec = \case
    AppZ   -> \_ -> id
    AppS a -> \case
      x :& xs -> (x :&) . appendRec a xs

-- | Convert a 'Prefix' to an 'Append', with an existential @bs@.
prefixToAppend
    :: Prefix as cs
    -> (forall bs. Append as bs cs -> r)
    -> r
prefixToAppend = \case
    PreZ   -> ($ AppZ)
    PreS p -> \f -> prefixToAppend p (f . AppS)

-- | Convert a 'Suffix' to an 'Append', with an existential @as@.
suffixToAppend
    :: Suffix bs cs
    -> (forall as. Append as bs cs -> r)
    -> r
suffixToAppend = \case
    SufZ   -> ($ AppZ)
    SufS s -> \f -> suffixToAppend s (f . AppS)

-- | Split an 'Append' into a 'Prefix' and 'Suffix'.  Basically
-- 'appendToPrefix' and 'appendToSuffix' at the same time.
splitAppend
    :: Append as bs cs
    -> (Prefix as cs, Suffix bs cs)
splitAppend = \case
    AppZ   -> (PreZ, SufZ)
    AppS a -> bimap PreS SufS . splitAppend $ a

-- | Convert an 'Append' to a 'Prefix', forgetting the suffix.
appendToPrefix :: Append as bs cs -> Prefix as cs
appendToPrefix = \case
    AppZ   -> PreZ
    AppS a -> PreS . appendToPrefix $ a

-- | Convert an 'Append' to a 'Suffix', forgetting the prefix
appendToSuffix :: Append as bs cs -> Suffix bs cs
appendToSuffix = \case
    AppZ   -> SufZ
    AppS a -> SufS . appendToSuffix $ a

-- | Split an 'Index' by an 'Append'.  If the 'Index' was in the first part
-- of the list, it'll return 'Left'.  If it was in the second part, it'll
-- return 'Right'.
--
-- This is essentially 'takeIndex' and 'dropIndex' at the same time.
splitIndex
    :: Append as bs cs
    -> Index cs x
    -> Either (Index as x) (Index bs x)
splitIndex = \case
    AppZ   -> Right
    AppS a -> \case
      IZ   -> Left IZ
      IS i -> first IS . splitIndex a $ i

-- | Shave off the final inhabitants of an 'Index', keeping only indices
-- a part of a given prefix.  If the index is out of range, 'Nothing' will
-- be returned.
--
-- This is essentially 'splitIndex', but taking only 'Left' results.
takeIndex
    :: Prefix as bs
    -> Index bs x
    -> Maybe (Index as x)
takeIndex p i = prefixToAppend p $ either Just (const Nothing)
                                 . (`splitIndex` i)

-- | Shave off the initial inhabitants of an 'Index', keeping only indices
-- a part of a given suffix  If the index is out of range, 'Nothing' will
-- be returned.
--
-- This is essentially 'splitIndex', but taking only 'Right' results.
dropIndex
    :: Suffix as bs
    -> Index bs x
    -> Maybe (Index as x)
dropIndex s i = suffixToAppend s $ either (const Nothing) Just
                                 . (`splitIndex` i)

-- | An index pointing to a given item in a prefix is also an index
-- pointing to the same item in the full list.  This "weakens" the bounds
-- of an index, widening the list at the end but preserving the original
-- index.  This is the inverse of 'takeIndex'.
weakenIndex
    :: Prefix as bs
    -> Index as x
    -> Index bs x
weakenIndex = \case
    PreZ   -> \case {}
    PreS p -> \case
      IZ   -> IZ
      IS i -> IS (weakenIndex p i)

-- | An index pointing to a given item in a suffix can be transformed into
-- an index pointing to the same item in the full list.  This is the
-- inverse of 'dropIndex'.
shiftIndex
    :: Suffix as bs
    -> Index as x
    -> Index bs x
shiftIndex = \case
    SufZ   -> id
    SufS s -> IS . shiftIndex s

-- | A @'Interleave' as bs cs@ witnesses that @cs@ is @as@ interleaved with
-- @bs@. It is constructed by selectively zipping items from @as@ and @bs@
-- together, like mergesort or riffle shuffle.
--
-- You construct an 'Interleave' from @as@ and @bs@ by picking "which item" from
-- @as@ and @bs@ to add to @cs@.
--
-- Some examples:
--
-- @
-- IntL (IntL (IntR (IntR IntZ))) :: Interleave '[1,2] '[3,4] '[1,2,3,4]
-- IntR (IntR (IntL (IntL IntZ))) :: Interleave '[1,2] '[3,4] '[3,4,1,2]
-- IntL (IntR (IntL (IntR IntZ))) :: Interleave '[1,2] '[3,4] '[1,3,2,4]
-- IntR (IntL (IntR (IntL IntZ))) :: Interleave '[1,2] '[3,4] '[3,1,4,2]
-- @
--
-- @since 0.1.1.0
data Interleave :: [k] -> [k] -> [k] -> Type where
    IntZ :: Interleave '[] '[] '[]
    IntL :: Interleave as bs cs -> Interleave (a ': as) bs        (a ': cs)
    IntR :: Interleave as bs cs -> Interleave as        (b ': bs) (b ': cs)

deriving instance Show (Interleave as bs cs)

-- | A type-level predicate that a given list is the "interleave" of @as@
-- and @bs@.
--
-- @since 0.1.2.0
type IsInterleave as bs = TyPred (Interleave as bs)

instance Auto (IsInterleave '[] '[]) '[] where
    auto = IntZ

instance Auto (IsInterleave as bs) cs => Auto (IsInterleave (a ': as) bs) (a ': cs) where
    auto = IntL (auto @_ @(IsInterleave as bs) @cs)

instance Auto (IsInterleave as bs) cs => Auto (IsInterleave as (b ': bs)) (b ': cs) where
    auto = IntR (auto @_ @(IsInterleave as bs) @cs)

instance (SDecide k, SingI (as :: [k]), SingI bs) => Decidable (IsInterleave as bs) where
    decide = case sing @as of
      SNil -> case sing @bs of
        SNil -> \case
          SNil -> Proved IntZ
          _ `SCons` _ -> Disproved $ \case {}
        y `SCons` (Sing :: Sing bs') -> \case
          z `SCons` (zs :: Sing cs') -> case y %~ z of
            Proved Refl -> case decide @(IsInterleave '[] bs') zs of
              Proved i -> Proved $ IntR i
              Disproved v -> Disproved $ \case
                IntR i -> v i
            Disproved v -> Disproved $ \case
              IntR _ -> v Refl
          SNil -> Disproved $ \case {}
      x `SCons` (Sing :: Sing as') -> case sing @bs of
        SNil -> \case
          z `SCons` (zs :: Sing cs') -> case x %~ z of
            Proved Refl -> case decide @(IsInterleave as' '[]) zs of
              Proved i -> Proved $ IntL i
              Disproved v -> Disproved $ \case
                IntL i -> v i
            Disproved v -> Disproved $ \case
              IntL _ -> v Refl
          SNil -> Disproved $ \case {}
        y `SCons` (Sing :: Sing bs') -> \case
          SNil -> Disproved $ \case {}
          z `SCons` (zs :: Sing cs') -> case x %~ z of
            Proved Refl -> case decide @(IsInterleave as' bs) zs of
              Proved i    -> Proved $ IntL i
              Disproved v -> case y %~ z of
                Proved Refl -> case decide @(IsInterleave as bs') zs of
                  Proved i -> Proved $ IntR i
                  Disproved u -> Disproved $ \case
                    IntL i -> v i
                    IntR i -> u i
                Disproved u -> Disproved $ \case
                  IntL i -> v i
                  IntR _ -> u Refl
            Disproved v -> case y %~ z of
              Proved Refl -> case decide @(IsInterleave as bs') zs of
                Proved i -> Proved $ IntR i
                Disproved u -> Disproved $ \case
                  IntL _ -> v Refl
                  IntR i -> u i
              Disproved u -> Disproved $ \case
                IntL _ -> v Refl
                IntR _ -> u Refl

-- | Automatically generate an 'Interleave' if @as@ and @bs@ are known
-- statically.
--
-- @since 0.1.2.0
autoInterleave :: forall as bs cs. Auto (IsInterleave as bs) cs => Interleave as bs cs
autoInterleave = auto @_ @(IsInterleave as bs) @cs

-- | Given two 'Rec's, interleave the two to create a combined 'Rec'.
--
-- @since 0.1.1.0
interleaveRec :: Interleave as bs cs -> Rec f as -> Rec f bs -> Rec f cs
interleaveRec = \case
    IntZ -> \case
      RNil -> \case
        RNil -> RNil
    IntL m -> \case
      x :& xs -> \ys -> x :& interleaveRec m xs ys
    IntR m -> \xs -> \case
      y :& ys -> y :& interleaveRec m xs ys

-- | Given a 'Rec', disinterleave it into two 'Rec's corresponding to an
-- 'Interleave'.
--
-- @since 0.1.1.0
unweaveRec :: Interleave as bs cs -> Rec f cs -> (Rec f as, Rec f bs)
unweaveRec = \case
    IntZ -> \case
      RNil -> (RNil, RNil)
    IntL m -> \case
      x :& xs -> first  (x :&) . unweaveRec m $ xs
    IntR m -> \case
      x :& xs -> second (x :&) . unweaveRec m $ xs

-- | Turn an 'Interleave' into a 'Rec' of indices from either sublist.
--
-- Warning: O(n^2)
--
-- @since 0.1.2.0
interleavedIxes :: Interleave as bs cs -> Rec (Index as :+: Index bs) cs
interleavedIxes = \case
    IntZ   -> RNil
    IntL i -> L1 IZ :& VR.rmap (\case L1 i' -> L1 (IS i'); R1 j -> R1 j)
                               (interleavedIxes i)
    IntR j -> R1 IZ :& VR.rmap (\case L1 i -> L1 i; R1 j' -> R1 (IS j'))
                               (interleavedIxes j)

-- | Interleave an 'Index' on @as@ into a full index on @cs@, which is @as@
-- interleaved with @bs@.
--
-- @since 0.1.1.0
injectIndexL :: Interleave as bs cs -> Index as a -> Index cs a
injectIndexL = \case
    IntZ -> \case {}
    IntL m -> \case
      IZ -> IZ
      IS i -> IS (injectIndexL m i)
    IntR m -> IS . injectIndexL m

-- | Interleave an 'Index' on @bs@ into a full index on @cs@, which is @as@
-- interleaved with @bs@.
--
-- @since 0.1.1.0
injectIndexR :: Interleave as bs cs -> Index bs b -> Index cs b
injectIndexR = \case
    IntZ -> \case {}
    IntL m -> IS . injectIndexR m
    IntR m -> \case
      IZ -> IZ
      IS i -> IS (injectIndexR m i)

-- | Given an index on @cs@, disinterleave it into either an index on @as@
-- or on @bs@.
--
-- @since 0.1.1.0
unweaveIndex :: Interleave as bs cs -> Index cs c -> Either (Index as c) (Index bs c)
unweaveIndex = \case
    IntZ -> \case {}
    IntL m -> \case
      IZ   -> Left IZ
      IS i -> first IS $ unweaveIndex m i
    IntR m -> \case
      IZ   -> Right IZ
      IS i -> second IS $ unweaveIndex m i

-- | Witness an isomorphism between 'Rec' and two parts that interleave it.
--
-- Read this type signature as:
--
-- @
-- 'interleaveRecIso'
--     :: Interleave as  bs  cs
--     -> Iso' (Rec f cs) (Rec f as, Rec f bs)
-- @
--
-- This can be used with the combinators from the lens library.
--
-- The 'Interleave' tells how to unweave the 'Rec'.
--
-- @since 0.1.1.0
interleaveRecIso
    :: (Profunctor p, Functor f)
    => Interleave as bs cs
    -> p (Rec g as, Rec g bs) (f (Rec g as, Rec g bs))
    -> p (Rec g cs)           (f (Rec g cs))
interleaveRecIso m = dimap (unweaveRec m) ((fmap . uncurry) (interleaveRec m))

-- | Swap the two halves of an 'Interleave'.
--
-- @since 0.1.3.0
swapInterleave
    :: Interleave as bs cs
    -> Interleave bs as cs
swapInterleave = \case
    IntZ   -> IntZ
    IntL i -> IntR $ swapInterleave i
    IntR i -> IntL $ swapInterleave i

-- | Get the 'Shape's associated with an 'Interleave'.
--
-- @since 0.1.3.0
interleaveShapes
    :: Interleave as bs cs
    -> (Shape [] as, Shape [] bs, Shape [] cs)
interleaveShapes = \case
    IntZ   -> (RNil, RNil, RNil)
    IntL i ->
      let (as         , bs         , cs         ) = interleaveShapes i
      in  (Proxy :& as, bs         , Proxy :& cs)
    IntR i ->
      let (as         , bs         , cs         ) = interleaveShapes i
      in  (as         , Proxy :& bs, Proxy :& cs)

-- | A @'Subset' as bs@ witnesses that @as@ is some subset of @bs@, with
-- items in the same order.  It is constructed by specifying
-- what item to include or exclude in @bs@ from @as@.  It is essentially
-- 'Interleave', but without one of the two initial parameters.
--
-- You construct an 'Subset' from @cs@ by picking "which item" from
-- @bs@ to add to @as@.
--
-- Some examples:
--
-- @
-- SubsetNo  (SubsetNo  (SubsetNo  SubsetNil)) :: Subset '[]      '[1,2,3]
-- SubsetYes (SubsetNo  (SubsetNo  SubsetNil)) :: Subset '[1]     '[1,2,3]
-- SubsetNo  (SubsetNo  (SubsetYes SubsetNil)) :: Subset '[3]     '[1,2,3]
-- SubsetYes (SubsetNo  (SubsetYes SubsetNil)) :: Subset '[1,3]   '[1,2,3]
-- SubsetYes (SubsetYes (SubsetYes SubsetNil)) :: Subset '[1,2,3] '[1,2,3]
-- @
--
-- @since 0.1.3.0
data Subset :: [k] -> [k] -> Type where
    SubsetNil :: Subset '[] '[]
    SubsetNo  :: Subset as bs -> Subset as        (b ': bs)
    SubsetYes :: Subset as bs -> Subset (a ': as) (a ': bs)

-- | Drop the right side of an 'Interleave', leaving only the left side.
interleaveLToSubset :: Interleave as bs cs -> Subset as cs
interleaveLToSubset = \case
    IntZ   -> SubsetNil
    IntL i -> SubsetYes . interleaveLToSubset $ i
    IntR i -> SubsetNo  . interleaveLToSubset $ i

-- | Drop the left side of an 'Interleave', leaving only the right side.
interleaveRToSubset :: Interleave as bs cs -> Subset bs cs
interleaveRToSubset = \case
    IntZ   -> SubsetNil
    IntL i -> SubsetNo  . interleaveRToSubset $ i
    IntR i -> SubsetYes . interleaveRToSubset $ i

-- | Convert a 'Subset' into an left 'Interleave', recovering the dropped
-- items.
subsetToInterleaveLeft
    :: Subset as cs
    -> (forall bs. Interleave as bs cs -> r)
    -> r
subsetToInterleaveLeft = \case
    SubsetNil   -> \f -> f IntZ
    SubsetNo  s -> \f -> subsetToInterleaveLeft s (f . IntR)
    SubsetYes s -> \f -> subsetToInterleaveLeft s (f . IntL)

-- | Convert a 'Subset' into an right 'Interleave', recovering the dropped
-- items.
subsetToInterleaveRight
    :: Subset bs cs
    -> (forall as. Interleave as bs cs -> r)
    -> r
subsetToInterleaveRight = \case
    SubsetNil   -> \f -> f IntZ
    SubsetNo  s -> \f -> subsetToInterleaveRight s (f . IntL)
    SubsetYes s -> \f -> subsetToInterleaveRight s (f . IntR)

-- | @as@ is a subset of @cs@; this function recovers @bs@, the subset of
-- @cs@ that is not @as@.
subsetComplement
    :: Subset as cs
    -> (forall bs. Subset bs cs -> r)
    -> r
subsetComplement = \case
    SubsetNil   -> \f -> f SubsetNil
    SubsetNo  s -> \f -> subsetComplement s (f . SubsetYes)
    SubsetYes s -> \f -> subsetComplement s (f . SubsetNo)

deriving instance Show (Subset as bs)


-- | A type-level predicate that a given list is a "superset" of @as@, in
-- correct order
--
-- @since 0.1.2.0
type IsSubset as = TyPred (Subset as)

instance Auto (IsSubset '[]) '[] where
    auto = SubsetNil

instance Auto (IsSubset as) bs => Auto (IsSubset as) (b ': bs) where
    auto = SubsetNo (auto @_ @(IsSubset as) @bs)

instance Auto (IsSubset as) bs => Auto (IsSubset (a ': as)) (a ': bs) where
    auto = SubsetYes (auto @_ @(IsSubset as) @bs)

instance (SDecide k, SingI (as :: [k])) => Decidable (IsSubset as) where
    decide = case sing @as of
      SNil -> \case
        SNil -> Proved SubsetNil
        _ `SCons` ys -> case decide @(IsSubset '[]) ys of
          Proved s    -> Proved $ SubsetNo s
          Disproved v -> Disproved $ \case
            SubsetNo s -> v s
      x `SCons` (Sing :: Sing as') -> \case
        SNil -> Disproved $ \case {}
        y `SCons` ys -> case x %~ y of
          Proved Refl -> case decide @(IsSubset as') ys of
            Proved s    -> Proved $ SubsetYes s
            Disproved v -> case decide @(IsSubset as) ys of
              Proved s    -> Proved $ SubsetNo s
              Disproved u -> Disproved $ \case
                SubsetNo s  -> u s
                SubsetYes s -> v s
          Disproved v -> case decide @(IsSubset as) ys of
            Proved s    -> Proved $ SubsetNo s
            Disproved u -> Disproved $ \case
              SubsetNo s  -> u s
              SubsetYes _ -> v Refl

-- | Automatically generate an 'Subset' if @as@ and @bs@ are known
-- statically.
autoSubset :: forall as bs. Auto (IsSubset as) bs => Subset as bs
autoSubset = auto @_ @(IsSubset as) @bs

-- | A lens into a subset of a record, indicated by a 'Subset'.
subsetRec :: Subset as bs -> Lens' (Rec f bs) (Rec f as)
subsetRec = \case
    SubsetNil   -> id
    SubsetNo  s -> \f -> \case
      x :& xs -> (x :&) <$> subsetRec s f xs
    SubsetYes s -> \f -> \case
      x :& xs -> fmap (uncurry (:&))
               . getCompose
               . subsetRec s (Compose . fmap (\(y :& ys) -> (y,ys)) . f . (x :&))
               $ xs

-- | Take a subset out of a 'Rec'.  An alias for @'view' ('subsetRec' s)@.
getSubset
    :: Subset as bs
    -> Rec f bs
    -> Rec f as
getSubset = view . subsetRec

-- | Get all of the indices of all the items in a 'Subset'.
subsetIxes
    :: Subset as bs
    -> Rec (Index bs) as
subsetIxes s = getSubset s . imapProd const $ sp
  where
    (_, sp) = subsetShapes s

-- | Get the 'Shape's associated with a 'Subset'.
subsetShapes
    :: Subset as bs
    -> (Shape [] as, Shape [] bs)
subsetShapes = \case
    SubsetNil    -> (RNil, RNil)
    SubsetNo   s -> second (Proxy :&) $ subsetShapes s
    SubsetYes  s -> bimap (Proxy :&) (Proxy :&) $ subsetShapes s

-- | Because @as@ is a subset of @bs@, an index into @as@ should also be an
-- index into @bs@.  This performs that transformation.
--
-- This is like a version of 'injectIndexL' or 'injectIndexR', for
-- 'Subset'.
weakenSubsetIndex
    :: Subset as bs
    -> Index as a
    -> Index bs a
weakenSubsetIndex = \case
    SubsetNil   -> \case {}
    SubsetNo  s -> IS . weakenSubsetIndex s
    SubsetYes s -> \case
      IZ   -> IZ
      IS i -> IS $ weakenSubsetIndex s i

-- | Because @as@ is a subset of @bs@, we can /sometimes/ transform an
-- index into @bs@ into an index into @as@.  This performs that
-- transformation.  If it succeeds, it means that the index in @bs@ also
-- exists in @as@; otherwise, it means that the index in @bs@ was excluded
-- from @as@.
--
-- Note that if the index into @a@ was excluded from @as@, it doesn't
-- necessarily mean that there is no @a@ in @bs@ --- @bs@ could contain
-- a duplicate that was included into @as@.  This converts into an index to
-- the exact same item (positionlly) in the list, if it is possible.
--
-- This is like a version of 'unweaveIndex', but for 'Subset'.
strengthenSubsetIndex
    :: Subset as bs
    -> Index bs a
    -> Maybe (Index as a)
strengthenSubsetIndex = \case
    SubsetNil   -> \case {}
    SubsetNo  s -> \case
      IZ   -> Nothing
      IS i -> strengthenSubsetIndex s i
    SubsetYes s -> \case
      IZ   -> Just IZ
      IS i -> IS <$> strengthenSubsetIndex s i