packages feed

libasterix-0.14.0: src/Asterix/Coding.hs

-- |
-- Module: Asterix.Coding
--
-- Asterix data structures and functions to decode, encode and
-- manipulate asterix data.
--
-- The 'U' prefix in some types stands for 'Untyped' version of data, in
-- contrast to 'Typed' versions, for example `Variation` and `UVariation`.
--
-- In some data structures, the fields are stored redundantly,
-- to optimize some parsing/unparsing scenarios.

{-# LANGUAGE AllowAmbiguousTypes    #-}
{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE LambdaCase             #-}
{-# LANGUAGE MultiWayIf             #-}
{-# LANGUAGE OverloadedStrings      #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE UndecidableInstances   #-}

module Asterix.Coding
( module Asterix.Coding
, module Asterix.Schema
, module Asterix.BitString
, module Asterix.Base
) where

import           Control.Applicative
import           Control.Monad
import           Data.Bool
import           Data.ByteString     (ByteString)
import           Data.Char           (isAsciiUpper, isDigit, ord)
import           Data.Coerce
import           Data.Kind           (Type)
import qualified Data.List.NonEmpty  as NE
import           Data.Maybe
import           Data.Proxy
import qualified Data.Text           as T
import           Data.Type.Bool
import           Data.Type.Equality
import           GHC.TypeLits

import           Asterix.Base
import           Asterix.BitString
import           Asterix.Schema

-- | Untyped variation.
data UVariation
    = UElement Bits
    | UGroup [UItem]
    | UExtended SBuilder [Maybe UItem]
    | URepetitive SBuilder [UVariation]
    | UExplicit SBuilder Bits
    | UCompound SBuilder [Maybe UNonSpare]
    deriving Show

-- | Untyped 'rule', capturing a 'Variation'.
newtype URuleVar = URuleVar { unURuleVar :: UVariation }
    deriving Show

-- | Untyped NonSpare item.
newtype UNonSpare = UNonSpare { unUNonSpare :: URuleVar }
    deriving Show

-- | Untyped item.
data UItem
    = USpare Bits
    | UItem UNonSpare
    deriving Show

-- | Untyped record.
data URecord = URecord
    { uRecUnparse :: SBuilder
    , uRecItems   :: [Maybe (RecordItem UNonSpare)]
    } deriving Show

-- | Untyped expansion.
data UExpansion = UExpansion
    { uExpUnparse :: SBuilder
    , uExpItems   :: [Maybe UNonSpare]
    } deriving Show

-- | In case of multiple uaps, we keep UAP name in addition to the record.
data UDatablock = UDatablock
    { uDatablockUnparse :: SBuilder
    , uDatablockRecords :: [(Maybe VText, URecord)]
    } deriving Show

-- Typed wrappers around U-version of data structures

-- | Typed variation.
newtype Variation (t :: TVariation) = Variation { unVariation :: UVariation }
    deriving Show

-- | Typed rule variation.
newtype RuleVar (t1 :: TRule t2) = RuleVar { unRuleVar :: URuleVar }
    deriving Show

-- | Typed nonSpare item.
newtype NonSpare (t :: TNonSpare) = NonSpare { unNonSpare :: UNonSpare }
    deriving Show

-- | Typed item.
newtype Item (t :: TItem) = Item { unItem :: UItem }
    deriving Show

-- | Typed record.
newtype Record (t :: TRecord) = Record { unRecord :: URecord }
    deriving Show

-- | Typed expansion.
newtype Expansion (t :: TExpansion) = Expansion { unExpansion :: UExpansion }
    deriving Show

-- | Typed datablock.
newtype Datablock (db :: TDatablock) = Datablock { unDatablock :: UDatablock }
    deriving Show

-- IsEmpty instances

instance IsEmpty
    (NonSpare ('GNonSpare name title ('GContextFree ('GRepetitive a b))))
  where
    isEmpty (NonSpare (UNonSpare (URuleVar var))) = case var of
        URepetitive _bld lst -> null lst
        _                    -> intError

instance IsEmpty
    (NonSpare ('GNonSpare name title ('GContextFree ('GCompound lst))))
  where
    isEmpty (NonSpare (UNonSpare (URuleVar var))) = case var of
        UCompound _bld lst -> null $ catMaybes lst
        _                  -> intError

instance IsEmpty URecord where
    isEmpty = null . catMaybes . uRecItems

instance IsEmpty (Record t) where
    isEmpty = isEmpty . unRecord

-- Unparsing to 'Bits' instances

instance Unparsing Bits UVariation where
    unparse = \case
        UElement bits -> bits
        UGroup lst -> case NE.nonEmpty lst of
            Nothing -> toBits @ByteString mempty
            Just ne -> concatBits (fmap unparse ne)
        UExtended b _ -> byteStringToBits $ builderToByteStringSlow $ sbData b
        URepetitive b _ -> byteStringToBits $ builderToByteStringSlow $ sbData b
        UExplicit b _ -> byteStringToBits $ builderToByteStringSlow $ sbData b
        UCompound b _ -> byteStringToBits $ builderToByteStringSlow $ sbData b

instance Unparsing Bits URuleVar where
    unparse = unparse . unURuleVar

instance Unparsing Bits UNonSpare where
    unparse = unparse . unUNonSpare

instance Unparsing Bits UItem where
    unparse = \case
        USpare b -> b
        UItem nsp -> unparse nsp

instance Unparsing Bits URecord where
    unparse
        = byteStringToBits
        . builderToByteStringSlow
        . sbData
        . uRecUnparse

instance Unparsing Bits UExpansion where
    unparse
        = byteStringToBits
        . builderToByteStringSlow
        . sbData
        . uExpUnparse

instance Unparsing Bits UDatablock where
    unparse
        = byteStringToBits
        . builderToByteStringSlow
        . sbData
        . uDatablockUnparse

instance Unparsing Bits (Variation t) where
    unparse = unparse . unVariation

instance Unparsing Bits (RuleVar t) where
    unparse = unparse . unRuleVar

instance Unparsing Bits (NonSpare t) where
    unparse = unparse . unNonSpare

instance Unparsing Bits (Item t) where
    unparse = unparse . unItem

instance Unparsing Bits (Record t) where
    unparse = unparse . unRecord

instance Unparsing Bits (Expansion t) where
    unparse = unparse . unExpansion

instance Unparsing Bits (Datablock db) where
    unparse = unparse . unDatablock

-- Unparsing to 'SBuilder' instances

instance Unparsing SBuilder (RecordItem UNonSpare) where
    unparse = \case
        RecordItem nsp -> bitsToSBuilder $ unparse nsp
        RecordItemSpare -> mempty
        RecordItemRFS lst -> numItems <> items
          where
            numItems = word8ToSBuilder (fromIntegral (length lst))
            items = mconcat $ do
                (frn, nsp) <- lst
                pure
                    ( word8ToSBuilder frn
                   <> bitsToSBuilder (unparse nsp))

instance Unparsing SBuilder URecord where
    unparse = uRecUnparse

instance Unparsing SBuilder UExpansion where
    unparse = uExpUnparse

instance Unparsing SBuilder UDatablock where
    unparse = uDatablockUnparse

instance Unparsing SBuilder (Record t) where
    unparse = unparse . unRecord

instance Unparsing SBuilder (Expansion t) where
    unparse = unparse . unExpansion

instance Unparsing SBuilder (Datablock db) where
    unparse = unparse . unDatablock

-- Num instances
-- Normally, only 'fromInteger' method is required,
-- but the existing Num typeclass makes it possible
-- for better syntactic sugar, without function name.

instance
    ( KnownNat8 o
    , KnownNat n
    ) => Num (Variation ('GElement o n rc)) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger = Variation . UElement . integerToBits
        (unInt8 $ natVal8 (Proxy @o))
        (fromInteger $ natVal (Proxy @n))

instance Num (Variation ('GGroup o '[])) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger _ = Variation $ UGroup []

instance
    ( Num (Item t)
    , Num (Variation ('GGroup o ts))
    , KnownNat (BitSizeOf ts)
    ) => Num (Variation ('GGroup o (t ': ts))) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger i =
        let n = natVal (Proxy @(BitSizeOf ts))
            (a, b) = divMod i (2 ^ n)
            x = unItem $ fromInteger @(Item t) a
            xs = case fromInteger @(Variation ('GGroup o ts)) b of
                Variation (UGroup lst) -> lst
                _                      -> intError
        in Variation $ UGroup (x : xs)

instance
    ( Num (Variation t)
    ) => Num (RuleVar ('GContextFree t)) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger = RuleVar . URuleVar . unVariation
        . fromInteger @(Variation t)

instance
    ( Num (Variation t)
    ) => Num (RuleVar ('GDependent ts1 t ts2)) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger = RuleVar . URuleVar . unVariation
        . fromInteger @(Variation t)

instance
    ( Num (RuleVar rv)
    ) => Num (NonSpare ('GNonSpare name title rv)) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger = NonSpare . UNonSpare . unRuleVar . fromInteger @(RuleVar rv)

instance
    ( KnownNat8 o
    , KnownNat n
    ) => Num (Item ('GSpare o n)) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger = Item . USpare . integerToBits
        (unInt8 $ natVal8 (Proxy @o))
        (fromInteger $ natVal (Proxy @n))

instance
    ( Num (NonSpare nsp)
    ) => Num (Item ('GItem nsp)) where
    a + b = fromInteger (unparseToNum a + unparseToNum b)
    a * b = fromInteger (unparseToNum a * unparseToNum b)
    abs = fromInteger . abs . unparseToNum
    signum = fromInteger . signum . unparseToNum
    negate = fromInteger . negate . unparseToNum
    fromInteger = Item . UItem . unNonSpare . fromInteger @(NonSpare nsp)

-- | The Asterix.Base module performs the actual parsing.
-- This structure tells how to store parsing results.
parsingStore :: ParsingStore UVariation URuleVar UNonSpare UItem URecord UExpansion
parsingStore = ParsingStore
    -- variations
    { psElement    = UElement
    , psGroup      = \_bits lst -> UGroup lst
    , psExtended   = UExtended . bitsToSBuilder
    , psRepetitive = URepetitive . bitsToSBuilder
    , psExplicit   = UExplicit . bitsToSBuilder
    , psCompound   = \rawFspec rawItems items -> UCompound
        (bitsToSBuilder rawFspec <> bitsToSBuilder rawItems) items

    -- rule variation
    , psRuleVar = URuleVar

    -- non-spare
    , psNsp = UNonSpare

    -- items
    , psSpare = USpare
    , psItem  = UItem

    -- record
    , psRecord = \rawFspec rawItems items -> URecord
        (bitsToSBuilder rawFspec <> bitsToSBuilder rawItems) items

    -- expansion
    , psExpansion = \rawFspec rawItems items -> UExpansion
        (bitsToSBuilder rawFspec <> bitsToSBuilder rawItems) items
    }

-- | ParsingM with some type parameters instantiated.
type Parsing pm = ParsingM pm UVariation URuleVar UNonSpare UItem URecord UExpansion

-- | The actual parsing function.
parse :: forall pm r.
    ( KnownParsingMode pm
    )
    => Parsing pm r           -- ^ parsing action, returning result 'r'
    -> ByteString             -- ^ input
    -> Either ParsingError r  -- ^ parsing result
parse act bs = do
    (result, o) <- runParsing act (Env parsingStore bs) 0
    case parsingMode @pm Proxy of
        StrictParsing -> case o == endOffset bs of
            True  -> pure result
            False -> Left "remaining bits"
        PartialParsing -> pure result

-- Constructing

-- | Type level index.
data Ix
    = IxItem Symbol
    | IxRfs Nat

-- | Indexed value.
newtype Indexed (ix :: Ix) a = Indexed a
    deriving (Eq, Show)

-- | Named value.
type Named name = Indexed ('IxItem name)

-- | Random field sequencing.
type Rfs cnt ts = Indexed ('IxRfs cnt) (HList ts)

deriving instance (Num a) => Num (Indexed ('IxItem name) a)

-- | Helper function to construct (Named) items.
item :: forall name a. a -> Named name a
item = Indexed

-- | Helper function to construct RFS.
rfs :: forall cnt ts. HList ts -> Rfs cnt ts
rfs = Indexed

-- | Reserved spare bits might contain non-zero value.
newtype AbuseSpare a = AbuseSpare a
    deriving (Eq, Show, Num)

-- | Set spare bits to any value.
abuseSpare :: a -> AbuseSpare a
abuseSpare = AbuseSpare

-- | Construct spare bits.
spare :: Num a => AbuseSpare a
spare = abuseSpare 0

-- | Data structure, representing Fx bit.
data Fx = Fx
    deriving (Eq, Show)

-- | Fx bit constructor.
fx :: Fx
fx = Fx

-- Construct string

-- | Function name overloading for different resulting types.
class MkString t where
    string :: String -> t

instance
    ( KnownNat8 o
    , KnownNat n
    , Schema st GStringType
    ) => MkString (Variation ('GElement o n ('GContextFree
        ('GContentString st))))
  where
    string val = Variation $ UElement $ integerToBits
        (unInt8 $ natVal8 @o Proxy)
        (fromIntegral $ natVal @n Proxy)
        (foldInt 0 $ unChar <$> padAndStrip val)
      where

        padChar :: Char
        padChar = case schema @st Proxy of
            GStringAscii -> ' '
            GStringICAO  -> ' '
            GStringOctal -> '0'

        bpc :: Int
        bpc = bitsPerChar (schema @st Proxy)

        foldInt :: Integer -> [Integer] -> Integer
        foldInt acc = \case
            [] -> acc
            (x : xs) -> foldInt (acc*(2^bpc) + x) xs

        unChar :: Char -> Integer
        unChar ch = fromIntegral $ case schema @st Proxy of
            GStringAscii -> ord ch
            GStringICAO -> if
                | isAsciiUpper ch -> 0x01 + ord ch - ord 'A'
                | ch == ' '       -> 0x20
                | isDigit ch      -> 0x30 + ord ch - ord '0'
                | otherwise       -> 0
            GStringOctal -> ord ch - ord '0'

        padAndStrip :: String -> String
        padAndStrip s = take n (s <> repeat padChar) where
            n = fromIntegral (natVal @n Proxy) `div` bpc

instance
    ( MkString (Variation var)
    ) => MkString (RuleVar ('GContextFree var)) where
    string = RuleVar . URuleVar . unVariation . string @(Variation var)

instance
    ( MkString (RuleVar rv)
    ) => MkString (NonSpare ('GNonSpare name title rv)) where
    string = NonSpare . UNonSpare . unRuleVar . string @(RuleVar rv)

instance
    ( MkString (NonSpare nsp)
    ) => MkString (Item ('GItem nsp)) where
    string = Item . UItem . unNonSpare . string @(NonSpare nsp)

instance
    ( MkString val
    , item ~ Item ('GItem nsp)
    , nsp ~ 'GNonSpare name2 title rv
    , name1 ~ name2
    ) => MkString (Named name1 val)
  where
    string = item . string @val

-- Construct quantity

-- | A Double wrapper with statically known unit, such as NM.
newtype Quantity (unit :: Symbol) (ruleIx :: (Maybe Nat)) = Quantity
    { unQuantity :: Double
    } deriving (Eq, Ord, Show, Num, Fractional)

-- There is a lot of code just to extract the lsb out of the type.
-- This would otherwise be easy to do on value level schema, however
-- we want type safety on 'unit' matching.

-- | Helper class to extract 'lsb' value, required for quantity function.
class GetLsb unit ruleIx t where
    getLsb :: VNumber

instance
    ( Schema lsb VNumber
    , ruleIx ~ 'Nothing
    , unit1 ~ unit2
    ) => GetLsb unit1 ruleIx (Variation
        ('GElement o n ('GContextFree ('GContentQuantity sig lsb unit2))))
  where
    getLsb = schema @lsb Proxy

-- | Helper class to handle dependent cases.
class GetLsb2 (flag :: Bool) (unit :: Symbol) (ix :: Nat) (rules :: [k])
  where
    getLsb2 :: Proxy flag -> Proxy unit -> Proxy ix -> Proxy rules -> VNumber

instance
    ( ix ~ 0
    , unit1 ~ unit2
    , Schema lsb VNumber
    ) => GetLsb2 'True unit1 ix ( '( val, 'GContentQuantity sig lsb unit2) ': ts)
  where
    getLsb2 _ _ _ _ = schema @lsb Proxy

instance
    ( iy ~ ix - 1
    , flag ~ (iy == 0)
    , GetLsb2 flag unit iy ts
    ) => GetLsb2 'False unit ix (t ': ts)
  where
    getLsb2 _ _ _ _
        = getLsb2 (Proxy @flag) (Proxy @unit) (Proxy @iy) (Proxy @ts)

-- We can now define GetLsb instance using the auxilary GetLsb2
instance
    ( ruleIx ~ 'Just ix
    , flag ~ (ix == 0)
    , GetLsb2 flag unit ix rules
    ) => GetLsb unit ruleIx (Variation
        ('GElement o n ('GDependent sel dt rules)))
  where
    getLsb = getLsb2 (Proxy @flag) (Proxy @unit) (Proxy @ix) (Proxy @rules)

-- | Function name overloading for different output types.
class MkQuantity unit ruleIx t where
    quantity :: Quantity unit ruleIx -> t

-- | When converting from Double to t, the 'sig' field is not important.
-- It only becomes important when converting back from t to Double.
processQuantity :: Int8 -> Int -> Double -> Quantity unit ruleIx -> Variation t
processQuantity (Int8 o) n lsb = Variation . UElement . integerToBits o n
    . round . (/ lsb) . unQuantity

instance
    ( KnownNat8 o
    , KnownNat n
    , GetLsb unit ruleIx (Variation (GElement o n rc))
    ) => MkQuantity unit ruleIx (Variation ('GElement o n rc))
  where
    quantity =
        let lsb = getLsb @unit @ruleIx @(Variation ('GElement o n rc))
        in processQuantity
            (natVal8 @o Proxy)
            (fromIntegral $ natVal @n Proxy)
            (evalNum lsb)

instance
    ( MkQuantity unit ix (Variation var)
    ) => MkQuantity unit ix (RuleVar ('GContextFree var))
  where
    quantity = RuleVar . URuleVar . unVariation
        . quantity @unit @ix @(Variation var)

instance
    ( MkQuantity unit ix (RuleVar rv)
    ) => MkQuantity unit ix (NonSpare ('GNonSpare name title rv))
  where
    quantity = NonSpare . UNonSpare . unRuleVar
        . quantity @unit @ix @(RuleVar rv)

instance
    ( MkQuantity unit ix (NonSpare nsp)
    ) => MkQuantity unit ix (Item ('GItem nsp))
  where
    quantity = Item . UItem . unNonSpare
        . quantity @unit @ix @(NonSpare nsp)

instance
    ( MkQuantity unit ix val
    ) => MkQuantity unit ix (Named name val)
  where
    quantity = item . quantity @unit @ix @val

-- Group construction
--
-- It's implemented in 2 staged:
--  - CGroup instances creates [UItem]
--  - MkGroup instances creates target types
--    (Variation, NonSpare, Item, Named)

-- | Helper class to construct groups.
class CGroup ts2 ts1 where
    groupConstruct :: Proxy ts2 -> HList ts1 -> [UItem]

instance
    TypeError ('Text "CGroup missing spare item")
    => CGroup ('GSpare o n ': ts) '[] where
    groupConstruct = intError

instance
    TypeError ('Text "CGroup missing item: " :<>: 'ShowType name)
    => CGroup ('GItem ('GNonSpare name title rv) ': ts) '[] where
    groupConstruct = intError

instance
    TypeError ('Text "CGroup unexpected item: " :<>: 'ShowType t)
    => CGroup '[] (t ': ts) where
    groupConstruct = intError

instance CGroup '[] '[] where
    groupConstruct _ _ = []

instance
    ( t1 ~ AbuseSpare a
    , a ~ Item ('GSpare o n)
    , CGroup ts2 ts1
    ) => CGroup ('GSpare o n ': ts2) (t1 ': ts1) where
    groupConstruct _proxy (HCons (AbuseSpare x) xs) =
        unItem x : groupConstruct (Proxy @ts2) xs

instance
    ( t1 ~ Named name1 a
    , a ~ Item ('GItem ('GNonSpare name2 title rv))
    , name1 ~ name2
    , CGroup ts2 ts1
    ) => CGroup ('GItem ('GNonSpare name2 title rv) ': ts2) (t1 ': ts1) where
    groupConstruct _proxy (HCons (Indexed x) xs)
        = unItem x : groupConstruct (Proxy @ts2) xs

-- | 'group' function is overloaded for different output types
class MkGroup t ts1 where
    group :: HList ts1 -> t

instance
    ( CGroup ts2 ts1
    ) => MkGroup (Variation ('GGroup o ts2)) ts1 where
    group lst1 = Variation $ UGroup lst2 where
        lst2 :: [UItem]
        lst2 = groupConstruct (Proxy @ts2) lst1

instance
    ( MkGroup (Variation var) ts1
    ) => MkGroup (NonSpare ('GNonSpare name title ('GContextFree var))) ts1
  where
    group = NonSpare . UNonSpare . URuleVar . unVariation
        . group @(Variation var)

instance
    ( MkGroup (NonSpare ('GNonSpare name title rv)) ts1
    ) => MkGroup (Item ('GItem ('GNonSpare name title rv))) ts1
  where
    group = Item . UItem . unNonSpare
        . group @(NonSpare ('GNonSpare name title rv))

instance
    ( name1 ~ name2
    , MkGroup (Item ('GItem ('GNonSpare name2 title (GContextFree var)))) ts1
    ) => MkGroup (Named name1
        (Item ('GItem ('GNonSpare name2 title ('GContextFree var))))) ts1
  where
    group = item
        . group @(Item ('GItem ('GNonSpare name2 title ('GContextFree var))))

-- Extended construction
--
-- It's implemented in 2 stages
--  - CExtended instances create [Maybe UItem]
--  - MkExtended, MkExtendedGroups create target types
--    It is possible to construct extended either by
--      1) extended (HList of individual items, fx...)
--      2) extendedGroups (HList of groups-of-items)

-- Extended construction by items

-- | Helper class to construct extended item.
class CExtended ts2 done ts1 where
    extendedConstruct :: Proxy ts2 -> Proxy done -> HList ts1 -> [Maybe UItem]

instance
    TypeError ('Text "CExtended missing FX item")
    => CExtended ('Nothing ': ts) 'False '[] where
    extendedConstruct = intError

instance
    TypeError ('Text "CExtended missing spare item")
    => CExtended ('Just ('GSpare o n) ': ts) 'False '[] where
    extendedConstruct = intError

instance
    TypeError ('Text "CExtended missing item: " :<>: 'ShowType name)
    => CExtended ('Just ('GItem ('GNonSpare name title rv)) ': ts) 'False '[] where
    extendedConstruct = intError

instance
    TypeError ('Text "CExtended unexpected item: " :<>: 'ShowType t)
    => CExtended '[] done (t ': ts) where
    extendedConstruct = intError

instance CExtended ts2 'True '[] where
    extendedConstruct _p1 _p2 _hlst = []

instance CExtended '[] 'False '[] where
    extendedConstruct _p1 _p2 _hlst = []

instance
    ( t1 ~ Fx
    , CExtended ts2 'True ts1
    ) => CExtended ('Nothing ': ts2) done (t1 ': ts1) where
    extendedConstruct _p1 _p2 (HCons _fx xs)
        = Nothing : extendedConstruct (Proxy @ts2) (Proxy @'True) xs

instance
    ( t1 ~ AbuseSpare a
    , a ~ Item ('GSpare o n)
    , CExtended ts2 'False ts1
    ) => CExtended ('Just ('GSpare o n) ': ts2) done (t1 ': ts1) where
    extendedConstruct _p1 _p2 (HCons (AbuseSpare x) xs)
        = Just (unItem x) : extendedConstruct (Proxy @ts2) (Proxy @'False) xs

instance
    ( t1 ~ Named name1 a
    , a ~ Item ('GItem ('GNonSpare name2 title rv))
    , name1 ~ name2
    , CExtended ts2 'False ts1
    ) => CExtended ('Just ('GItem ('GNonSpare name2 title rv)) ': ts2) done
        (t1 ': ts1)
  where
    extendedConstruct _p1 _p2 (HCons (Indexed x) xs)
        = Just (unItem x) : extendedConstruct (Proxy @ts2) (Proxy @'False) xs

-- | 'extended' function is overloaded for different output types
class MkExtended t ts1 where
    extended :: HList ts1 -> t

instance
    ( CExtended ts2 'False ts1
    ) => MkExtended (Variation ('GExtended ts2)) ts1
  where
    extended lst1 = Variation $ UExtended bld lst2
      where
        lst2 :: [Maybe UItem]
        lst2 = extendedConstruct (Proxy @ts2) (Proxy @'False) lst1

        bld :: SBuilder
        bld = bitsToSBuilder $ recreateExtended lst2

instance
    ( MkExtended (Variation var) ts1
    ) => MkExtended (NonSpare ('GNonSpare name title ('GContextFree var))) ts1
  where
    extended = NonSpare . UNonSpare . URuleVar . unVariation
        . extended @(Variation var)

instance
    ( MkExtended (NonSpare ('GNonSpare name title rv)) ts1
    ) => MkExtended (Item ('GItem ('GNonSpare name title rv))) ts1
  where
    extended = Item . UItem . unNonSpare
        . extended @(NonSpare ('GNonSpare name title rv))

instance
    ( name1 ~ name2
    , MkExtended (Item ('GItem ('GNonSpare name2 title (GContextFree var)))) ts1
    ) => MkExtended (Named name1
        (Item ('GItem ('GNonSpare name2 title ('GContextFree var))))) ts1
  where
    extended = item
        . extended @(Item ('GItem ('GNonSpare name2 title ('GContextFree var))))

-- Extended construction by groups

-- | Helper class to construct extended groups.
class CExtendedGroups ts2 ts1 where
    extendedConstructGroups :: Proxy ts2 -> HList ts1 -> [Maybe UItem]

instance
    ( TypeError ('Text
        "CExtendedGroups at least one extended group shall be provided")
    ) => CExtendedGroups ts2 '[] where
    extendedConstructGroups = intError

instance -- this instance matches last group (t1 ': '[])
    ( t1 ~ Variation ('GGroup 0 lst)
    , lst ~ ExtendedFirstGroup ts2
    , fx ~ ExtendedTrailingFx ts2
    , KnownBool fx
    ) => CExtendedGroups ts2 (t1 ': '[]) where
    extendedConstructGroups _p (HCons (Variation val) HNil) = case val of
        UGroup lst -> fmap Just lst <> trailingFx
        _          -> intError
      where
        appendFx = boolVal @fx Proxy
        trailingFx = bool [] [Nothing] appendFx

instance -- this instance matches non-last group (t1 ': t2 ': '[])
    ( t1 ~ Variation ('GGroup 0 lst)
    , lst ~ ExtendedFirstGroup ts2
    , ts3 ~ ExtendedRemainingItems ts2
    , CExtendedGroups ts3 (t2 ': ts1)
    ) => CExtendedGroups ts2 (t1 ': t2 ': ts1) where
    extendedConstructGroups _p (HCons (Variation val) xs) = case val of
        UGroup lst ->
            fmap Just lst
          <> [Nothing]
          <> extendedConstructGroups @ts3 @(t2 ': ts1) Proxy xs
        _ -> intError

-- | 'extendedGroups' function is overloaded for different output types
class MkExtendedGroups t ts1 where
    extendedGroups :: HList ts1 -> t

instance
    ( CExtendedGroups ts2 ts1
    ) => MkExtendedGroups (Variation ('GExtended ts2)) ts1
  where
    extendedGroups lst1 = Variation $ UExtended bld lst2
      where
        lst2 :: [Maybe UItem]
        lst2 = extendedConstructGroups (Proxy @ts2) lst1

        bld :: SBuilder
        bld = bitsToSBuilder $ recreateExtended lst2

instance
    ( MkExtendedGroups (Variation var) ts1
    ) => MkExtendedGroups (NonSpare ('GNonSpare name title ('GContextFree var)))
        ts1
  where
    extendedGroups = NonSpare . UNonSpare . URuleVar . unVariation
        . extendedGroups @(Variation var)

instance
    ( MkExtendedGroups (NonSpare ('GNonSpare name title rv)) ts1
    ) => MkExtendedGroups (Item ('GItem ('GNonSpare name title rv))) ts1
  where
    extendedGroups = Item . UItem . unNonSpare
        . extendedGroups @(NonSpare ('GNonSpare name title rv))

instance
    ( name1 ~ name2
    , MkExtendedGroups (Item ('GItem ('GNonSpare name2 title (GContextFree var))))
      ts1
    ) => MkExtendedGroups (Named name1
        (Item ('GItem ('GNonSpare name2 title ('GContextFree var))))) ts1
  where
    extendedGroups = item . extendedGroups
        @(Item ('GItem ('GNonSpare name2 title ('GContextFree var))))

-- Repetitive construction

-- | 'repetitive' function is overloaded for different output types
class MkRepetitive t2 t1 | t2 -> t1 where
    type RepetitiveInputList t2 :: Type -> Type
    repetitive :: RepetitiveInputList t2 t1 -> t2

instance
    ( KnownNat n
    ) => MkRepetitive
        (Variation ('GRepetitive ('GRepetitiveRegular n) var))
        (Variation var)
      where
    type RepetitiveInputList
        (Variation ('GRepetitive ('GRepetitiveRegular n) var)) = []
    repetitive lst1 = Variation $ URepetitive bld lst2
      where
        lst2 :: [UVariation]
        lst2 = fmap unVariation lst1

        bld :: SBuilder
        bld =
            let nBytes = fromIntegral $ natVal (Proxy @n)
                n = integerToBits 0 (nBytes*8)
                    (fromIntegral $ Prelude.length lst2)
                items = fmap unparse lst1
            in bitsToSBuilder $ concatBits (n NE.:| items)

instance MkRepetitive
        (Variation ('GRepetitive 'GRepetitiveFx var))
        (Variation var)
      where
    type RepetitiveInputList
        (Variation ('GRepetitive 'GRepetitiveFx var)) = NE.NonEmpty
    repetitive lst1 = Variation $ URepetitive (bld lst1) lst2
      where
        lst2 :: [UVariation]
        lst2 = fmap unVariation (NE.toList lst1)

        addFx :: Bool -> Bits -> Bits
        addFx flag arg = appendBits arg (boolsToBits 7 [flag])

        bld :: NE.NonEmpty (Variation var) -> SBuilder
        bld = bitsToSBuilder
            . concatBits
            . NE.reverse
            . NE.zipWith addFx (False NE.:| repeat True)
            . NE.reverse
            . fmap unparse

instance
    ( MkRepetitive (Variation var) ts1
    ) => MkRepetitive (NonSpare ('GNonSpare name title ('GContextFree var))) ts1
  where
    type RepetitiveInputList (NonSpare ('GNonSpare name title ('GContextFree var)))
            = RepetitiveInputList (Variation var)
    repetitive = NonSpare . UNonSpare . URuleVar . unVariation
        . repetitive @(Variation var)

instance
    ( MkRepetitive (NonSpare ('GNonSpare name title rv)) ts1
    ) => MkRepetitive (Item ('GItem ('GNonSpare name title rv))) ts1
  where
    type RepetitiveInputList (Item ('GItem ('GNonSpare name title rv)))
        = RepetitiveInputList (NonSpare ('GNonSpare name title rv))
    repetitive = Item . UItem . unNonSpare
        . repetitive @(NonSpare ('GNonSpare name title rv))

instance
    ( name1 ~ name2
    , MkRepetitive (Item ('GItem ('GNonSpare name2 title (GContextFree var))))
      ts1
    ) => MkRepetitive (Named name1
        (Item ('GItem ('GNonSpare name2 title ('GContextFree var))))) ts1
  where
    type RepetitiveInputList
        (Named name1
            (Item ('GItem ('GNonSpare name2 title ('GContextFree var)))))
            = RepetitiveInputList
                (Item ('GItem ('GNonSpare name2 title (GContextFree var))))
    repetitive = item . repetitive
        @(Item ('GItem ('GNonSpare name2 title ('GContextFree var))))

-- | 'explicit' function is overloaded for different output types
class MkExplicit t2 t1 where
    explicit :: t1 -> t2

instance
    ( Unparsing SBuilder t1
    , Unparsing Bits t1
    ) => MkExplicit (Variation var) t1
  where
    explicit val = Variation $ UExplicit bld s where
        s :: Bits
        s = unparse val

        nBytes :: Int
        nBytes = numBytes $ bitsSize s

        n :: SBuilder
        n = bitsToSBuilder $ integerToBits 0 8 (succ $ fromIntegral nBytes)

        bld :: SBuilder
        bld = n <> unparse val

instance
    ( MkExplicit (Variation var) t1
    ) => MkExplicit (NonSpare ('GNonSpare name title ('GContextFree var))) t1
  where
    explicit = NonSpare . UNonSpare . URuleVar . unVariation
        . explicit @(Variation var)

instance
    ( MkExplicit (NonSpare ('GNonSpare name title rv)) ts1
    ) => MkExplicit (Item ('GItem ('GNonSpare name title rv))) ts1
  where
    explicit = Item . UItem . unNonSpare
        . explicit @(NonSpare ('GNonSpare name title rv))

instance
    ( name1 ~ name2
    , MkExplicit (Item ('GItem ('GNonSpare name2 title (GContextFree var))))
      ts1
    ) => MkExplicit (Named name1
        (Item ('GItem ('GNonSpare name2 title ('GContextFree var))))) ts1
  where
    explicit = item . explicit
        @(Item ('GItem ('GNonSpare name2 title ('GContextFree var))))

-- | Helper class for compound and record construction.
class CSetItem nsp ts1 where
    cSetItem :: Proxy nsp -> HList ts1 -> Maybe UNonSpare

instance CSetItem nsp '[] where
    cSetItem _p _hlist = Nothing

instance {-# OVERLAPPING #-}
    ( nsp ~ NonSpare ('GNonSpare name title rv)
    ) => CSetItem ('GNonSpare name title rv) (Named name nsp ': ts)
  where
    cSetItem _p (HCons (Indexed nsp) _ts) = Just $ unNonSpare nsp

instance
    ( CSetItem ('GNonSpare name1 title rv) ts
    ) => CSetItem ('GNonSpare name1 title rv) (Named name2 nsp ': ts)
  where
    cSetItem p (HCons _nsp ts) = cSetItem p ts

-- | Check if name is present in HList.
type family ElemName ix ts where
    ElemName ix '[] = 'False
    ElemName ix (Named ix1 a ': ts) = If (ix == ix1) 'True (ElemName ix ts)
    ElemName ix (Rfs cnt a ': ts) = ElemName ix ts
    ElemName ix ts = TypeError ('Text "ElemName unexpected argument: "
        :<>: 'ShowType ix :<>: 'Text ", " :<>: 'ShowType ts)

-- | Check if rfs index is present in HList.
type family ElemRfs ix ts where
    ElemRfs ix '[] = 'False
    ElemRfs ix (Named name a ': ts) = ElemRfs ix ts
    ElemRfs ix (Rfs cnt a ': ts) = If (ix == cnt) 'True (ElemRfs ix ts)
    ElemRfs ix ts = TypeError ('Text "ElemRfs unexpected argument: "
        :<>: 'ShowType ix :<>: 'Text ", " :<>: 'ShowType ts)

-- | Assert there are no duplicates in a HList
-- This type level function could simply return the constraint,
-- however, GHC sometimes takes it as redundant, so return types instead.
type family NoDuplicates t where
    NoDuplicates '[] = '[]
    NoDuplicates (Named name a ': ts) = If
        (ElemName name ts)
        (TypeError ('Text "NoDuplicates duplicated item: " :<>: 'ShowType name))
        (Named name a ': NoDuplicates ts)
    NoDuplicates (Rfs cnt a ': ts) = If
        (ElemRfs cnt ts)
        (TypeError ('Text "NoDuplicates duplicated rfs: " :<>: 'ShowType cnt))
        (Rfs cnt a ': NoDuplicates ts)
    NoDuplicates t
        = TypeError ('Text "NoDuplicates unexpected argument: " :<>: 'ShowType t)

-- Compound construction

-- | Check if name is defined inside Compound.
type family IsDefinedCompound ts name where
    IsDefinedCompound '[] name = 'False
    IsDefinedCompound ('Nothing ': ts) name = IsDefinedCompound ts name
    IsDefinedCompound ('Just ('GNonSpare name1 title rv) ': ts) name = If
        (name1 == name)
        'True
        (IsDefinedCompound ts name)
    IsDefinedCompound ts name
        = TypeError ('Text "IsDefinedCompound unexpected argument: "
            :<>: 'ShowType ts :<>: 'Text ", " :<>: 'ShowType name)

-- | Check if all names are defined inside Compound.
type family AllDefinedCompound ts2 ts1 where
    AllDefinedCompound ts2 '[] = '[]
    AllDefinedCompound ts2 (Named name rv ': ts) = If
        (IsDefinedCompound ts2 name == 'True)
        (Named name rv ': AllDefinedCompound ts2 ts)
        (TypeError ('Text "AllDefinedCompound unknown item: " :<>: 'ShowType name))
    AllDefinedCompound ts2 ts1
        = TypeError ('Text "AllDefinedCompound unexpected argument: "
            :<>: 'ShowType ts2 :<>: 'Text ", " :<>: 'ShowType ts1)

-- | Helper class to construct compound item.
class CCompound ts2 ts1 where
    compoundConstruct :: Proxy ts2 -> HList ts1 -> [Maybe UNonSpare]

instance CCompound '[] ts1 where
    compoundConstruct _p1 _hlist = []

instance
    ( CCompound ts ts1
    ) => CCompound ('Nothing ': ts) ts1 where
    compoundConstruct _p1 hlist
        = Nothing
        : compoundConstruct (Proxy @ts) hlist

instance
    ( CCompound ts ts1
    , CSetItem nsp ts1
    ) => CCompound ('Just nsp ': ts) ts1 where
    compoundConstruct _p1 hlist
        = cSetItem (Proxy @nsp) hlist
        : compoundConstruct (Proxy @ts) hlist

-- Compound item is created from HList, where
--  - items are specified in any order
--  - duplicates are not allowed
--  - all items must be named and valid

-- | 'compound' function is overloaded for different output types
class MkCompound t ts1 where
    compound :: HList ts1 -> t

rebuildCompound :: Unparsing Bits a => [Maybe a] -> SBuilder
rebuildCompound items = mkFspecFx (fmap isJust items)
    <> mconcat (bitsToSBuilder . unparse <$> catMaybes items)

instance
    ( CCompound ts2 ts1
    , NoDuplicates ts1 ~ ts1
    , AllDefinedCompound ts2 ts1 ~ ts1
    ) => MkCompound (Variation ('GCompound ts2)) ts1 where
    compound lst1 = Variation $ UCompound bld items where
        items :: [Maybe UNonSpare]
        items = compoundConstruct @ts2 @(AllDefinedCompound ts2 (NoDuplicates ts1))
            Proxy lst1

        bld :: SBuilder
        bld = rebuildCompound items

instance
    ( MkCompound (Variation var) ts1
    ) => MkCompound (NonSpare ('GNonSpare name title ('GContextFree var))) ts1
  where
    compound = NonSpare . UNonSpare . URuleVar . unVariation
        . compound @(Variation var)

instance
    ( MkCompound (NonSpare ('GNonSpare name title rv)) ts1
    ) => MkCompound (Item ('GItem ('GNonSpare name title rv))) ts1
  where
    compound = Item . UItem . unNonSpare
        . compound @(NonSpare ('GNonSpare name title rv))

instance
    ( name1 ~ name2
    , MkCompound (Item ('GItem ('GNonSpare name2 title (GContextFree var)))) ts1
    ) => MkCompound (Named name1
        (Item ('GItem ('GNonSpare name2 title ('GContextFree var))))) ts1
  where
    compound = item
        . compound @(Item ('GItem ('GNonSpare name2 title ('GContextFree var))))

-- Record construction
--
-- Record is constructed similar to 'compound' item, except:
--  - Result is always a Record, so no need to overload 'record' for that
--    reason, but there are cases, depending on number of allowed RFS blocks.
--  - Input HList may include RFS blocks too.
--  - Regular items follow the same rules (any order, no duplication)
--  - RFS blocks rules:
--    - If the record spec does not include RFS, there shall be no RFS
--      block in input HList.
--    - If the record spec contains 1 RFS flag, the can only be 0 or 1
--      RFS block in the input HList. RFS block might be enumerated
--      with '@0' tag or without.
--    - Otherwise, if the record spec contains multiple RFS flags, they
--      must be enumerated in HList, but the order of specification
--      is not important. They should not be duplicated.
--    - The items inside RFS block may be duplicated and specified in the
--      order or required result.

-- | Helper type level function to count Rfs flags.
type family MaxRfs ts where
    MaxRfs '[] = 0
    MaxRfs ('GUapItem nsp ': ts) = MaxRfs ts
    MaxRfs ('GUapItemSpare ': ts) = MaxRfs ts
    MaxRfs ('GUapItemRFS ': ts) = 1 + MaxRfs ts
    MaxRfs ts = TypeError ('Text "MaxRfs unexpected argument: " :<>: 'ShowType ts)

-- | Check if name is defined inside record.
type family IsDefinedRecord ts name where
    IsDefinedRecord '[] name = 'False
    IsDefinedRecord ('GUapItem ('GNonSpare name1 title rv) ': ts) name2 = If
        (name1 == name2)
        'True
        (IsDefinedRecord ts name2)
    IsDefinedRecord ('GUapItemSpare ': ts) name2 = IsDefinedRecord ts name2
    IsDefinedRecord ('GUapItemRFS ': ts) name2 = IsDefinedRecord ts name2
    IsDefinedRecord ts name
        = TypeError ('Text "IsDefinedRecord unexpected argument: "
            :<>: 'ShowType ts :<>: 'Text ", " :<>: 'ShowType name)

-- | Helper type level function to check if Rfs is specified.
type family IsDefinedRfs ix ts cnt where
    IsDefinedRfs ix '[] cnt = 'False
    IsDefinedRfs ix ('GUapItem nsp ': ts) cnt = IsDefinedRfs ix ts cnt
    IsDefinedRfs ix ('GUapItemSpare ': ts) cnt = IsDefinedRfs ix ts cnt
    IsDefinedRfs ix ('GUapItemRFS ': ts) cnt = If
        (ix == cnt)
        'True
        (IsDefinedRfs (ix+1) ts cnt)
    IsDefinedRfs ix ts cnt
        = TypeError ('Text "IsDefinedRfs unexpected argument: "
            :<>: 'ShowType ts :<>: 'Text ", " :<>: 'ShowType cnt)

-- | Helper function to check if all record items are actually defined.
type family AllDefinedRecord ts2 ts1 where
    AllDefinedRecord ts2 '[] = '[]
    AllDefinedRecord ts2 (Named name rv ': ts) = If
        (IsDefinedRecord ts2 name == 'True)
        (Named name rv ': AllDefinedRecord ts2 ts)
        (TypeError ('Text "AllDefinedRecord unknown item: " :<>: 'ShowType name))
    AllDefinedRecord ts2 (Rfs cnt a ': ts) = If
        (IsDefinedRfs 0 ts2 cnt == 'True)
        (Rfs cnt a ': AllDefinedRecord ts2 ts)
        (TypeError ('Text "AllDefinedRecord unknown rfs: " :<>: 'ShowType cnt))
    AllDefinedRecord ts2 ts1
        = TypeError ('Text "AllDefinedRecord unexpected argument: "
            :<>: 'ShowType ts2 :<>: 'Text ", " :<>: 'ShowType ts1)

-- | Recreate a record from some items.
rebuildRecord :: [Maybe (RecordItem UNonSpare)] -> SBuilder
rebuildRecord items
    = mkFspecFx (fmap isJust items)
   <> mconcat (unparse <$> catMaybes items)

-- | Helper function to extract frame number from list of items.
getFrn :: Enum n
    => n            -- ^ accumulator
    -> VText        -- ^ item name to find
    -> [VUapItem]   -- ^ remaining candidates
    -> n
getFrn acc name = \case
    [] -> intError
    (GUapItem (GNonSpare name2 _title _rv) : ts) -> case name == name2 of
        True  -> acc
        False -> getFrn (succ acc) name ts
    (GUapItemSpare : ts) -> getFrn (succ acc) name ts
    (GUapItemRFS : ts) -> getFrn (succ acc) name ts

-- | 'mkRfs' name overloading.
class MkRfs tsAll ts2 ts1 where
    mkRfs :: HList ts1 -> [(FRN, UNonSpare)]

instance MkRfs tsAll ts2 '[] where
    mkRfs HNil = []

instance
    ( TypeError ('Text "MkRfs item not defined: " :<>: 'ShowType name)
    ) => MkRfs tsAll '[] (Named name nsp ': ts) where
    mkRfs = intError

instance {-# OVERLAPPING #-}
    ( nsp ~ NonSpare ('GNonSpare name title rv)
    , MkRfs tsAll tsAll ts2
    , KnownSymbol name
    , Schema tsAll [VUapItem]
    ) => MkRfs tsAll ('GUapItem ('GNonSpare name title rv) ': ts1)
        (Named name nsp ': ts2) where
    mkRfs (HCons (Indexed (NonSpare x)) xs)
        = (getFrn 1 (schema @name Proxy) (schema @tsAll Proxy), x)
        : mkRfs @tsAll @tsAll xs

instance
    ( MkRfs tsAll ts1 (t2 : ts2)
    ) => MkRfs tsAll (t1 ': ts1) (t2 ': ts2) where
    mkRfs = mkRfs @tsAll @ts1

-- | Helper class to set items.
class RSetItem nsp ts1 where
    rSetItem :: HList ts1 -> Maybe (RecordItem UNonSpare)

instance RSetItem nsp '[] where
    rSetItem _lst = Nothing

instance {-# OVERLAPPING #-}
    ( nsp ~ NonSpare ('GNonSpare name title rv)
    ) => RSetItem ('GNonSpare name title rv) (Named name nsp ': ts) where
    rSetItem (HCons (Indexed (NonSpare x)) _xs) = Just (RecordItem x)

instance
    ( RSetItem ('GNonSpare name title rv) ts
    ) => RSetItem ('GNonSpare name title rv) (t ': ts) where
    rSetItem (HCons _x xs) = rSetItem @('GNonSpare name title rv) xs

-- | Helper class to set Rfs.
class RSetRfs (rfs :: Nat) tsAll ts1 where
    rSetRfs :: HList ts1 -> Maybe (RecordItem UNonSpare)

instance RSetRfs rfs tsAll '[] where
    rSetRfs _lst = Nothing

instance {-# OVERLAPPING #-}
    ( MkRfs tsAll tsAll ts1
    ) => RSetRfs rfs tsAll (Indexed ('IxRfs rfs) (HList ts1) ': ts) where
    rSetRfs (HCons (Indexed lst) _xs)
        = Just $ RecordItemRFS (mkRfs @tsAll @tsAll lst)

instance
    ( RSetRfs rfs tsAll ts
    ) => RSetRfs rfs tsAll (t ': ts) where
    rSetRfs (HCons _x xs) = rSetRfs @rfs @tsAll xs

-- | Helper class for constructing records.
class CRecord maxRfs rfs tsAll ts2 ts1 where
    recordConstruct :: HList ts1 -> [Maybe (RecordItem UNonSpare)]

instance CRecord (maxRfs :: Nat) (rfs :: Nat) tsAll '[] ts1 where
    recordConstruct _lst = []

instance
    ( CRecord maxRfs rfs tsAll ts ts1
    , RSetItem nsp ts1
    ) => CRecord maxRfs rfs tsAll ('GUapItem nsp ': ts) ts1 where
    recordConstruct lst
        = rSetItem @nsp lst
        : recordConstruct @maxRfs @rfs @tsAll @ts lst

instance
    ( CRecord maxRfs rfs tsAll ts ts1
    ) => CRecord maxRfs rfs tsAll ('GUapItemSpare ': ts) ts1 where
    recordConstruct lst
        = Nothing
        : recordConstruct @maxRfs @rfs @tsAll @ts lst

instance
    ( CRecord maxRfs (rfs+1) tsAll ts ts1
    , RSetRfs rfs tsAll ts1
    ) => CRecord maxRfs rfs tsAll ('GUapItemRFS ': ts) ts1 where
    recordConstruct lst
        = rSetRfs @rfs @tsAll lst
        : recordConstruct @maxRfs @(rfs+1) @tsAll @ts lst

-- | In case of a single rfs, we explicitly set it to 'rfs @0',
-- so that inside the application this type application '@0'
-- becomes optional.
class EnumerateRfs (single :: Bool) ts1 where
    type EnumerateRfsR single ts1 :: [Type]
    enumerateRfs :: HList ts1 -> HList (EnumerateRfsR single ts1)

instance EnumerateRfs 'False ts1 where
    type (EnumerateRfsR 'False ts1) = ts1
    enumerateRfs lst = lst

instance EnumerateRfs 'True '[] where
    type (EnumerateRfsR 'True '[]) = '[]
    enumerateRfs lst = lst

instance
    ( EnumerateRfs 'True ts
    ) => EnumerateRfs 'True (Named name a ': ts) where
    type (EnumerateRfsR 'True (Named name a ': ts))
        = Named name a ': EnumerateRfsR 'True ts
    enumerateRfs (HCons x xs) = HCons x (enumerateRfs @'True @ts xs)

instance
    ( EnumerateRfs 'True ts
    , cnt ~ 0
    ) => EnumerateRfs 'True (Rfs cnt a ': ts) where
    type (EnumerateRfsR 'True (Rfs cnt a ': ts))
        = Rfs 0 a ': EnumerateRfsR 'True ts
    enumerateRfs (HCons x xs) = HCons x (enumerateRfs @'True @ts xs)

-- | Record constructor function.
record :: forall ts2 ts1 ts1'.
    ( ts1' ~ EnumerateRfsR (MaxRfs ts2 == 1) ts1
    , AllDefinedRecord ts2 (NoDuplicates ts1) ~ ts1'
    , CRecord (MaxRfs ts2) 0 ts2 ts2 ts1'
    , EnumerateRfs (MaxRfs ts2 == 1) ts1
    , NoDuplicates ts1 ~ ts1
    ) => HList ts1 -> Record ('GRecord ts2)
record lst = Record $ URecord bld items
  where
    items :: [Maybe (RecordItem UNonSpare)]
    items = recordConstruct @(MaxRfs ts2) @0 @ts2 @ts2
        @(AllDefinedRecord ts2 (NoDuplicates ts1))
            (enumerateRfs @(MaxRfs ts2 == 1) lst)

    bld :: SBuilder
    bld = rebuildRecord items

-- Expansion construction

-- | Expansion constructor function.
expansion :: forall mn ts2 ts1.
    ( CCompound ts2 ts1
    , NoDuplicates ts1 ~ ts1
    , AllDefinedCompound ts2 ts1 ~ ts1
    , Schema mn (Maybe Int)
    ) => HList ts1 -> Expansion ('GExpansion mn ts2)
expansion lst1 = Expansion $ UExpansion bld items
  where
    items :: [Maybe UNonSpare]
    items = compoundConstruct @ts2 @(AllDefinedCompound ts2 (NoDuplicates ts1))
        Proxy lst1

    fxbits :: SBuilder
    fxbits = case schema @mn Proxy of
        Nothing     -> mkFspecFx (fmap isJust items)
        Just nBytes -> mkFspecFixed nBytes (fmap isJust items)

    bld :: SBuilder
    bld = fxbits
        <> mconcat (bitsToSBuilder . unparse <$> catMaybes items)

-- Datablock construction

-- | Helper class to construct databock.
class CDatablock multi dbt ts where
    datablockConstruct ::
        Proxy multi       -- multi UAP datablock (Bool)
        -> Proxy dbt      -- datablock type
        -> HList ts       -- list of records
        -> [(Maybe VText, URecord)]

instance CDatablock multi dbt '[] where
    datablockConstruct _p1 _p2 _hlist = []

-- For single UAP situation, the type of record is determined
instance
    ( CDatablock 'False dbt ts
    , dbt ~ 'GDatablock cat uap
    , t ~ TypeOfRecord dbt  -- record must match
    ) => CDatablock 'False dbt (Record t ': ts) where
    datablockConstruct p1 p2 (HCons x xs)
        = (Nothing, unRecord x)
        : datablockConstruct p1 p2 xs

-- For multi UAP situation, the type of record must be explicit
instance
    ( CDatablock 'True dbt ts
    , dbt ~ 'GDatablock cat uap
    , uap ~ 'GUaps lst sel
    , name ~ UapName t lst
    , KnownSymbol name
    ) => CDatablock 'True dbt (Record t ': ts) where
    datablockConstruct p1 p2 (HCons x xs)
        = (Just uapName, unRecord x)
        : datablockConstruct p1 p2 xs
      where
        uapName = T.pack $ symbolVal (Proxy @name)

-- | Helper function to compose datablock from records.
datablockBuilder :: Integral n => n -> [URecord] -> SBuilder
datablockBuilder cat lst =
    let bldData = mconcat (unparse <$> lst)
        n = sbByteSize bldData + 3
        (n1, n2) = divMod n 256
    in word8ToSBuilder (fromIntegral cat)
     <> word8ToSBuilder (fromIntegral n1)
     <> word8ToSBuilder (fromIntegral n2)
     <> bldData

-- | Datablock constructor function.
datablock :: forall dbt ts.
    ( CDatablock (IsMultiUap dbt) dbt ts
    , KnownNat (CategoryOf dbt)
    ) => HList ts -> Datablock dbt
datablock lst = Datablock $ UDatablock bld records where
    records :: [(Maybe VText, URecord)]
    records = datablockConstruct (Proxy @(IsMultiUap dbt)) (Proxy @dbt) lst

    bld :: SBuilder
    bld = datablockBuilder (natVal (Proxy @(CategoryOf dbt))) (fmap snd records)

-- | Convert some types to 'String'.
class ToString ruleIx t where
    asString :: t -> String

instance
    ( Schema st VStringType
    , KnownNat n
    ) => ToString ruleIx (Variation
        ('GElement o n ('GContextFree ('GContentString st))))
  where
    asString (Variation val) = case val of
        UElement s -> bitsToString
            (schema @st Proxy)
            (fromIntegral $ natVal (Proxy @n))
            s
        _ -> intError

instance {-# OVERLAPPING #-}
    ( Schema st VStringType
    , KnownNat n
    ) => ToString ('Just 0) (Variation
        ('GElement o n ('GDependent ts1 dt
            ( '(x, 'GContentString st) ': ts2))))
  where
    asString (Variation val) = case val of
        UElement s -> bitsToString
            (schema @st Proxy)
            (fromIntegral $ natVal (Proxy @n))
            s
        _ -> intError

instance
    ( ToString (Just (ix - 1))
        (Variation (GElement o n (GDependent ts1 dt ts2)))
    ) => ToString ('Just ix) (Variation
        ('GElement o n ('GDependent ts1 dt
            ( '(x, 'GContentString st) ': ts2))))
  where
    asString (Variation val) = asString
        @('Just (ix -1))
        @(Variation ('GElement o n ('GDependent ts1 dt ts2)))
        (Variation val)

instance
    ( ToString ruleIx (Variation var)
    ) => ToString ruleIx (RuleVar ('GContextFree var))
  where
    asString = asString @ruleIx @(Variation var)
        . Variation . unURuleVar . unRuleVar

instance
    ( ToString ruleIx (RuleVar rv)
    ) => ToString ruleIx (NonSpare ('GNonSpare name title rv))
  where
    asString = asString @ruleIx @(RuleVar rv)
        . RuleVar . unUNonSpare . unNonSpare

instance
    ( ToString ruleIx (NonSpare nsp)
    ) => ToString ruleIx (Item ('GItem nsp))
  where
    asString (Item i) = case i of
        UItem val -> asString @ruleIx @(NonSpare nsp) (NonSpare val)
        _         -> intError

-- | Convert some types to 'Integer'.
class ToInteger ruleIx t where
    asInteger :: t -> Integer

instance
    ( Schema sig VSignedness
    , KnownNat n
    ) => ToInteger ruleIx (Variation
        ('GElement o n ('GContextFree ('GContentInteger sig))))
  where
    asInteger (Variation val) = case val of
        UElement s -> bitsToInteger
            (schema @sig Proxy)
            (fromIntegral $ natVal (Proxy @n))
            s
        _ -> intError

instance {-# OVERLAPPING #-}
    ( Schema sig VSignedness
    , KnownNat n
    ) => ToInteger ('Just 0) (Variation
        ('GElement o n ('GDependent ts1 dt
            ( '(x, 'GContentInteger sig) ': ts2))))
  where
    asInteger (Variation val) = case val of
        UElement s -> bitsToInteger
            (schema @sig Proxy)
            (fromIntegral $ natVal (Proxy @n))
            s
        _ -> intError

instance
    ( ToInteger (Just (ix - 1))
        (Variation (GElement o n (GDependent ts1 dt ts2)))
    ) => ToInteger ('Just ix) (Variation
        ('GElement o n ('GDependent ts1 dt
            ( '(x, 'GContentString st) ': ts2))))
  where
    asInteger (Variation val) = asInteger
        @('Just (ix -1))
        @(Variation ('GElement o n ('GDependent ts1 dt ts2)))
        (Variation val)

instance
    ( ToInteger ruleIx (Variation var)
    ) => ToInteger ruleIx (RuleVar ('GContextFree var))
  where
    asInteger = asInteger @ruleIx @(Variation var)
        . Variation . unURuleVar . unRuleVar

instance
    ( ToInteger ruleIx (RuleVar rv)
    ) => ToInteger ruleIx (NonSpare ('GNonSpare name title rv))
  where
    asInteger = asInteger @ruleIx @(RuleVar rv)
        . RuleVar . unUNonSpare . unNonSpare

instance
    ( ToInteger ruleIx (NonSpare nsp)
    ) => ToInteger ruleIx (Item ('GItem nsp))
  where
    asInteger (Item i) = case i of
        UItem val -> asInteger @ruleIx @(NonSpare nsp) (NonSpare val)
        _         -> intError

-- | Convert types to 'Quantity'.
class ToQuantity unit ruleIx t where
    asQuantity :: t -> Quantity unit ruleIx

instance
    ( Schema sig VSignedness
    , KnownNat n
    , Schema lsb VNumber
    , unit1 ~ unit2
    ) => ToQuantity unit1 ruleIx (Variation
        ('GElement o n ('GContextFree ('GContentQuantity sig lsb unit2))))
  where
    asQuantity (Variation val) = case val of
        UElement s -> Quantity $ bitsToDouble
            (schema @sig Proxy)
            (fromIntegral $ natVal (Proxy @n))
            (evalNum $ schema @lsb Proxy)
            s
        _ -> intError

instance {-# OVERLAPPING #-}
    ( Schema sig VSignedness
    , KnownNat n
    , Schema lsb VNumber
    , unit1 ~ unit2
    ) => ToQuantity unit1 ('Just 0) (Variation
        ('GElement o n ('GDependent ts1 dt
            ( '(x, 'GContentQuantity sig lsb unit2) ': ts2))))
  where
    asQuantity (Variation val) = case val of
        UElement s -> Quantity $ bitsToDouble
            (schema @sig Proxy)
            (fromIntegral $ natVal (Proxy @n))
            (evalNum $ schema @lsb Proxy)
            s
        _ -> intError

instance
    ( ToQuantity unit1 (Just (ix - 1))
        (Variation (GElement o n (GDependent ts1 dt ts2)))
    ) => ToQuantity unit1 ('Just ix) (Variation
        ('GElement o n ('GDependent ts1 dt
            ( '(x, 'GContentQuantity sig lsb unit2) ': ts2))))
  where
    asQuantity (Variation val) = Quantity $ unQuantity $
        asQuantity @unit1 @('Just (ix -1))
            @(Variation ('GElement o n ('GDependent ts1 dt ts2))) (Variation val)

instance
    ( ToQuantity unit ruleIx (Variation var)
    ) => ToQuantity unit ruleIx (RuleVar ('GContextFree var))
  where
    asQuantity = asQuantity @unit @ruleIx @(Variation var)
        . Variation . unURuleVar . unRuleVar

instance
    ( ToQuantity unit ruleIx (RuleVar rv)
    ) => ToQuantity unit ruleIx (NonSpare ('GNonSpare name title rv))
  where
    asQuantity = asQuantity @unit @ruleIx @(RuleVar rv)
        . RuleVar . unUNonSpare . unNonSpare

instance
    ( ToQuantity unit ruleIx (NonSpare nsp)
    ) => ToQuantity unit ruleIx (Item ('GItem nsp))
  where
    asQuantity (Item i) = case i of
        UItem val -> asQuantity @unit @ruleIx @(NonSpare nsp) (NonSpare val)
        _         -> intError

type family GetVariationResult t where
    GetVariationResult ('GNonSpare name title ('GContextFree var)) = var
    GetVariationResult ('GNonSpare name title ('GDependent lst1 d lst2)) = d
    GetVariationResult t
        = TypeError ('Text "GetVariationResult unexpected argument: "
            :<>: 'ShowType t)

-- | Extract 'Variation' from 'NonSpare'.
getVariation :: NonSpare nsp -> Variation (GetVariationResult nsp)
getVariation = Variation . unURuleVar . unUNonSpare . unNonSpare

-- | Get Dependent Variation from 'NonSpare'.
getDepVariation :: forall ix nsp.
    ( Schema (DepRule nsp ix) VVariation
    ) => NonSpare nsp -> Either ParsingError (Variation (DepRule nsp ix))
getDepVariation nsp = do
    let Bits bs o1 size1 = unparse @Bits nsp
        act = parseVariation (schema @(DepRule nsp ix) Proxy)
        env = Env parsingStore bs
    (var, o2) <- runParsing act env o1
    let size2 = coerce $ o2 - o1
    when (size2 /= size1) $ Left $ ParsingError "remaining bits"
    pure $ Variation var

-- | Extract all spare items from group.
getGroupSpares :: Variation ('GGroup o lst) -> [Bits]
getGroupSpares (Variation var) = case var of
    UGroup lst -> lst >>= \case
        USpare val -> pure val
        _ -> empty
    _ -> intError

-- | Extract group subitem by typelevel name.
getGroupItem :: forall name o lst.
    ( KnownNat (LookupGroup name lst)
    ) => Variation ('GGroup o lst) -> NonSpare ('GGroup o lst ~> name)
getGroupItem (Variation var) = case var of
    UGroup lst ->
        let ix = fromIntegral (natVal @(LookupGroup name lst) Proxy)
            getNsp = \case
                UItem nsp -> NonSpare nsp
                _ -> intError
        in getNsp $ lst !! ix
    _ -> intError

-- | Extract all present spare items from extended.
getExtendedSpares :: Variation ('GExtended lst) -> [Bits]
getExtendedSpares (Variation var) = case var of
    UExtended _bld lst -> lst >>= \case
        Just (USpare val) -> pure val
        _ -> empty
    _ -> intError

-- | Extract extended subitem by typelevel name.
getExtendedItem :: forall name lst.
    ( KnownNat (LookupExtended name lst)
    ) => Variation ('GExtended lst)
        -> Maybe (NonSpare ('GExtended lst ~> name))
getExtendedItem (Variation var) = case var of
    UExtended _bld lst -> do
        let n = fromIntegral $ natVal (Proxy @(LookupExtended name lst))
        guard (n < length lst)
        case lst !! n of
            Just (UItem nsp) -> Just (NonSpare nsp)
            _                -> intError
    _ -> intError

-- | Extract repetitive content.
getRepetitiveItems :: Variation ('GRepetitive rt var) -> [Variation var]
getRepetitiveItems (Variation var) = case var of
    URepetitive _bld lst -> fmap Variation lst
    _                    -> intError

-- | Extract explicit data 'Bits'.
getExplicitData :: Variation ('GExplicit mt) -> Bits
getExplicitData (Variation var) = case var of
    UExplicit _bld val -> val
    _                  -> intError

-- | Extract compound subitem by typelevel name.
getCompoundItem :: forall name lst. -- t lst ix.
    ( KnownNat (LookupCompound name lst)
    ) => Variation ('GCompound lst)
        -> Maybe (NonSpare ('GCompound lst ~> name))
getCompoundItem (Variation var) = case var of
    UCompound _bld lst ->
        let n = fromIntegral $ natVal (Proxy @(LookupCompound name lst))
        in NonSpare <$> (lst !! n)
    _ -> intError

-- | Set compound subitem by typelevel name.
setCompoundItem :: forall name lst.
    ( KnownNat (LookupCompound name lst)
    ) => NonSpare ('GCompound lst ~> name) -> Variation ('GCompound lst)
        -> Variation ('GCompound lst)
setCompoundItem (NonSpare nsp) (Variation var) = case var of
    UCompound _bld lst1 ->
        -- replace element in a list and rebuild
        let n = fromIntegral $ natVal (Proxy @(LookupCompound name lst))
            (a, b) = splitAt n lst1
            lst2 = a <> [Just nsp] <> drop 1 b
            bld = rebuildCompound lst2
        in Variation (UCompound bld lst2)
    _ -> intError

-- | Del compound subitem by typelevel name.
delCompoundItem :: forall name lst.
    ( KnownNat (LookupCompound name lst)
    ) => Variation ('GCompound lst) -> Variation ('GCompound lst)
delCompoundItem (Variation var) = case var of
    UCompound _bld lst1 ->
        let n = fromIntegral $ natVal (Proxy @(LookupCompound name lst))
            (a, b) = splitAt n lst1
            lst2 = a <> [Nothing] <> drop 1 b
            bld = rebuildCompound lst2
        in Variation (UCompound bld lst2)
    _ -> intError

-- | Extract record subitem by typelevel name.
getRecordItem :: forall name lst.
    ( KnownNat (LookupRecord name lst)
    ) => Record ('GRecord lst) -> Maybe (NonSpare ('GRecord lst ~> name))
getRecordItem (Record (URecord _bld lst)) = f <$> lst !! n
  where
    n = fromIntegral (natVal (Proxy @(LookupRecord name lst)))
    f :: RecordItem UNonSpare
        -> NonSpare (Lookup name (PrependName (RecordNonSpares lst)))
    f = \case
        RecordItem nsp -> NonSpare nsp
        _ -> intError

-- | Set record subitem by typelevel name.
setRecordItem :: forall name lst.
    ( KnownNat (LookupRecord name lst)
    ) => NonSpare ('GRecord lst ~> name) -> Record ('GRecord lst)
        -> Record ('GRecord lst)
setRecordItem (NonSpare nsp) (Record (URecord _bld lst1))
    = Record (URecord bld lst2)
  where
    n = fromIntegral (natVal (Proxy @(LookupRecord name lst)))
    (a, b) = splitAt n lst1
    lst2 = a <> [Just $ RecordItem nsp] <> drop 1 b
    bld = rebuildRecord lst2

-- | Del record subitem by typelevel name.
delRecordItem :: forall name lst.
    ( KnownNat (LookupRecord name lst)
    ) => Record ('GRecord lst) -> Record ('GRecord lst)
delRecordItem (Record (URecord _bld lst1)) = Record (URecord bld lst2)
  where
    n = fromIntegral (natVal (Proxy @(LookupRecord name lst)))
    (a, b) = splitAt n lst1
    lst2 = a <> [Nothing] <> drop 1 b
    bld = rebuildRecord lst2

-- | Get RFS subitems by typelevel name.
getRfsItem :: forall name lst.
    ( KnownSymbol name
    , Schema lst [VUapItem]
    ) => Record ('GRecord lst) -> [NonSpare ('GRecord lst ~> name)]
getRfsItem (Record (URecord _bld lst)) = mconcat $ fmap f lst
  where
    frnRequired = getFrn 1 (schema @name Proxy) (schema @lst Proxy)
    f Nothing = []
    f (Just ri) = case ri of
        RecordItem _nsp -> []
        RecordItemSpare -> []
        RecordItemRFS lst2 -> do
            (frn, nsp) <- lst2
            guard $ frn == frnRequired
            pure $ NonSpare nsp

-- | Get Expansion subitems by typelevel name.
getExpansionItem :: forall name mn lst.
    ( KnownNat (LookupCompound name lst)
    ) => Expansion ('GExpansion mn lst)
        -> Maybe (NonSpare ('GExpansion mn lst ~> name))
getExpansionItem (Expansion (UExpansion _bld lst))
    = NonSpare <$> (lst !! n)
  where
    n = fromIntegral (natVal (Proxy @(LookupCompound name lst)))

-- Item access overloaded functions

-- | 'getItem' name overloading.
class GetItem name parent child | name parent -> child where
    getItem :: parent -> child

instance
    ( GetItem name (Variation (GetVariationResult nsp1)) (NonSpare nsp2)
    ) => GetItem name (NonSpare nsp1) (NonSpare nsp2) where
    getItem obj = getItem @name $ getVariation obj

instance
    ( r ~ 'GRecord lst
    , nsp ~ Lookup name (PrependName (RecordNonSpares lst))
    , KnownNat (LookupRecord name lst)
    ) => GetItem name (Record r) (Maybe (NonSpare nsp)) where
    getItem = getRecordItem @name

instance
    ( nsp ~ 'GGroup o lst ~> name
    , KnownNat (LookupGroup name lst)
    ) => GetItem name (Variation ('GGroup o lst)) (NonSpare nsp) where
    getItem = getGroupItem @name

instance
    ( nsp ~ 'GExtended lst ~> name
    , KnownNat (LookupExtended name lst)
    ) => GetItem name (Variation ('GExtended lst)) (Maybe (NonSpare nsp)) where
    getItem = getExtendedItem @name

instance
    ( nsp ~ 'GCompound lst ~> name
    , KnownNat (LookupCompound name lst)
    ) => GetItem name (Variation ('GCompound lst)) (Maybe (NonSpare nsp)) where
    getItem = getCompoundItem @name

instance
    ( nsp ~ 'GExpansion mn lst ~> name
    , KnownNat (LookupCompound name lst)
    ) => GetItem name (Expansion ('GExpansion mn lst)) (Maybe (NonSpare nsp)) where
    getItem = getExpansionItem @name

-- | 'getSpares' name overloading.
class GetSpares t where
    getSpares :: t -> [Bits]

instance
    ( GetSpares (Variation (GetVariationResult nsp))
    ) => GetSpares (NonSpare nsp) where
    getSpares = getSpares . getVariation

instance GetSpares (Variation ('GGroup o lst)) where
    getSpares = getGroupSpares

-- | 'setItem' name overloading.
class SetItem name parent child | name parent -> child where
    setItem :: child -> parent -> parent

-- | If we are able to perform "setItem @name Variation NonSpare", then
-- we shall also be able to do "setItem @name NonSpare NonSpare".
-- This is useful in situations like:
--  compound nil
--  & setItem @"I1" val1
--  & setItem @"I2" val2
instance
    ( nspChild ~ nspParent ~> name
    , var ~ GetVariationResult nspParent
    , SetItem name (Variation var) (NonSpare nspChild)
    ) => SetItem name (NonSpare nspParent) (NonSpare nspChild) where
    setItem child parent =
        let var = getVariation parent
            var' = setItem @name child var
        in NonSpare $ UNonSpare $ URuleVar $ unVariation var'

instance
    ( r ~ 'GRecord lst
    , nsp ~ Lookup name (PrependName (RecordNonSpares lst))
    , KnownNat (LookupRecord name lst)
    ) => SetItem name (Record r) (NonSpare nsp) where
    setItem = setRecordItem @name

instance
    ( nsp ~ 'GCompound lst ~> name
    , KnownNat (LookupCompound name lst)
    ) => SetItem name (Variation ('GCompound lst)) (NonSpare nsp) where
    setItem = setCompoundItem @name

-- | Helper function to setItem conditionally.
maybeSetItem :: forall name parent child.
    ( SetItem name parent child
    ) => Maybe child -> parent -> parent
maybeSetItem Nothing parent      = parent
maybeSetItem (Just child) parent = setItem @name child parent

-- | 'delItem' name overloading.
class DelItem name parent where
    delItem :: parent -> parent

instance
    ( r ~ 'GRecord lst
    , KnownNat (LookupRecord name lst)
    ) => DelItem name (Record r) where
    delItem = delRecordItem @name

instance
    ( var ~ 'GCompound lst
    , KnownNat (LookupCompound name lst)
    ) => DelItem name (Variation var) where
    delItem = delCompoundItem @name

-- | Helper function to manipulate extended subitem if present.
class ExtModify name parent child | name parent -> child where
    modifyExtendedSubitemIfPresent :: (child -> child) -> parent -> parent

instance
    ( ExtModify name (Variation (GetVariationResult nsp1)) (NonSpare nsp2)
    ) => ExtModify name (NonSpare nsp1) (NonSpare nsp2) where
    modifyExtendedSubitemIfPresent f parent =
        let Variation v =
                modifyExtendedSubitemIfPresent @name f $ getVariation parent
        in NonSpare $ UNonSpare $ URuleVar v

instance
    ( nsp ~ 'GExtended lst ~> name
    , KnownNat (LookupExtended name lst)
    ) => ExtModify name (Variation ('GExtended lst)) (NonSpare nsp)
  where
    modifyExtendedSubitemIfPresent f (Variation var) = case var of
        UExtended _bld lst ->
            let ix = fromIntegral $ natVal (Proxy @(LookupExtended name lst))
            in case ix < length lst of
                False -> Variation var -- item not present
                True -> case lst !! ix of
                    Just (UItem x) ->
                        let NonSpare y = f (NonSpare x)
                            lst2 :: [Maybe UItem]
                            lst2 = take ix lst ++ Just (UItem y) : drop (ix+1) lst
                            bld :: SBuilder
                            bld = bitsToSBuilder $ recreateExtended lst2
                        in Variation $ UExtended bld lst2
                    _ -> intError
        _ -> intError