content: -- | This module provides some tools to encode multiple versions
  --   of a data model in a single data-type parametrized by version number.
  --   The addition or removal of a field can be expressed through the
  --   'Since' and 'Until' type families.
  --
  --   Example:
  --
  -- > data Rec v = Rec
  -- >     { foo :: Int               -- this field exists in all versions
  -- >     , bar :: Since V2 v Bool   -- this field has been introduced in V2
  -- >     , baz :: Until V2 v Double -- this field has been removed in V3
  -- >     }
  --
  --   Besides reducing the number of data declarations,
  --   this approach also has other advantages:
  --
  --   * It makes migrations declarative and self-documenting.
  --
  --   * It allows for less verbose version-upcasting functions,
  --     since the fields that have a non-parametric type do not need to be copied.
  --
  --   * It is a foundation on which other useful abstractions can be built.
  --
  --   Please note that some classes may require a separate standalone deriving clause
  --   for each version of a data-type or some kind of inductive deriving mechanism.
  {-# LANGUAGE DataKinds             #-}
  {-# LANGUAGE ExplicitNamespaces    #-}
  {-# LANGUAGE FlexibleContexts      #-}
  {-# LANGUAGE MultiParamTypeClasses #-}
  {-# LANGUAGE ScopedTypeVariables   #-}
  {-# LANGUAGE TypeFamilies          #-}
  {-# LANGUAGE TypeOperators         #-}
  {-# LANGUAGE UndecidableInstances  #-}
  module Versioning.Base (
        -- * Types
        V (..)
      , VPred
      , VSucc
      , VNat
      , VCmp
      , Since
      , SinceS
      , Until
      , UntilS
      , NA
      , na
      , V0
      , V1
      , V2
      , V3
      , V4
      , V5
      , V6
      , V7
      , V8
      , V9
      , V10
      , V11
      , V12
      , V13
      , V14
      , V15
      , V16
      , V17
      , V18
      , V19
      , V20
        -- * Functions
      , versionNumber
  ) where
  
  import           Data.Proxy               (Proxy (..))
  import           GHC.TypeNats             (type (+), KnownNat, Nat, natVal)
  import           Numeric.Natural          (Natural)
  
  import           Versioning.Internal.Base (Bare)
  
  -- | The version of a data model
  data V = VZero | VSucc V deriving (Eq, Show)
  
  -- | Get the previous version
  type family VPred (v :: V) :: V where
      VPred ('VSucc v) = v
      VPred 'VZero = 'VZero
  
  type VSucc = 'VSucc
  
  -- | Get the type-level natural of a version
  type family VNat (v :: V) :: Nat where
      VNat v = VNat' v 0
  
  type family VNat' (v :: V) (n :: Nat) :: Nat where
      VNat' 'VZero n = n
      VNat' ('VSucc v) n = VNat' v (n + 1)
  
  -- | Compare two versions
  type family VCmp (v :: V) (w :: V) :: Ordering where
      VCmp 'VZero 'VZero = 'EQ
      VCmp 'VZero v = 'LT
      VCmp v 'VZero = 'GT
      VCmp ('VSucc v) ('VSucc w) = VCmp v w
  
  -- | Get the version number of a versioned value
  versionNumber :: forall a v. KnownNat (VNat v) => a v -> Natural
  versionNumber _ = natVal (Proxy :: Proxy (VNat v))
  
  -- | This allows us to express that a field is only present since
  --   a given version.
  --   The first parameter is the version in which the field has been introduced,
  --   the second parameter is the actual version of the data-type.
  type family Since (s :: V) (v :: V) a where
      Since s v a = Since' (VCmp s v) a NA
  
  -- | Same as 'Since', for sum types.
  --   The only difference between 'Since' and 'SinceS' is in the type used to
  --   indicate absence.
  --   In 'Since' absence is expressed with 'NA', which is isomorphic to '()'.
  --   In 'SinceS' it is expressed with 'Bare', which is isomorphic to 'Void'.
  type family SinceS (s :: V) (v :: V) a where
      SinceS s v a = Since' (VCmp s v) a Bare
  
  type family Since' (o :: Ordering) a absent :: * where
      Since' 'LT a _ = a
      Since' 'EQ a _ = a
      Since' 'GT a absent = absent
  
  -- | This allows us to express that a field is only present until
  --   a given version.
  --   The first parameter is the last version in which the field is present,
  --   the second parameter is the actual version of the data-type.
  type family Until (u :: V) (v :: V) a where
      Until u v a = Until' (VCmp u v) a NA
  
  -- | Same as 'Until', for sum types.
  type family UntilS (u :: V) (v :: V) a where
      UntilS u v a = Until' (VCmp u v) a Bare
  
  type family Until' (o :: Ordering) a absent :: * where
      Until' 'GT a _ = a
      Until' 'EQ a _ = a
      Until' 'LT a absent = absent
  
  -- | A type indicating absence.
  --   The 'Maybe' is a hack needed to let aeson parse a record successfully even
  --   if a field of type 'NA' is missing.
  --
  --   Ideally we would like to define it as
  --
  -- > data NA = NA
  --
  --   but this would not work with 'FromJSON' instances that are derived
  --   with Generic.
  type NA = Maybe Bare
  
  -- | A placeholder for an absent value.
  na :: NA
  na = Nothing
  
  -- Version type synonyms
  type V0 = 'VZero
  
  type V1 = 'VSucc V0
  
  type V2 = 'VSucc V1
  
  type V3 = 'VSucc V2
  
  type V4 = 'VSucc V3
  
  type V5 = 'VSucc V4
  
  type V6 = 'VSucc V5
  
  type V7 = 'VSucc V6
  
  type V8 = 'VSucc V7
  
  type V9 = 'VSucc V8
  
  type V10 = 'VSucc V9
  
  type V11 = 'VSucc V10
  
  type V12 = 'VSucc V11
  
  type V13 = 'VSucc V12
  
  type V14 = 'VSucc V13
  
  type V15 = 'VSucc V14
  
  type V16 = 'VSucc V15
  
  type V17 = 'VSucc V16
  
  type V18 = 'VSucc V17
  
  type V19 = 'VSucc V18
  
  type V20 = 'VSucc V19
  