{-# LANGUAGE TypeFamilies #-}

module Servant.Pagination.Internal where

import           Data.Kind      (Constraint)
import           Data.Proxy     (Proxy (..))
import           Data.Semigroup ((<>))
import           Data.Text      (Text)
import           Servant        (FromHttpApiData (..), ToHttpApiData (..))


-- | Helper to execute two `Either a b` successively
orElse :: Either a b -> Either a b -> Either a b
orElse a b =
  either (const b) (const a) a
{-# INLINE orElse #-}


-- | Representation of AcceptRanges as a list of comma-separated text fields from the type
-- of a Range only (value isn't needed here since only the 'field' matters)
class ToAcceptRanges r where
  toAcceptRanges :: Proxy r -> Text


-- | Combine two ranges in a new range, parsing is done left-first
data a :|: b = InL a | InR b
infixl 7 :|:

instance (ToHttpApiData a, ToHttpApiData b) => ToHttpApiData (a :|: b) where
  toUrlPiece (InL a) =
    toUrlPiece a

  toUrlPiece (InR b) =
    toUrlPiece b

instance (FromHttpApiData a, FromHttpApiData b) => FromHttpApiData (a :|: b) where
  parseUrlPiece txt =
             (liftRange <$> (parseUrlPiece txt :: Either Text a))
    `orElse` (liftRange <$> (parseUrlPiece txt :: Either Text b))

instance (ToAcceptRanges a, ToAcceptRanges b) => ToAcceptRanges (a :|: b) where
  toAcceptRanges _ =
    toAcceptRanges (Proxy :: Proxy a) <> "," <>  toAcceptRanges (Proxy :: Proxy b)


-- | Type family helper to define a constraint about ranges
type family InRanges r rs :: Constraint where
  InRanges r r           = ()
  InRanges r (rs :|: r)  = ()
  InRanges r (rs :|: r') = InRanges r rs


-- | Relation for lifting range into a combination of ranges
class range :<: ranges where
  liftRange :: range -> ranges

instance r :<: r where
  liftRange = id
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2) where
  liftRange = InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r) where
  liftRange = InR
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3) where
  liftRange = InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3) where
  liftRange = InL . InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3 :|: r4) where
  liftRange = InL . InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3 :|: r4) where
  liftRange = InL . InL . InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3 :|: r4 :|: r5) where
  liftRange = InL . InL . InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3 :|: r4 :|: r5) where
  liftRange = InL . InL . InL . InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3 :|: r4 :|: r5 :|: r6) where
  liftRange = InL . InL . InL . InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3 :|: r4 :|: r5 :|: r6) where
  liftRange = InL . InL . InL . InL . InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3 :|: r4 :|: r5 :|: r6 :|: r7) where
  liftRange = InL . InL . InL . InL . InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3 :|: r4 :|: r5 :|: r6 :|: r7) where
  liftRange = InL . InL . InL . InL . InL . InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3 :|: r4 :|: r5 :|: r6 :|: r7 :|: r8) where
  liftRange = InL . InL . InL . InL . InL . InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3 :|: r4 :|: r5 :|: r6 :|: r7 :|: r8) where
  liftRange = InL . InL . InL . InL . InL . InL . InL
  {-# INLINE liftRange #-}

instance r :<: (r1 :|: r :|: r3 :|: r4 :|: r5 :|: r6 :|: r7 :|: r8 :|: r9) where
  liftRange = InL . InL . InL . InL . InL . InL . InL . InR
  {-# INLINE liftRange #-}

instance r :<: (r :|: r2 :|: r3 :|: r4 :|: r5 :|: r6 :|: r7 :|: r8 :|: r9) where
  liftRange = InL . InL . InL . InL . InL . InL . InL . InL
  {-# INLINE liftRange #-}