{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE PolyKinds #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE KindSignatures  #-}
{-# LANGUAGE AllowAmbiguousTypes #-}


-- | TypeLits related utilities.
--
-- Lots of this could be avoided by adding @singletons@ as dependency.
--
-- Uses @symbols@ library for its ToList type family.
--
-- Currently this is spread out in different modules
--
-- * "Data.TypedEncoding.Common.Class.Util"
-- * "Data.TypedEncoding.Common.Types.SomeAnnotation"
--
-- (TODO) these will need to get consolidated.
module  Data.TypedEncoding.Common.Util.TypeLits where

import           GHC.TypeLits
import           Data.Symbol.Ascii


-- $setup
-- >>> :set -XScopedTypeVariables -XTypeFamilies -XKindSignatures -XDataKinds

-- !
-- @since 0.2.1.0
type family AcceptEq (msg :: ErrorMessage) (c :: Ordering) :: Bool where
    AcceptEq _  EQ = True
    AcceptEq msg _ =  TypeError msg

-- !
-- @since 0.4.0.0
type family OrdBool (c :: Ordering) :: Bool where
    OrdBool EQ = 'True
    OrdBool _  =  'False


-- !
-- @since 0.2.1.0
type family And (b1 :: Bool) (b2 :: Bool) :: Bool where
    And 'True 'True = 'True
    And _ _ = 'False

-- !
-- @since 0.2.1.0
type family Or (b1 :: Bool) (b2 :: Bool) :: Bool where
    Or 'False 'False = 'False
    Or _ _ = 'True

-- !
-- @since 0.2.1.0
type family If (b1 :: Bool) (a :: k) (b :: k) :: k where
    If 'True a _ = a
    If 'False _ b = b

-- !
-- @since 0.2.1.0
type family Repeat (n :: Nat) (s :: Symbol) :: Symbol where
    Repeat 0 s = ""
    Repeat n s = AppendSymbol s (Repeat (n - 1) s)

-- !
-- @since 0.2.1.0
type family Fst (s :: (k,h)) :: k where
   Fst ('(,) a _) = a

type family Dupl (s :: k) :: (k,k) where
   Dupl a = '(,) a a

-- | 
-- >>> :kind! Concat (LDrop 6 (ToList "bool: \"r-ban:ff-ff\" | \"r-ban:ffff\""))
-- ...
-- = "\"r-ban:ff-ff\" | \"r-ban:ffff\""
-- 
-- @since 0.2.1.0
type family Concat (s :: [Symbol]) :: Symbol where
    Concat '[] = ""
    Concat (x ': xs) = AppendSymbol x (Concat xs)

-- !
-- @since 0.2.1.0
type family Drop (n :: Nat) (s :: Symbol) :: Symbol where
    Drop n s = Concat (LDrop n (ToList s))

-- TODO create TypeList.List module ?

-- :kind! LDrop 6 (ToList "bool: \"r-ban:ff-ff\" | \"r-ban:ffff\"")

-- | 
-- 
-- @since 0.2.1.0
type family LDrop (n :: Nat) (s :: [k]) :: [k] where
    LDrop 0 s = s
    LDrop n '[] = '[]
    LDrop n (x ': xs) = LDrop (n - 1) xs


-- type family DropVerify (n :: Nat) (s :: Symbol) (v :: Symbol) :: Symbol where
--     Drop n s v = Concat (LDrop n (ToList s))

-- type family DropVerify (n :: Nat) (s :: Symbol) (v :: Symbol) :: Symbol where
--     Drop n s v = Concat (LDrop n (ToList s))

-- | 
-- >>> :kind! Take 3 "123456"
-- ...
-- = "123"
-- 
-- @since 0.2.1.0
type family Take (n :: Nat) (s :: Symbol) :: Symbol where
    Take n s = Concat (LTake n (ToList s))

-- :kind! LTake 3 (ToList "123456")

-- | 
-- 
-- @since 0.2.1.0
type family LTake (n :: Nat) (s :: [k]) :: [k] where
    LTake 0 s = '[]
    LTake n '[] = '[]
    LTake n (x ': xs) = x ': LTake (n - 1) xs

-- 
-- >>> kind! TakeUntil "findme:blah" ":"
-- ...
-- = "findme"
--
-- @since 0.2.1.0
type family TakeUntil (s :: Symbol) (stop :: Symbol) :: Symbol where
    TakeUntil s stop = Concat (LTakeUntil (ToList s) stop)

-- | 
-- 
-- @since 0.2.1.0
type family LTakeUntil (s :: [Symbol]) (stop :: Symbol) :: [Symbol] where
    LTakeUntil '[] _ = '[]
    LTakeUntil (x ': xs) stop = LTakeUntilHelper (x ': LTakeUntil xs stop) (CmpSymbol x stop)

type family LTakeUntilHelper (s :: [Symbol]) (o :: Ordering) :: [Symbol] where
    LTakeUntilHelper '[] _ = '[]
    LTakeUntilHelper (x ': xs) 'EQ = '[]
    LTakeUntilHelper (x ': xs) _ = (x ': xs)

-- | 
-- 
-- @since 0.2.1.0
type family Length (s :: Symbol) :: Nat where
    Length x = LLengh (ToList x)

-- | 
-- 
-- @since 0.2.1.0
type family LLengh (s :: [k]) :: Nat where
    LLengh '[] = 0
    LLengh (x ': xs) = 1 + LLengh xs


-- |
-- >>> :kind! LLast '["1","2","3"]
-- ...
-- = "3"
--
-- @since 0.2.2.0
type family LLast (s :: [Symbol]) :: Symbol where
    LLast '[] = TypeError ('Text "Empty Symbol list not allowed")
    LLast '[x] = x
    LLast (_ ': xs) = LLast xs



-- |
-- >>> :kind! Concat (Snoc '["1","2","3"] "4") 
-- ...
-- = "1234"
--
-- @since 0.2.2.0
type family Snoc (s :: [k]) (t :: k) :: [k] where
    Snoc '[] x = '[x]
    Snoc (x ': xs) y = x ': Snoc xs y


-- |
-- :kind! UnSnoc '["1","2","3"]  
-- ...
-- = '( (':) Symbol "1" ((':) Symbol "2" ('[] Symbol)), "3")
--
-- @since 0.2.2.0  
type family UnSnoc (s :: [k]) :: ([k], k) where
    UnSnoc '[] = TypeError ('Text "Empty list, no last element")
    UnSnoc '[x] = '(,) '[] x
    UnSnoc (x ': xs) = UnSnocHelper x (UnSnoc xs)


type family UnSnocHelper (s :: k) (t :: ([k], k)) :: ([k], k) where
   UnSnocHelper y ('(,) xs x) = '(,) (y ': xs) x