{-# LANGUAGE DataKinds              #-}
{-# LANGUAGE PolyKinds              #-}
{-# LANGUAGE TypeFamilies           #-}
{-# LANGUAGE TypeInType             #-}
{-# LANGUAGE TypeOperators          #-}
{-# LANGUAGE UndecidableInstances   #-}
{-# OPTIONS_GHC -Wall                       #-}
{-# OPTIONS_GHC -Werror=incomplete-patterns #-}

{-|
Module      : Fcf.Data.Symbol
Description : Type level symbols
Copyright   : (c) gspia 2020-
License     : BSD
Maintainer  : gspia

= Fcf.Data.Symbol

Type-level symbols and functions for them.

Note that the operators from this module conflict with "GHC.TypeLits".


TODO: Would this whole module have a place first-class-families?

-}

--------------------------------------------------------------------------------

module Fcf.Data.Symbol
    ( -- * Reexported type
      -- | From "GHC.TypeList".

      Symbol

      -- * Functions

    , Append
    , Intercalate
    , IsSpace
    , IsNewLine
    , IsTab
    , IsSpaceDelim
    , IsDigit

     -- * Comparison functions

    , SymbolOrd
    , type (<=)
    , type (>=)
    , type (<)
    , type (>)
    , type (==)
    )
  where

--------------------------------------------------------------------------------

import           GHC.TypeLits (Symbol)
import qualified GHC.TypeLits as TL

import           Fcf.Core (Eval, Exp)
import           Fcf.Data.List (Foldr, Elem)
import           Fcf.Data.Bool (type (||))
import           Fcf.Utils (TyEq)

import           Fcf.Combinators (type (=<<))

--------------------------------------------------------------------------------

-- | Append two type-level symbols.
--
-- === __Example__
-- 
-- >>> :kind! Eval (Append "hmm" " ok")
-- Eval (Append "hmm" " ok") :: Symbol
-- = "hmm ok"
data Append :: Symbol -> Symbol -> Exp Symbol
type instance Eval (Append s1 s2) = TL.AppendSymbol s1 s2


-- | Intercalate type-level symbols.
-- 
-- === __Example__
-- 
-- >>> :kind! Eval (Intercalate "+" '["aa", "bb", "cc"])
-- Eval (Intercalate "+" '["aa", "bb", "cc"]) :: Symbol
-- = "aa+bb+cc"
--
-- >>> :kind! Eval (Intercalate "+" '["aa"])
-- Eval (Intercalate "+" '["aa"]) :: Symbol
-- = "aa"
--
-- >>> :kind! Eval (Intercalate "+" '[])
-- Eval (Intercalate "+" '[]) :: Symbol
-- = ""
data Intercalate :: Symbol -> [Symbol] -> Exp Symbol
type instance Eval (Intercalate s1 '[]) = ""
type instance Eval (Intercalate s1 (s ': sLst)) =
    Eval (Append s =<< Foldr (InterCalHelp s1) "" sLst)

-- helper
data InterCalHelp :: Symbol -> Symbol -> Symbol -> Exp Symbol
type instance Eval (InterCalHelp s s1 s2) = Eval (Append (Eval (Append s s1)) s2)


-- | IsSpace
--
-- === __Example__
-- 
-- >>> :kind! Eval (IsSpace "a")
-- Eval (IsSpace "a") :: Bool
-- = 'False
--
-- >>> :kind! Eval (IsSpace " ")
-- Eval (IsSpace " ") :: Bool
-- = 'True
data IsSpace :: Symbol -> Exp Bool
type instance Eval (IsSpace s) = Eval (s == " ")


-- | IsNewline
--
-- === __Example__
-- 
-- >>> :kind! Eval (IsNewLine "a")
-- Eval (IsNewLine "a") :: Bool
-- = 'False
--
-- >>> :kind! Eval (IsNewLine "\n")
-- Eval (IsNewLine "\n") :: Bool
-- = 'True
data IsNewLine :: Symbol -> Exp Bool
type instance Eval (IsNewLine s) = Eval (s == "\n")


-- | IsTab
--
-- === __Example__
-- 
-- >>> :kind! Eval (IsTab "a")
-- Eval (IsTab "a") :: Bool
-- = 'False
--
-- >>> :kind! Eval (IsTab "\t")
-- Eval (IsTab "\t") :: Bool
-- = 'True
data IsTab :: Symbol -> Exp Bool
type instance Eval (IsTab s) = Eval (s == "\t")


-- | IsSpaceDelim
--
-- === __Example__
-- 
-- >>> :kind! Eval (IsSpaceDelim "a")
-- Eval (IsSpaceDelim "a") :: Bool
-- = 'False
--
-- >>> :kind! Eval (IsSpaceDelim "\n")
-- Eval (IsSpaceDelim "\n") :: Bool
-- = 'True
data IsSpaceDelim :: Symbol -> Exp Bool
type instance Eval (IsSpaceDelim s) =
    Eval (Eval (IsSpace s) || (Eval (Eval (IsNewLine s) || Eval (IsTab s))))


-- | IsDigit
--
-- === __Example__
-- 
-- >>> :kind! Eval (IsDigit "3")
-- Eval (IsDigit "3") :: Bool
-- = 'True
--
-- >>> :kind! Eval (IsDigit "a")
-- Eval (IsDigit "a") :: Bool
-- = 'False
data IsDigit :: Symbol -> Exp Bool
type instance Eval (IsDigit s)
    = Eval (Elem s '["0","1","2","3","4","5","6","7","8","9"])



--------------------------------------------------------------------------------


-- | SymbolOrd - compare two symbols and give type-level Ordering 
-- ( $ 'LT $, $ 'EQ $ or $ 'GT $ ).
--
-- === __Example__
-- 
-- >>> :kind! Eval (SymbolOrd "a" "b")
-- Eval (SymbolOrd "a" "b") :: Ordering
-- = 'LT
data SymbolOrd :: Symbol -> Symbol -> Exp Ordering
type instance Eval (SymbolOrd a b) = TL.CmpSymbol a b

-- | Less-than-or-equal comparison for symbols.
-- 
-- === __Example__
-- 
-- >>> :kind! Eval ("b" <= "a")
-- Eval ("b" <= "a") :: Bool
-- = 'False
--
data (<=) :: Symbol -> Symbol -> Exp Bool
type instance Eval ((<=) a b) =
    Eval (Eval (TyEq (TL.CmpSymbol a b) 'LT) || Eval (TyEq (TL.CmpSymbol a b) 'EQ))

-- | Larger-than-or-equal comparison for symbols.
-- 
-- === __Example__
-- 
-- >>> :kind! Eval ("b" >= "a")
-- Eval ("b" >= "a") :: Bool
-- = 'True
data (>=) :: Symbol -> Symbol -> Exp Bool
type instance Eval ((>=) a b) =
    Eval (Eval (TyEq (TL.CmpSymbol a b) 'GT) || Eval (TyEq (TL.CmpSymbol a b) 'EQ))

-- | Less-than comparison for symbols.
-- 
-- === __Example__
-- 
-- >>> :kind! Eval ("a" < "b")
-- Eval ("a" < "b") :: Bool
-- = 'True
data (<) :: Symbol -> Symbol -> Exp Bool
type instance Eval ((<) a b) = Eval (TyEq (TL.CmpSymbol a b) 'LT)

-- | Larger-than comparison for symbols.
-- 
-- === __Example__
-- 
-- >>> :kind! Eval ("b" > "a")
-- Eval ("b" > "a") :: Bool
-- = 'True
data (>) :: Symbol -> Symbol -> Exp Bool
type instance Eval ((>) a b) = Eval (TyEq (TL.CmpSymbol a b) 'GT)

-- | Equality of symbols
-- 
-- === __Example__
-- 
-- >>> :kind! Eval ("b" == "a")
-- Eval ("b" == "a") :: Bool
-- = 'False
data (==) :: Symbol -> Symbol -> Exp Bool
type instance Eval ((==) a b) = Eval (TyEq (TL.CmpSymbol a b) 'EQ)