content: -- | Encoding-agnosting deserialization utilities.
  {-# LANGUAGE AllowAmbiguousTypes   #-}
  {-# LANGUAGE ApplicativeDo         #-}
  {-# LANGUAGE ConstraintKinds       #-}
  {-# LANGUAGE DataKinds             #-}
  {-# LANGUAGE ExplicitNamespaces    #-}
  {-# LANGUAGE FlexibleContexts      #-}
  {-# LANGUAGE FlexibleInstances     #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE RankNTypes            #-}
  {-# LANGUAGE ScopedTypeVariables   #-}
  {-# LANGUAGE TypeApplications      #-}
  {-# LANGUAGE TypeFamilies          #-}
  {-# LANGUAGE TypeInType            #-}
  {-# LANGUAGE TypeOperators         #-}
  {-# LANGUAGE UndecidableInstances  #-}
  module Versioning.Internal.Decoding
    ( -- * Types
      Applied
    , Apply
    , ApplyM
    , DecodableTo
    , DecodableToFrom
    , DecodeAnyVersion
    , Decoder (..)
    , WithAnyVersion
    , WithAnyVersionFrom
      -- * Decoding and upgrading
    , decodeAnyVersion
    , decodeAnyVersionFrom
      -- * Decoding and applying an action
    , withAnyVersion
    , withAnyVersionM
    , withAnyVersionFromM
    , withAnyVersionFrom
    )
  where
  
  import           Data.Functor.Alt             (Alt (..))
  import           Data.Functor.Identity        (Identity (..))
  import           Data.Kind                    (Constraint, Type)
  
  import           Versioning.Base
  import           Versioning.Internal.Equality (type (==))
  import           Versioning.Upgrade
  
  -- | The result type of the action that has been applied to the decoded object
  --   with 'withAnyVersion' or 'withAnyVersionM'.
  type family Applied (c :: Type -> Constraint) (a :: V -> Type) :: Type
  
  -- | The pure function to apply to the decoded object with 'withAnyVersion'
  type Apply a c = forall v. c (a v) => a v -> Applied c a
  
  -- | The action to apply to the decoded object with 'withAnyVersionM'
  type ApplyM m a c = forall v. c (a v) => a v -> m (Applied c a)
  
  -- | The function that will perform the actual decoding
  newtype Decoder dec enc t a = Decoder (forall v. dec (a v) => enc -> t (a v))
  
  -- | Handy constraint synonym to be used with 'decodeAnyVersion'
  type DecodableTo dec v a = DecodableToFrom V0 dec v a
  
  type DecodableToFrom from dec v a = DecodeAnyVersionFrom from v v a dec
  
  decodeAnyVersion
    :: forall v a dec enc t
     . (Alt t, Applicative t, DecodableTo dec v a)
    => Decoder dec enc t a
    -> enc
    -> t (a v)
  decodeAnyVersion = decodeAnyVersionFrom @V0
  
  -- | Decode by trying all the versions decrementally
  --   and upgrade the decoded object to the newest version.
  decodeAnyVersionFrom
    :: forall from v a dec enc t
     . (Alt t, Applicative t, DecodableToFrom from dec v a)
    => Decoder dec enc t a
    -> enc
    -> t (a v)
  decodeAnyVersionFrom = decodeAnyVersion' @(from == v) @from @v @v
  
  -- | Decode by trying all the versions decrementally
  --   and apply an action to the decoded object at its original version.
  withAnyVersionM
    :: forall v c a dec enc m t
     . (WithAnyVersion v a c dec, Alt t, Applicative t, Traversable t, Applicative m, c (a v))
    => Decoder dec enc t a
    -> ApplyM m a c
    -> enc
    -> m (t (Applied c a))
  withAnyVersionM = withAnyVersionFromM @V0 @v @c
  
  -- | Like 'withAnyVersionM', with an additional type-parameter
  --   indicating the oldest version you want to be able to decode
  withAnyVersionFromM
    :: forall from v c a dec enc m t
     . (WithAnyVersionFrom from v a c dec, Alt t, Applicative t, Traversable t, Applicative m, c (a v))
    => Decoder dec enc t a
    -> ApplyM m a c
    -> enc
    -> m (t (Applied c a))
  withAnyVersionFromM = withAnyVersion' @(from == v) @from @v @a @c @dec
  
  -- | Pure version of 'withAnyVersionM'.
  withAnyVersion
    :: forall v c a dec enc t
     . (WithAnyVersion v a c dec, c (a v), Alt t, Applicative t, Traversable t)
    => Decoder dec enc t a
    -> Apply a c
    -> enc
    -> t (Applied c a)
  withAnyVersion dec action =
    runIdentity . withAnyVersionM @v @c @a dec (Identity . action)
  
  -- | Pure version of 'withAnyVersionFromM'.
  withAnyVersionFrom
    :: forall from v c a dec enc t
     . (WithAnyVersionFrom from v a c dec, c (a v), Alt t, Applicative t, Traversable t)
    => Decoder dec enc t a
    -> Apply a c
    -> enc
    -> t (Applied c a)
  withAnyVersionFrom dec action =
    runIdentity . withAnyVersionFromM @from @v @c @a dec (Identity . action)
  
  type DecodeAnyVersion v w a dec = DecodeAnyVersionFrom V0 v w a dec
  
  type DecodeAnyVersionFrom from v w a dec = DecodeAnyVersion' (from == v) from v w a dec
  
  class DecodeAnyVersion' (eq :: Bool) (from :: V) (v :: V) (w :: V) (a :: V -> Type) dec where
      decodeAnyVersion'
        :: (Alt t, Applicative t)
        => Decoder dec enc t a
        -> enc
        -> t (a w)
  
  instance (from ~ v, dec (a from), Upgrade from w a)
    => DecodeAnyVersion' 'True from v w a dec where
      decodeAnyVersion' (Decoder decode) bs = upgrade @from @w <$> decode @from bs
  
  instance (DecodeAnyVersion' (VPred v == from) from (VPred v) w a dec, dec (a v), dec (a (VPred v)), Upgrade v w a)
    => DecodeAnyVersion' 'False from v w a dec where
      decodeAnyVersion' decoder@(Decoder decode) bs = upgrade @v @w <$> decode @v bs
                                                  <!> decodeAnyVersion' @(VPred v == from) @from @(VPred v) @w decoder bs
  
  type WithAnyVersion v a c dec = WithAnyVersionFrom V0 v a c dec
  
  type WithAnyVersionFrom from v a c dec = WithAnyVersion' (from == v) from v a c dec
  
  class WithAnyVersion' (eq :: Bool) (from :: V) (v :: V) (a :: V -> Type) c dec where
      withAnyVersion' :: (Applicative m, Alt t, Applicative t, Traversable t, c (a v))
                      => Decoder dec enc t a
                      -> ApplyM m a c
                      -> enc
                      -> m (t (Applied c a))
  
  instance (from ~ v, dec (a from), c (a from))
    => WithAnyVersion' 'True from v a c dec where
      withAnyVersion' (Decoder decode) action bs = traverse action (decode @from bs)
  
  instance (WithAnyVersion' (VPred v == from) from (VPred v) a c dec, dec (a v), dec (a (VPred v)), c (a v), c (a (VPred v)))
    => WithAnyVersion' 'False from v a c dec where
      withAnyVersion' dec@(Decoder decode) action bs = do
          res  <- traverse action (decode @v bs)
          next <- withAnyVersion' @(VPred v == from) @from @(VPred v) @a @c dec action bs
          pure (res <!> next)
  