content: {-# LANGUAGE LambdaCase #-}
  {-# LANGUAGE TypeFamilies #-}
  
  -- | C enumerations
  --
  -- This module is intended to be imported qualified.
  module HsBindgen.Runtime.CEnum (
      -- * Type classes
      CEnum(..)
    , SequentialCEnum(..)
      -- * API
    , getNames
      -- * Instance support
    , DeclaredValues
    , declaredValuesFromList
    , showCEnum
    , showsCEnum
    , showsWrappedUndeclared
    , readEitherCEnum
    , readPrecCEnum
    , readPrecWrappedUndeclared
    , seqIsDeclared
    , seqMkDeclared
      -- * Deriving via support
    , AsCEnum(..)
    , AsSequentialCEnum(..)
      -- ** Exceptions
    , CEnumException(..)
    ) where
  
  import Control.Exception (Exception (displayException), throw)
  import Data.Bifunctor (Bifunctor (first))
  import Data.Coerce (Coercible, coerce)
  import Data.List qualified as List
  import Data.List.NonEmpty (NonEmpty ((:|)))
  import Data.List.NonEmpty qualified as NonEmpty
  import Data.Map.Strict (Map)
  import Data.Map.Strict qualified as Map
  import Data.Proxy (Proxy (Proxy))
  import GHC.Show (appPrec, appPrec1, showSpace)
  import Text.ParserCombinators.ReadP qualified as ReadP
  import Text.ParserCombinators.ReadPrec qualified as ReadPrec
  import Text.Read (ReadPrec, minPrec, (+++))
  import Text.Read qualified as Read
  import Text.Read.Lex (Lexeme (..), expect)
  
  {-------------------------------------------------------------------------------
    Type classes
  -------------------------------------------------------------------------------}
  
  -- | C enumeration
  --
  -- This class implements an API for Haskell representations of C enumerations.
  -- C @enum@ declarations only declare values; they do not limit the range of the
  -- corresponding integral type.  They may have negative values, non-sequential
  -- values, and multiple names for a single value.
  --
  -- At a low level, @hs-bindgen@ generates a @newtype@ wrapper around the
  -- integral representation type to represent a C @enum@.  An instance of this
  -- class is generated automatically.  A 'Show' instance defined using
  -- 'showsCEnum' is also generated by default.  'Bounded' and 'Enum' instances are
  -- /not/ generated automatically because values do not technically need to be
  -- declared.  Users may optionally derive these instances using 'AsCEnum' or
  -- 'AsSeqentialCEnum' when appropriate.
  --
  -- This class may also be used with Haskell sum-type representations of
  -- enumerations.
  class Integral (CEnumZ a) => CEnum a where
    -- | Integral representation type
    type CEnumZ a
  
    -- | Construct a value from the integral representation
    --
    -- prop> fromCEnum . toCEnum === id
    toCEnum :: CEnumZ a -> a
    default toCEnum :: Coercible a (CEnumZ a) => CEnumZ a -> a
    toCEnum = coerce
  
    -- | Get the integral representation for a value
    --
    -- prop> toCEnum . fromCEnum === id
    --
    -- If @a@ has an 'Ord' instance, it should be compatible with the 'Ord'
    -- instance on the underlying integral value:
    --
    -- prop> \x y -> (x <= y) === (fromCEnum x <= fromCEnum y)
    fromCEnum :: a -> CEnumZ a
    default fromCEnum :: Coercible a (CEnumZ a) => a -> CEnumZ a
    fromCEnum = coerce
  
    -- | Declared values and associated names
    declaredValues :: proxy a -> DeclaredValues a
  
    -- | Show undeclared value
    --
    -- Like any 'Show' related function, this should generate a valid Haskell
    -- expression. In this case, a valid Haskell expression for values /outside/
    -- of the set of declared values (that is, for which 'isDeclared' will return
    -- 'False').
    --
    -- The default definition just shows the underlying integer value; this is
    -- valid if the Haskell wrapper has a 'Num' instance. If the Haskell type is
    -- simply a newtype wrapper around the underlying C type, you can use
    -- 'showsWrappedUndeclared'. Finally, if the Haskell type /cannot/ represent
    -- undeclared values, this can be defined using @error@.
    --
    -- > showsUndeclared _ = \_ x ->
    -- >   error $ "Unexpected value " ++ show x ++ " for type Foo"
    showsUndeclared :: proxy a -> Int -> CEnumZ a -> ShowS
    default showsUndeclared ::
                 Show (CEnumZ a)
              => proxy a -> Int -> CEnumZ a -> ShowS
    showsUndeclared _ = showsPrec
  
    -- | Read undeclared value
    --
    -- See 'showsUndeclared', 'showsWrappedUndeclared', and
    -- 'readPrecWrappedUndeclared'.
    readPrecUndeclared :: ReadPrec a
  
    -- | Determine if the specified value is declared
    --
    -- This has a default definition in terms of 'getDeclaredValues', but you may
    -- wish to override this with a more efficient implementation (in particular,
    -- see 'seqIsDeclared').
    isDeclared :: a -> Bool
    isDeclared x = (fromCEnum x) `Map.member` getIntegralToDeclaredValues (Proxy :: Proxy a)
  
    -- | Construct a value only if it is declared
    --
    -- See also 'seqMkDeclared'.
    mkDeclared :: CEnumZ a -> Maybe a
    mkDeclared i
      | i `Map.member` getIntegralToDeclaredValues (Proxy :: Proxy a) = Just (toCEnum i)
      | otherwise = Nothing
  
  -- | C enumeration with sequential values
  --
  -- 'Bounded' and 'Enum' methods may be implemented more efficiently when the
  -- values of an enumeration are sequential.  An instance of this class is
  -- generated automatically in this case.  Users may optionally derive these
  -- instances using 'AsSequentialCEnum' when appropriate.
  --
  -- This class may also be used with Haskell sum-type representations of
  -- enumerations.
  --
  -- prop> all isDeclared [minDeclaredValue..maxDeclaredValue]
  class CEnum a => SequentialCEnum a where
    -- | The minimum declared value
    --
    -- prop> minDeclaredValue == minimum (filter isDeclared (map toCEnum [minBound..]))
    minDeclaredValue :: a
  
    -- | The maximum declared value
    --
    -- prop> maxDeclaredValue == maximum (filter isDeclared (map toCEnum [minBound..]))
    maxDeclaredValue :: a
  
  {-------------------------------------------------------------------------------
    API
  -------------------------------------------------------------------------------}
  
  -- | Get all names associated with a value
  --
  -- An empty list is returned when the specified value is not declared.
  getNames :: forall a. CEnum a => a -> [String]
  getNames x = maybe [] NonEmpty.toList $
      Map.lookup (fromCEnum x) (getIntegralToDeclaredValues (Proxy :: Proxy a))
  
  {-------------------------------------------------------------------------------
    Instance support
  -------------------------------------------------------------------------------}
  
  -- | Declared values (opaque)
  data DeclaredValues a = DeclaredValues
      { integralToDeclaredValues :: !(Map (CEnumZ a) (NonEmpty String))
      , declaredValueToIntegral :: !(Map String (CEnumZ a))
      }
  
  -- | Construct 'DeclaredValues' from a list of values and associated names
  declaredValuesFromList ::
       Ord (CEnumZ a)
    => [(CEnumZ a, NonEmpty String)]
    -> DeclaredValues a
  declaredValuesFromList xs =
    DeclaredValues
      { integralToDeclaredValues = Map.fromList xs
      , declaredValueToIntegral =
          Map.fromList [ (n, i) | (i, ns) <-  xs , n <- NonEmpty.toList ns ] }
  
  declaredValuesList :: DeclaredValues a -> [String]
  declaredValuesList = Map.keys . declaredValueToIntegral
  
  -- | Show the specified value
  --
  -- Examples for a hypothetical enumeration type, using generated defaults:
  --
  -- > showCEnum StatusOK == "StatusOK"
  --
  -- > showCEnum (StatusCode 418) == "StatusCode 418"
  showCEnum :: forall a. CEnum a => a -> String
  showCEnum x = showsCEnum 0 x ""
  
  -- | Generalization of 'showCEnum' (akin to 'showsPrec').
  --
  -- This function may be used in the definition of a 'Show' instance for a
  -- @newtype@ representation of a C enumeration.
  --
  -- When the value is declared, a corresponding name is returned.  Otherwise,
  -- 'showsUndeclared' is called.
  showsCEnum :: forall a. CEnum a => Int -> a -> ShowS
  showsCEnum prec x =
      case Map.lookup i (getIntegralToDeclaredValues (Proxy :: Proxy a)) of
        Just (name :| _names) -> showString name
        Nothing -> showsUndeclared (Proxy :: Proxy a) prec i
    where
      i :: CEnumZ a
      i = fromCEnum x
  
  -- | Read a 'CEnum' from string
  --
  -- Examples for a hypothetical enumeration type, using generated defaults:
  --
  -- > (readEitherCEnum "StatusCode 200" :: StatusCode) == Right StatusOK
  --
  -- > (readEitherCEnum "StatusOK" :: StatusCode) == Right StatusOK
  --
  -- > (readEitherCEnum "StatusCode 123" :: StatusCode) == Right (StatusCode 123)
  readEitherCEnum :: forall a. CEnum a => String -> Either String a
  readEitherCEnum s =
    case [ x | (x,"") <- ReadPrec.readPrec_to_S read' minPrec s ] of
      [x] -> Right x
      []  -> Left "readEitherCEnum: no parse"
      _xs -> Left "readEitherCEnum: ambiguous parse"
   where
    read' =
      do x <- readPrecCEnum
         ReadPrec.lift ReadP.skipSpaces
         return x
  
  -- | Helper function for defining 'showsUndeclared'
  --
  -- This helper can be used in the case where @a@ is a newtype wrapper around
  -- the underlying @CEnumZ a@.
  showsWrappedUndeclared ::
       Show (CEnumZ a)
    => String -> proxy a -> Int -> CEnumZ a -> ShowS
  showsWrappedUndeclared constructorName _ p x = showParen (p >= appPrec1) $
       showString constructorName
     . showSpace
     . showsPrec appPrec1 x
  
  -- | Read a declared 'CEnum' value
  readPrecDeclaredValue :: forall proxy a. CEnum a => proxy a -> ReadPrec a
  readPrecDeclaredValue proxy = Read.parens $ ReadPrec.prec appPrec1 $ do
    declaredValue <- ReadPrec.lift $ ReadP.choice $
                       map ReadP.string $ declaredValuesList $ declaredValues proxy
    pure $ toCEnum $ (declaredValueToIntegral $ declaredValues proxy) Map.! declaredValue
  
  -- | Helper function for defining 'readPrecUndeclared'
  --
  -- This helper can be used in the case where @a@ is a newtype wrapper around
  -- the underlying @CEnumZ a@.
  readPrecWrappedUndeclared
    :: forall a. (CEnum a, Read (CEnumZ a)) => String -> ReadPrec a
  readPrecWrappedUndeclared constructorName = Read.parens $ ReadPrec.prec appPrec $ do
    ReadPrec.lift $ expect $ Ident constructorName
    n <- Read.step (Read.readPrec :: ReadPrec (CEnumZ a))
    pure $ toCEnum n
  
  -- | Read a 'CEnum' from string
  --
  -- This function may be used in the definition of a 'Read' instance for a
  -- @newtype@ representation of a C enumeration.
  readPrecCEnum :: forall a. CEnum a => ReadPrec a
  readPrecCEnum = readPrecDeclaredValue (Proxy :: Proxy a) +++ readPrecUndeclared
  
  -- | Determine if the specified value is declared
  --
  -- This implementation is optimized for 'SequentialCEnum'.
  seqIsDeclared :: forall a. SequentialCEnum a => a -> Bool
  seqIsDeclared x = i >= minZ && i <= maxZ
    where
      minZ, maxZ, i :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
  
  -- | Construct a value only if it is declared
  --
  -- This implementation is optimized for 'SequentialCEnum'.
  seqMkDeclared :: forall a. SequentialCEnum a => CEnumZ a -> Maybe a
  seqMkDeclared i
      | i >= minZ && i <= maxZ = Just (toCEnum i)
      | otherwise = Nothing
    where
      minZ, maxZ :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
  
  {-------------------------------------------------------------------------------
    Deriving via support
  -------------------------------------------------------------------------------}
  
  -- | Type used to derive classes using @DerivingVia@ a type with a 'CEnum'
  -- instance
  --
  -- When the values are sequential, 'AsSequentialCEnum' provides better
  -- performance and should therefore be used instead.
  --
  -- The following classes may be derived:
  --
  -- * 'Bounded' may be derived using the bounds of the declared values.  This is
  --   /not/ derived by default.
  -- * 'Enum` may be derived using the bounds of the declared values.  This
  --   instance assumes that only the declared values are valid and throws a
  --   'CEnumException' if passed a value that is not declared.  This is /not/
  --   derived by default.
  --
  -- For /declared/ values we have
  --
  -- prop> toEnum   === coerce       . toCENum   . fromIntegral
  -- prop> fromEnum === fromIntegral . fromCEnum . coerce
  --
  -- In addition we guarantee that where 'pred' or 'succ' are defined, we have
  --
  -- prop> \x -> (pred x < x) && (x < succ x)
  newtype AsCEnum a = WrapCEnum { unwrapCEnum :: a }
  
  instance CEnum a => Bounded (AsCEnum a) where
    minBound = WrapCEnum minBoundGen
    maxBound = WrapCEnum maxBoundGen
  
  instance CEnum a => Enum (AsCEnum a) where
    succ = WrapCEnum . succGen . unwrapCEnum
    pred = WrapCEnum . predGen . unwrapCEnum
  
    toEnum   = WrapCEnum   . toEnumGen
    fromEnum = fromEnumGen . unwrapCEnum
  
    enumFrom (WrapCEnum x)                   = WrapCEnum <$> enumFromGen x
    enumFromThen (WrapCEnum x) (WrapCEnum y) = WrapCEnum <$> enumFromThenGen x y
    enumFromTo (WrapCEnum x) (WrapCEnum z)   = WrapCEnum <$> enumFromToGen x z
    enumFromThenTo (WrapCEnum x) (WrapCEnum y) (WrapCEnum z) =
      WrapCEnum <$> enumFromThenToGen x y z
  
  -- | Type used to derive classes using @DerivingVia@ a type with a
  -- 'SequentialCEnum' instance
  --
  -- The following classes may be derived:
  --
  -- * 'Bounded' may be derived using the bounds of the declared values.  This is
  --   /not/ derived by default.
  -- * 'Enum` may be derived using the bounds of the declared values.  This
  --   instance assumes that only the declared values are valid and throws a
  --   'CEnumException' if passed a value that is not declared.  This is /not/
  --   derived by default.
  --
  -- 'AsSequentialCEnum' should have the same properties as 'AsCEnum'.
  newtype AsSequentialCEnum a = WrapSequentialCEnum { unwrapSequentialCEnum :: a }
  
  instance SequentialCEnum a => Bounded (AsSequentialCEnum a) where
    minBound = WrapSequentialCEnum minBoundSeq
    maxBound = WrapSequentialCEnum maxBoundSeq
  
  instance SequentialCEnum a => Enum (AsSequentialCEnum a) where
    succ = WrapSequentialCEnum . succSeq . unwrapSequentialCEnum
    pred = WrapSequentialCEnum . predSeq . unwrapSequentialCEnum
  
    toEnum   = WrapSequentialCEnum . toEnumSeq
    fromEnum = fromEnumSeq . unwrapSequentialCEnum
  
    enumFrom (WrapSequentialCEnum x) = WrapSequentialCEnum <$> enumFromSeq x
    enumFromThen (WrapSequentialCEnum x) (WrapSequentialCEnum y) =
      WrapSequentialCEnum <$> enumFromThenSeq x y
    enumFromTo (WrapSequentialCEnum x) (WrapSequentialCEnum z) =
      WrapSequentialCEnum <$> enumFromToSeq x z
    enumFromThenTo
      (WrapSequentialCEnum x)
      (WrapSequentialCEnum y)
      (WrapSequentialCEnum z) =
        WrapSequentialCEnum <$> enumFromThenToSeq x y z
  
  {-------------------------------------------------------------------------------
    Exceptions
  -------------------------------------------------------------------------------}
  
  -- Exceptions used by optional C enumeration instances
  data CEnumException
    = CEnumNotDeclared Integer
    | CEnumNoSuccessor Integer
    | CEnumNoPredecessor Integer
    | CEnumEmpty
    | CEnumFromEqThen Integer
    deriving (Eq, Show)
  
  instance Exception CEnumException where
    displayException = \case
      CEnumNotDeclared i -> "C enumeration value not declared: " ++ show i
      CEnumNoSuccessor i ->
        "C enumeration value has no declared successor: " ++ show i
      CEnumNoPredecessor i ->
        "C enumeration value has no declared predecessor: " ++ show i
      CEnumEmpty -> "C enumeration has no declared values"
      CEnumFromEqThen i -> "enumeration from and then values equal: " ++ show i
  
  {-------------------------------------------------------------------------------
    Bounded instance implementation
  -------------------------------------------------------------------------------}
  
  minBoundGen :: forall a. CEnum a => a
  minBoundGen = case Map.lookupMin (getIntegralToDeclaredValues (Proxy :: Proxy a)) of
      Just (i, _names) -> toCEnum i
      Nothing -> throw CEnumEmpty
  
  minBoundSeq :: SequentialCEnum a => a
  minBoundSeq = minDeclaredValue
  
  maxBoundGen :: forall a. CEnum a => a
  maxBoundGen = case Map.lookupMax (getIntegralToDeclaredValues (Proxy :: Proxy a)) of
      Just (k, _names) -> toCEnum k
      Nothing -> throw CEnumEmpty
  
  maxBoundSeq :: SequentialCEnum a => a
  maxBoundSeq = maxDeclaredValue
  
  {-------------------------------------------------------------------------------
    Enum instance implementation
  -------------------------------------------------------------------------------}
  
  succGen :: forall a. CEnum a => a -> a
  succGen x = either (throw . CEnumNotDeclared) id $ do
      (_ltMap, gtMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
      case Map.lookupMin gtMap of
        Just (j, _names) -> return $ toCEnum j
        Nothing -> throw $ CEnumNoSuccessor (toInteger i)
    where
      i :: CEnumZ a
      i = fromCEnum x
  
  succSeq :: forall a. SequentialCEnum a => a -> a
  succSeq x
      | i >= minZ && i < maxZ = toCEnum (i + 1)
      | i == maxZ = throw $ CEnumNoSuccessor (toInteger i)
      | otherwise = throw $ CEnumNotDeclared (toInteger i)
    where
      minZ, maxZ, i :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
  
  predGen :: forall a. CEnum a => a -> a
  predGen y = either (throw . CEnumNotDeclared) id $ do
      (ltMap, _gtMap) <- splitMap j (getIntegralToDeclaredValues (Proxy :: Proxy a))
      case Map.lookupMax ltMap of
        Just (i, _names) -> return $ toCEnum i
        Nothing -> throw $ CEnumNoPredecessor (toInteger j)
    where
      j :: CEnumZ a
      j = fromCEnum y
  
  predSeq :: forall a. SequentialCEnum a => a -> a
  predSeq y
      | j > minZ && j <= maxZ = toCEnum (j - 1)
      | j == minZ = throw $ CEnumNoPredecessor (toInteger j)
      | otherwise = throw $ CEnumNotDeclared (toInteger j)
    where
      minZ, maxZ, j :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      j    = fromCEnum y
  
  toEnumGen :: CEnum a => Int -> a
  toEnumGen i = case mkDeclared (fromIntegral i) of
      Just x  -> x
      Nothing -> throw $ CEnumNotDeclared (toInteger i)
  
  toEnumSeq :: forall a. SequentialCEnum a => Int -> a
  toEnumSeq n
      | i >= minZ && i <= maxZ = toCEnum i
      | otherwise = throw $ CEnumNotDeclared (toInteger i)
    where
      minZ, maxZ, i :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromIntegral n
  
  fromEnumGen :: forall a. CEnum a => a -> Int
  fromEnumGen x
      | i `Map.member` getIntegralToDeclaredValues (Proxy :: Proxy a) = fromIntegral i
      | otherwise = throw $ CEnumNotDeclared (toInteger i)
    where
      i :: CEnumZ a
      i = fromCEnum x
  
  fromEnumSeq :: forall a. SequentialCEnum a => a -> Int
  fromEnumSeq x
      | i >= minZ && i <= maxZ = fromIntegral i
      | otherwise = throw $ CEnumNotDeclared (toInteger i)
    where
      minZ, maxZ, i :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
  
  enumFromGen :: forall a. CEnum a => a -> [a]
  enumFromGen x = either (throw . CEnumNotDeclared) id $ do
      (_ltMap, gtMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
      return $ x : map toCEnum (Map.keys gtMap)
    where
      i :: CEnumZ a
      i = fromCEnum x
  
  enumFromSeq :: forall a. SequentialCEnum a => a -> [a]
  enumFromSeq x
      | i >= minZ && i <= maxZ = map toCEnum [i .. maxZ]
      | otherwise = throw $ CEnumNotDeclared (toInteger i)
    where
      minZ, maxZ, i :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
  
  enumFromThenGen :: forall a. CEnum a => a -> a -> [a]
  enumFromThenGen x y = case compare i j of
      LT -> either (throw . CEnumNotDeclared) id $ do
        (_ltIMap, gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
        (ltJMap,  gtJMap) <- splitMap j gtIMap
        let w  = Map.size ltJMap + 1
            js = j : Map.keys gtJMap
        return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
      GT -> either (throw . CEnumNotDeclared) id $ do
        (ltIMap, _gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
        (ltJMap, gtJMap)  <- splitMap j ltIMap
        let w  = Map.size gtJMap + 1
            js = j : reverse (Map.keys ltJMap)
        return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
      EQ -> throw $ CEnumFromEqThen (toInteger i)
    where
      i, j :: CEnumZ a
      i = fromCEnum x
      j = fromCEnum y
  
  enumFromThenSeq :: forall a. SequentialCEnum a => a -> a -> [a]
  enumFromThenSeq x y
      | i == j = throw $ CEnumFromEqThen (toInteger i)
      | i < minZ || i > maxZ = throw $ CEnumNotDeclared (toInteger i)
      | j < minZ || j > maxZ = throw $ CEnumNotDeclared (toInteger j)
      | i < j = map toCEnum [i, j .. maxZ]
      | otherwise = map toCEnum [i, j .. minZ]
    where
      minZ, maxZ, i, j :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
      j    = fromCEnum y
  
  enumFromToGen :: forall a. CEnum a => a -> a -> [a]
  enumFromToGen x z = either (throw . CEnumNotDeclared) id $ do
      (_ltIMap, gtIMap)  <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
      if i == k
        then return [x]
        else do
          (ltKMap,  _gtKMap) <- splitMap k gtIMap
          return $ x : map toCEnum (Map.keys ltKMap) ++ [z]
    where
      i, k :: CEnumZ a
      i = fromCEnum x
      k = fromCEnum z
  
  enumFromToSeq :: forall a. SequentialCEnum a => a -> a -> [a]
  enumFromToSeq x z
      | i < minZ || i > maxZ = throw $ CEnumNotDeclared (toInteger i)
      | k < minZ || k > maxZ = throw $ CEnumNotDeclared (toInteger k)
      | otherwise = map toCEnum [i .. k]
    where
      minZ, maxZ, i, k :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
      k    = fromCEnum z
  
  enumFromThenToGen :: forall a. CEnum a => a -> a -> a -> [a]
  enumFromThenToGen x y z = case compare i j of
      LT -> either (throw . CEnumNotDeclared) id $ do
        (_ltIMap, gtIMap)  <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
        (ltJMap,  gtJMap)  <- splitMap j gtIMap
        (ltKMap,  _gtKMap) <- splitMap k gtJMap
        let w  = Map.size ltJMap + 1
            js = j : Map.keys ltKMap ++ [k]
        return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
      GT -> either (throw . CEnumNotDeclared) id $ do
        (ltIMap,  _gtIMap) <- splitMap i (getIntegralToDeclaredValues (Proxy :: Proxy a))
        (ltJMap,  gtJMap)  <- splitMap j ltIMap
        (_ltKMap, gtKMap)  <- splitMap k ltJMap
        let w  = Map.size gtJMap + 1
            js = j : reverse (k : Map.keys gtKMap)
        return $ x : map (toCEnum . NonEmpty.head) (nonEmptyChunksOf w js)
      EQ -> throw $ CEnumFromEqThen (toInteger i)
    where
      i, j, k :: CEnumZ a
      i = fromCEnum x
      j = fromCEnum y
      k = fromCEnum z
  
  enumFromThenToSeq :: forall a. SequentialCEnum a => a -> a -> a -> [a]
  enumFromThenToSeq x y z
      | i == j = throw $ CEnumFromEqThen (toInteger i)
      | i < minZ || i > maxZ = throw $ CEnumNotDeclared (toInteger i)
      | j < minZ || j > maxZ = throw $ CEnumNotDeclared (toInteger j)
      | k < minZ || k > maxZ = throw $ CEnumNotDeclared (toInteger k)
      | otherwise = map toCEnum [i, j .. k]
    where
      minZ, maxZ, i, j, k :: CEnumZ a
      minZ = fromCEnum (minDeclaredValue @a)
      maxZ = fromCEnum (maxDeclaredValue @a)
      i    = fromCEnum x
      j    = fromCEnum y
      k    = fromCEnum z
  
  {-------------------------------------------------------------------------------
    Auxiliary Functions
  -------------------------------------------------------------------------------}
  
  getIntegralToDeclaredValues :: CEnum a => proxy a -> Map (CEnumZ a) (NonEmpty String)
  getIntegralToDeclaredValues = integralToDeclaredValues . declaredValues
  
  nonEmptyChunksOf :: Int -> [a] -> [NonEmpty a]
  nonEmptyChunksOf n xs
      | n > 0     = aux xs
      | otherwise = error $ "nonEmptyChunksOf: n must be positive, got " ++ show n
    where
      aux :: [a] -> [NonEmpty a]
      aux xs' = case first NonEmpty.nonEmpty (List.splitAt n xs') of
        (Just ne, rest)  -> ne : aux rest
        (Nothing, _rest) -> []
  
  splitMap :: Integral k => k -> Map k v -> Either Integer (Map k v, Map k v)
  splitMap n m = case Map.splitLookup n m of
      (ltMap,  Just{},  gtMap)  -> Right (ltMap, gtMap)
      (_ltMap, Nothing, _gtMap) -> Left (toInteger n)
  