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

{-|
Module      : Fcf.Data.NewText
Description : Type-level Text data structure with methods
Copyright   : (c) gspia 2020-
License     : BSD
Maintainer  : gspia

= Fcf.Data.NewText

We mimick Data.Text but on type level. The current internal representation
of Fcf.Data.Text is based
on type level lists.  The current (as of early 2023) implementation of this
Fcf.Data.Text will be deprecated and replaced with the contents of Fcf.Data.NewText
later 2023 as newer version GHC become more widespread.

The old version working with 9.0.x or less will be kept at Fcf.Data.OldText for
some time.  Similarly, the module Fcf.Data.NewText contains the functions
and definitions for better Text type, which will be taken into use after some time.

The Fcf.Data.NewText will replace Fcf.Data.Text eventually.

-}

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

module Fcf.Data.NewText
#if __GLASGOW_HASKELL__ >= 902
    ( Text (..)

    -- * Creation
    , Empty
    , Singleton
    , FromList
    , FromSymbolList
    , FromSymbol
    , ToList
    , ToSymbolList
    , ToCharList
    , Unpack

    -- * Basic Interface
    , Null
    , Length
    , Append
    , Cons
    , ConsSymbol
    , Snoc
    , SnocSymbol
    , Uncons
    , Unsnoc
    , Head
    , Last
    , Tail
    , Init
    , CompareLength

    -- * Transformation
    , FMap
    , FMapSymbol
    , FMapT
    , Intercalate
    , Intersperse
    , IntersperseSymbol
    , Reverse
    , Replace

    -- * Special Folds
    , Concat
    , ConcatMap
    , ConcatMapSymbol
    , ConcatMapCS
    , Any
    , AnySymbol
    , All
    , AllSymbol

    -- * Substrings
    , Take
    , TakeEnd
    , Drop
    , DropEnd
    , TakeWhile
    , TakeWhileSymbol
    , TakeWhileEnd
    , TakeWhileEndSymbol
    , DropWhile
    , DropWhileEnd
    , DropWhileEndSymbol
    , DropAround
    , DropAroundSymbol
    , Strip

    -- * Breaking etc
    , SplitOn
    , Split
    , Lines
    , Words
    , Unlines
    , Unwords

    -- * Predicates
    , IsPrefixOf
    , IsSuffixOf
    , IsInfixOf
    )
#endif
  where

-- Version should be larger than 9.2.x. Note the two digit space for the
-- minor.
#if __GLASGOW_HASKELL__ >= 902

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

import           Fcf ( If, Eval, Exp, type (=<<), type (@@)
                     , Flip, Pure, IsNothing, Fst, Snd)
import qualified Fcf.Class.Foldable as F (All, Any)
import           Fcf.Data.List ( type (++) )
import qualified Fcf.Data.List as F
    ( Length, Init, Reverse, Take, Drop, TakeWhile, DropWhile
    , Intercalate, Intersperse, IsPrefixOf, IsSuffixOf, IsInfixOf, Snoc)

import qualified Fcf.Class.Functor as F ( FMap )


import           Fcf.Data.Nat (Nat)
import qualified Fcf.Data.Symbol as S
import           Fcf.Data.Tuple (Swap)
import           Fcf.Alg.Other ( PairMaybeToMaybePair )
import           Fcf.Alg.Morphism (First,Second)
-- import           Fcf.Alg.Nat (type (==))
import qualified Fcf.Alg.Symbol as Sym

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


-- For the doctests:

-- $setup
-- >>> import           Fcf (type (<=<), Not)
-- >>> import           Fcf.Data.Char as C

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

-- | 'Text' is a data structure, that is, a list to hold type-level symbols of
-- length one.
data Text = Text Symbol

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

-- | Empty
--
-- === __Example__
--
-- >>> :kind! (Eval Empty :: Text)
-- (Eval Empty :: Text) :: Text
-- = 'Text ""
--
-- See also the other examples in this module.
data Empty :: Exp Text
type instance Eval Empty = 'Text ""

-- | Singleton
--
-- >>> :kind! Eval (Singleton 'a')
-- Eval (Singleton 'a') :: Text
-- = 'Text "a"
data Singleton :: Char -> Exp Text
type instance Eval (Singleton c) = Eval (FromSymbol =<< S.CharToSymbol c)


-- | FromSymbol
--
-- === __Example__
--
-- >>> :kind! Eval (FromSymbol "some text")
-- Eval (FromSymbol "some text") :: Text
-- = 'Text "some text"
data FromSymbol :: Symbol -> Exp Text
type instance Eval (FromSymbol s) = 'Text s


-- | Use FromList to construct a Text from type-level list.
--
-- === __Example__
--
-- :kind! Eval (FromSymbolList '["h", "e", "l", "l", "u", "r", "e", "i"])
-- Eval (FromSymbolList '["h", "e", "l", "l", "u", "r", "e", "i"]) :: Text
-- = 'Text "hellurei"
data FromSymbolList :: [Symbol] -> Exp Text
type instance Eval (FromSymbolList sym) = Eval (FromSymbol =<< S.Concat sym)


data FromList :: [Text] -> Exp Text
type instance Eval (FromList txt) =
    Eval (FromSymbol =<< S.Concat =<< F.FMap Unpack txt)
-- hmm, this is also a Monoid, so we should be able to use Concat
    

-- | Get the type-level list out of the 'Text'.
--
-- === __Example__
--
-- >>> :kind! Eval (ToSymbolList =<< FromSymbolList '["a", "b"])
-- Eval (ToSymbolList =<< FromSymbolList '["a", "b"]) :: [Symbol]
-- = '["a", "b"]
data ToSymbolList :: Text -> Exp [Symbol]
type instance Eval (ToSymbolList ('Text sym)) =
    Eval (F.FMap S.CharToSymbol =<< S.ToCharList sym)




-- | Split text to characters and give them as Char list.
--
-- > :kind! Eval (ToCharList =<< FromSymbol "abc")
-- Eval (ToCharList =<< FromSymbol "abc") :: [Char]
-- = '['a', 'b', 'c']
data ToCharList :: Text -> Exp [Char]
type instance Eval (ToCharList ('Text sym)) = S.ToCharList @@ sym


-- | Split text to characters and give them as Text list.
--
-- > :kind! Eval (ToList =<< FromSymbol "abc")
-- Eval (ToList =<< FromSymbol "abc") :: [Text]
-- = '[ 'Text "a", 'Text "b", 'Text "c"]
data ToList :: Text -> Exp [Text]
type instance Eval (ToList txt) =
    Eval (F.FMap Singleton =<< ToCharList txt)


-- | Unpack
--
-- === __Example__
--
-- >>> :kind! Eval (Unpack =<< FromSymbol "word")
-- Eval (Unpack =<< FromSymbol "word") :: Symbol
-- = "word"
data Unpack :: Text -> Exp Symbol
type instance Eval (Unpack ('Text sym)) = sym



-- | Null
--
-- === __Example__
--
-- >>> :kind! Eval (Null ('Text "ab"))
-- Eval (Null ('Text "ab")) :: Bool
-- = 'False
--
-- >>> :kind! Eval (Null =<< Empty)
-- Eval (Null =<< Empty) :: Bool
-- = 'True
data Null :: Text -> Exp Bool
type instance Eval (Null txt) = Eval
    (If (Eval (IsNothing =<< NullHelper txt))
        (Pure 'True)
        (Pure 'False)
    )

-- > :kind! Eval (NullHelper =<< (Singleton "koe"))
-- Eval (NullHelper =<< (Singleton "koe")) :: Maybe
--                                                      (Char, Text)
-- = 'Just '('k', 'Text "oe")
data NullHelper :: Text -> Exp (Maybe (Char, Text))
type instance Eval (NullHelper ('Text symbol)) =
    Eval (F.FMap (Second FromSymbol) =<< S.UnconsSymbol symbol)


-- | Length
--
-- === __Example__
--
-- >>> :kind! Eval (Length =<< FromSymbol "ab")
-- Eval (Length =<< FromSymbol "ab") :: TL.Natural
-- = 2
data Length :: Text -> Exp Nat
type instance Eval (Length txt) = Eval (F.Length =<< ToCharList txt)


-- | Add a Char to the beginning of a type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Cons 'h' ('Text "aamu"))
-- Eval (Cons 'h' ('Text "aamu")) :: Text
-- = 'Text "haamu"
data Cons :: Char -> Text -> Exp Text
type instance Eval (Cons c ('Text sym)) =
    'Text (TL.AppendSymbol (Eval (S.CharToSymbol c)) sym)


-- | Add a Symbol to the beginning of a type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (ConsSymbol "h" ('Text "aamu"))
-- Eval (ConsSymbol "h" ('Text "aamu")) :: Text
-- = 'Text "haamu"
data ConsSymbol :: Symbol -> Text -> Exp Text
type instance Eval (ConsSymbol s ('Text sym)) = 'Text (TL.AppendSymbol s sym)


-- | Add a Char to the end of a type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Snoc ('Text "aam") 'u')
-- Eval (Snoc ('Text "aam") 'u') :: Text
-- = 'Text "aamu"
data Snoc :: Text -> Char -> Exp Text
type instance Eval (Snoc ('Text sym) c) =
    'Text (TL.AppendSymbol sym (Eval (S.CharToSymbol c)))

-- | Add a Symbol to the end of a type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (SnocSymbol ('Text "aam") "u")
-- Eval (SnocSymbol ('Text "aam") "u") :: Text
-- = 'Text "aamu"
data SnocSymbol :: Text -> Symbol -> Exp Text
type instance Eval (SnocSymbol ('Text sym) s) = 'Text (TL.AppendSymbol sym s)


-- | Append two type-level texts.
--
-- === __Example__
--
-- >>> :kind! Eval (Append ('Text "aa") ('Text "mu"))
-- Eval (Append ('Text "aa") ('Text "mu")) :: Text
-- = 'Text "aamu"
data Append :: Text -> Text -> Exp Text
type instance Eval (Append ('Text s1) ('Text s2)) = 'Text (TL.AppendSymbol s1 s2)


-- | Get the first Char from type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Uncons ('Text "haamu"))
-- Eval (Uncons ('Text "haamu")) :: Maybe (Char, Text)
-- = 'Just '('h', 'Text "aamu")
--
-- >>> :kind! Eval (Uncons ('Text ""))
-- Eval (Uncons ('Text "")) :: Maybe (Char, Text)
-- = 'Nothing
data Uncons :: Text -> Exp (Maybe (Char, Text))
type instance Eval (Uncons txt) =
    Eval (PairMaybeToMaybePair '( Eval (Head txt), Eval (Tail txt) ))


-- | Get the last Char from type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Unsnoc ('Text "aamun"))
-- Eval (Unsnoc ('Text "aamun")) :: Maybe (Text, Char)
-- = 'Just '( 'Text "aamu", 'n')
--
-- >>> :kind! Eval (Unsnoc ('Text ""))
-- Eval (Unsnoc ('Text "")) :: Maybe (Text, Char)
-- = 'Nothing
data Unsnoc :: Text -> Exp (Maybe (Text, Char))
type instance Eval (Unsnoc txt) = 
    Eval (F.FMap Swap =<< F.FMap (Second Reverse) =<< Uncons =<< Reverse txt)


-- | Get the first Char of type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Head ('Text "aamu"))
-- Eval (Head ('Text "aamu")) :: Maybe Char
-- = 'Just 'a'
--
-- >>> :kind! Eval (Head ('Text ""))
-- Eval (Head ('Text "")) :: Maybe Char
-- = 'Nothing
data Head :: Text -> Exp (Maybe Char)
type instance Eval (Head ('Text sym)) =
    Eval (F.FMap Fst =<< S.UnconsSymbol sym)


-- | Get the last Char of type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Last ('Text "aamu"))
-- Eval (Last ('Text "aamu")) :: Maybe Char
-- = 'Just 'u'
--
-- >>> :kind! Eval (Last ('Text ""))
-- Eval (Last ('Text "")) :: Maybe Char
-- = 'Nothing
data Last :: Text -> Exp (Maybe Char)
type instance Eval (Last txt) = Eval (F.FMap Snd =<< Unsnoc txt)


-- | Get the tail of a type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Tail ('Text "haamu"))
-- Eval (Tail ('Text "haamu")) :: Maybe Text
-- = 'Just ('Text "aamu")
--
-- >>> :kind! Eval (Tail ('Text ""))
-- Eval (Tail ('Text "")) :: Maybe Text
-- = 'Nothing
data Tail :: Text -> Exp (Maybe Text)
type instance Eval (Tail ('Text sym)) =
    Eval (F.FMap FromSymbol =<< F.FMap Snd =<< S.UnconsSymbol sym)


-- | Take all except the last Char from type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Init ('Text "aamun"))
-- Eval (Init ('Text "aamun")) :: Maybe Text
-- = 'Just ('Text "aamu")
--
-- >>> :kind! Eval (Init ('Text ""))
-- Eval (Init ('Text "")) :: Maybe Text
-- = 'Nothing
data Init :: Text -> Exp (Maybe Text)
type instance Eval (Init ('Text sym)) = Eval
    (F.FMap FromSymbol =<< F.FMap S.ConcatChars =<< F.Init =<< S.ToCharList sym)


-- | Compare the length of type-level text to given Nat and give
-- the Ordering.
--
-- === __Example__
--
-- >>> :kind! Eval (CompareLength ('Text "aamu") 3)
-- Eval (CompareLength ('Text "aamu") 3) :: Ordering
-- = 'GT
data CompareLength :: Text -> Nat -> Exp Ordering
type instance Eval (CompareLength txt n) = TL.CmpNat (Length @@ txt) n



-- | FMapSymbol for type-level text.
--
-- === __Example__
--
-- >>> :{
-- data IsIsymb :: Symbol -> Exp Bool
-- type instance Eval (IsIsymb s) = Eval ("i" Sym.== s)
-- data Isymb2e :: Symbol -> Exp Symbol
-- type instance Eval (Isymb2e s) = Eval
--     (If (IsIsymb @@ s)
--         (Pure "e")
--         (Pure s)
--     )
-- :}
--
-- >>> :kind! Eval (FMapSymbol Isymb2e ('Text "imu"))
-- Eval (FMapSymbol Isymb2e ('Text "imu")) :: Text
-- = 'Text "emu"
data FMapSymbol :: (Symbol -> Exp Symbol) -> Text -> Exp Text
type instance Eval (FMapSymbol f txt) =
    Eval (FromSymbol =<< S.Concat =<< F.FMap f =<< F.FMap Unpack =<< ToList txt)


-- | FMap for type-level text.
--
-- === __Example__
--
-- >>> :{
-- data DigitsToX :: Char -> Exp Char
-- type instance Eval (DigitsToX c) = Eval
--     (If (IsDigit @@ c)
--         (Pure 'X')
--         (Pure c)
--     )
-- :}
--
-- >>> :kind! Eval (FMap DigitsToX ('Text "Some4text5oh9."))
-- Eval (FMap DigitsToX ('Text "Some4text5oh9.")) :: Text
-- = 'Text "SomeXtextXohX."
data FMap :: (Char -> Exp Char) -> Text -> Exp Text
type instance Eval (FMap f ('Text sym)) =
    Eval (FromSymbol =<< S.ConcatChars =<< F.FMap f =<< S.ToCharList sym)


data FMapT :: (Text -> Exp Text) -> Text -> Exp Text
type instance Eval (FMapT f txt) = Eval
    (FromList =<< F.FMap f =<< ToList txt)
-- is this too much


-- | Intercalate for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Intercalate ('Text " & ") ('[ 'Text "aamu", 'Text "valo"]))
-- Eval (Intercalate ('Text " & ") ('[ 'Text "aamu", 'Text "valo"])) :: Text
-- = 'Text "aamu & valo"
data Intercalate :: Text -> [Text] -> Exp Text
type instance Eval (Intercalate txt txts) = 
    Eval (FromList =<< F.Intercalate '[txt] =<< F.FMap ToList txts)


-- | Intersperse Char for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Intersperse '.' ('Text "aamu"))
-- Eval (Intersperse '.' ('Text "aamu")) :: Text
-- = 'Text "a.a.m.u"
data Intersperse :: Char  -> Text -> Exp Text
type instance Eval (Intersperse c ('Text sym)) = Eval
    (FromSymbolList =<< F.Intersperse (S.CharToSymbol @@c) =<< F.FMap S.CharToSymbol =<< S.ToCharList sym)

-- | Intersperse Symbol for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (IntersperseSymbol "." ('Text "aamu"))
-- Eval (IntersperseSymbol "." ('Text "aamu")) :: Text
-- = 'Text "a.a.m.u"
data IntersperseSymbol :: Symbol -> Text -> Exp Text
type instance Eval (IntersperseSymbol s ('Text sym)) = Eval
    (FromSymbolList =<< F.Intersperse s =<< F.FMap S.CharToSymbol =<< S.ToCharList sym)


-- | Reverse for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Reverse ('Text "aamu"))
-- Eval (Reverse ('Text "aamu")) :: Text
-- = 'Text "umaa"
--
-- >>> :kind! Eval (Reverse =<< Reverse ('Text "aamu"))
-- Eval (Reverse =<< Reverse ('Text "aamu")) :: Text
-- = 'Text "aamu"
data Reverse :: Text -> Exp Text
type instance Eval (Reverse ('Text sym)) =
    Eval (FromSymbol =<< S.ConcatChars =<< F.Reverse =<< S.ToCharList sym)

    
-- | Replace for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Replace ('Text "tu") ('Text "la") ('Text "tuututtaa"))
-- Eval (Replace ('Text "tu") ('Text "la") ('Text "tuututtaa")) :: Text
-- = 'Text "laulattaa"
data Replace :: Text -> Text -> Text -> Exp Text
type instance Eval (Replace orig new txt) =
    Eval (Intercalate new =<< SplitOn orig txt)


-- | Concat for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Concat '[ 'Text "la", 'Text "kana"])
-- Eval (Concat '[ 'Text "la", 'Text "kana"]) :: Text
-- = 'Text "lakana"
data Concat :: [Text] -> Exp Text
type instance Eval (Concat lst) = Eval (FromSymbol =<< S.Concat =<< F.FMap Unpack lst)


-- | FConcatMapSymbol for type-level text. This takes Symbol to Text function.
--
-- === __Example__
--
-- >>> :{
-- data IsIsymb :: Symbol -> Exp Bool
-- type instance Eval (IsIsymb s) = Eval ("i" Sym.== s)
-- data Isymb2aa :: Symbol -> Exp Text
-- type instance Eval (Isymb2aa s) = Eval
--     (If (IsIsymb @@ s)
--         (Pure ('Text "aa"))
--         (Pure ('Text s))
--     )
-- :}
--
-- >>> :kind! Eval (ConcatMapSymbol Isymb2aa ('Text "imu ih"))
-- Eval (ConcatMapSymbol Isymb2aa ('Text "imu ih")) :: Text
-- = 'Text "aamu aah"
data ConcatMapSymbol :: (Symbol -> Exp Text) -> Text -> Exp Text
type instance Eval (ConcatMapSymbol f ('Text sym)) =
    Eval (Concat =<< F.FMap f =<< F.FMap S.CharToSymbol =<< S.ToCharList sym)


-- | ConcatMap for type-level text.  This takes Char to Text function.
--
-- === __Example__
--
-- >>> :{
-- data DigitsToHoo :: Char -> Exp Text
-- type instance Eval (DigitsToHoo c) = Eval
--     (If (IsDigit @@ c)
--         (Pure ( 'Text "hoo"))
--         (Singleton c)
--     )
-- :}
--
-- >>> :kind! Eval (ConcatMap DigitsToHoo ('Text "haa2hui2"))
-- Eval (ConcatMap DigitsToHoo ('Text "haa2hui2")) :: Text
-- = 'Text "haahoohuihoo"
data ConcatMap :: (Char -> Exp Text) -> Text -> Exp Text
type instance Eval (ConcatMap f ('Text sym)) =
    Eval (Concat =<< F.FMap f =<< S.ToCharList sym)

-- | ConcatMap for type-level text.  This takes Char to Symbol function.
data ConcatMapCS :: (Char -> Exp Symbol) -> Text -> Exp Text
type instance Eval (ConcatMapCS f ('Text sym)) =
    Eval (FromSymbol =<< S.Concat =<< F.FMap f =<< S.ToCharList sym)


-- | Any for type-level text.  This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (Any IsDigit ('Text "aamu1"))
-- Eval (Any IsDigit ('Text "aamu1")) :: Bool
-- = 'True
--
-- >>> :kind! Eval (Any IsDigit ('Text "aamu"))
-- Eval (Any IsDigit ('Text "aamu")) :: Bool
-- = 'False
data Any :: (Char -> Exp Bool) -> Text -> Exp Bool
type instance Eval (Any f ('Text sym)) = Eval (F.Any f =<< S.ToCharList sym)


-- | AnySymbol for type-level text.  This takes Symbol to Bool function.
-- Note that the given function needs to be compatible... (i.e. operating
-- with symbols of length 1.
--
-- === __Example__
--
-- >>> :kind! Eval (AnySymbol Sym.IsDigit ('Text "aamu1"))
-- Eval (AnySymbol Sym.IsDigit ('Text "aamu1")) :: Bool
-- = 'True
--
-- >>> :kind! Eval (AnySymbol Sym.IsDigit ('Text "aamu"))
-- Eval (AnySymbol Sym.IsDigit ('Text "aamu")) :: Bool
-- = 'False
data AnySymbol :: (Symbol -> Exp Bool) -> Text -> Exp Bool
type instance Eval (AnySymbol f ('Text sym)) =
    Eval (F.Any f =<< F.FMap S.CharToSymbol =<< S.ToCharList sym)


-- | All for type-level text. This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (All IsDigit ('Text "aamu1"))
-- Eval (All IsDigit ('Text "aamu1")) :: Bool
-- = 'False
--
-- >>> :kind! Eval (All IsDigit ('Text "321"))
-- Eval (All IsDigit ('Text "321")) :: Bool
-- = 'True
data All :: (Char -> Exp Bool) -> Text -> Exp Bool
type instance Eval (All f ('Text sym)) = Eval (F.All f =<< S.ToCharList sym)


-- | AllSymbol for type-level text. This takes Symbol to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (AllSymbol Sym.IsDigit ('Text "aamu1"))
-- Eval (AllSymbol Sym.IsDigit ('Text "aamu1")) :: Bool
-- = 'False
--
-- >>> :kind! Eval (AllSymbol Sym.IsDigit ('Text "321"))
-- Eval (AllSymbol Sym.IsDigit ('Text "321")) :: Bool
-- = 'True
data AllSymbol :: (Symbol -> Exp Bool) -> Text -> Exp Bool
type instance Eval (AllSymbol f ('Text sym)) =
    Eval (F.All f =<< F.FMap S.CharToSymbol =<< S.ToCharList sym)


-- | Take for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Take 4 ('Text "aamun"))
-- Eval (Take 4 ('Text "aamun")) :: Text
-- = 'Text "aamu"
data Take :: Nat -> Text -> Exp Text
type instance Eval (Take n txt) = Eval (FromList =<< F.Take n =<< ToList txt)

-- | TakeEnd for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (TakeEnd 4 ('Text "haamu"))
-- Eval (TakeEnd 4 ('Text "haamu")) :: Text
-- = 'Text "aamu"
data TakeEnd :: Nat -> Text -> Exp Text
type instance Eval (TakeEnd n txt) =
    Eval (FromList =<< F.Reverse =<< F.Take n =<< F.Reverse =<< ToList txt)


-- | Drop for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Drop 2 ('Text "aamuna"))
-- Eval (Drop 2 ('Text "aamuna")) :: Text
-- = 'Text "muna"
data Drop :: Nat -> Text -> Exp Text
type instance Eval (Drop n txt) = Eval (FromList =<< F.Drop n =<< ToList txt)


-- | DropEnd for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (DropEnd 2 ('Text "aamuna"))
-- Eval (DropEnd 2 ('Text "aamuna")) :: Text
-- = 'Text "aamu"
data DropEnd :: Nat -> Text -> Exp Text
type instance Eval (DropEnd n txt) =
    Eval (FromList =<< F.Reverse =<< F.Drop n =<< F.Reverse =<< ToList txt)

-- | TakeWhile for type-level text. This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (TakeWhile (Not <=< IsDigit) ('Text "aamu12"))
-- Eval (TakeWhile (Not <=< IsDigit) ('Text "aamu12")) :: Text
-- = 'Text "aamu"
data TakeWhile :: (Char -> Exp Bool) -> Text -> Exp Text
type instance Eval (TakeWhile f ('Text sym)) =
    Eval (FromSymbol =<< S.ConcatChars =<< F.TakeWhile f =<< S.ToCharList sym)


-- | TakeWhileSymbol for type-level text. This takes Symbol to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (TakeWhileSymbol (Not <=< Sym.IsDigit) ('Text "aamu12"))
-- Eval (TakeWhileSymbol (Not <=< Sym.IsDigit) ('Text "aamu12")) :: Text
-- = 'Text "aamu"
data TakeWhileSymbol :: (Symbol -> Exp Bool) -> Text -> Exp Text
type instance Eval (TakeWhileSymbol f txt) = Eval
    (FromSymbolList =<< F.TakeWhile f =<< ToSymbolList txt)


-- | TakeWhileEnd for type-level text. This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (TakeWhileEnd (Not <=< IsDigit) ('Text "12aamu"))
-- Eval (TakeWhileEnd (Not <=< IsDigit) ('Text "12aamu")) :: Text
-- = 'Text "aamu"
data TakeWhileEnd :: (Char -> Exp Bool) -> Text -> Exp Text
type instance Eval (TakeWhileEnd f ('Text sym)) = Eval
    (FromSymbol
    =<< S.ConcatChars =<< F.Reverse =<< F.TakeWhile f =<< F.Reverse
    =<< S.ToCharList sym)


-- | TakeWhileEndSymbol for type-level text. This takes Symbol to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (TakeWhileEndSymbol (Not <=< Sym.IsDigit) ('Text "12aamu"))
-- Eval (TakeWhileEndSymbol (Not <=< Sym.IsDigit) ('Text "12aamu")) :: Text
-- = 'Text "aamu"
data TakeWhileEndSymbol :: (Symbol -> Exp Bool) -> Text -> Exp Text
type instance Eval (TakeWhileEndSymbol f txt) = Eval
    (FromSymbolList =<< F.Reverse =<< F.TakeWhile f =<< F.Reverse =<< ToSymbolList txt)


-- | DropWhile for type-level text. This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (DropWhile IsDigit ('Text "12aamu"))
-- Eval (DropWhile IsDigit ('Text "12aamu")) :: Text
-- = 'Text "aamu"
data DropWhile :: (Char -> Exp Bool) -> Text -> Exp Text
type instance Eval (DropWhile f ('Text sym)) = Eval
    (FromSymbol =<< S.ConcatChars =<< F.DropWhile f =<< S.ToCharList sym)


-- | DropWhileSymbol for type-level text. This takes Symbol to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (DropWhileSymbol Sym.IsDigit ('Text "12aamu"))
-- Eval (DropWhileSymbol Sym.IsDigit ('Text "12aamu")) :: Text
-- = 'Text "aamu"
data DropWhileSymbol :: (Symbol -> Exp Bool) -> Text -> Exp Text
type instance Eval (DropWhileSymbol f txt) = Eval
    (FromSymbolList =<< F.DropWhile f =<< ToSymbolList txt)


-- | DropWhileEnd for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (DropWhileEnd IsDigit ('Text "aamu12"))
-- Eval (DropWhileEnd IsDigit ('Text "aamu12")) :: Text
-- = 'Text "aamu"
data DropWhileEnd :: (Char -> Exp Bool) -> Text -> Exp Text
type instance Eval (DropWhileEnd f ('Text sym)) = Eval
    (FromSymbol =<< S.ConcatChars  =<< F.Reverse =<< F.DropWhile f
    =<< F.Reverse =<< S.ToCharList sym)


-- | DropWhileEndSymbol for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (DropWhileEndSymbol Sym.IsDigit ('Text "aamu12"))
-- Eval (DropWhileEndSymbol Sym.IsDigit ('Text "aamu12")) :: Text
-- = 'Text "aamu"
data DropWhileEndSymbol :: (Symbol -> Exp Bool) -> Text -> Exp Text
type instance Eval (DropWhileEndSymbol f txt) = Eval
    (FromSymbolList =<< F.Reverse =<< F.DropWhile f =<< F.Reverse =<< ToSymbolList txt)


-- | DropAround for type-level text. This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (DropAround IsDigit ('Text "34aamu12"))
-- Eval (DropAround IsDigit ('Text "34aamu12")) :: Text
-- = 'Text "aamu"
data DropAround :: (Char -> Exp Bool) -> Text -> Exp Text
type instance Eval (DropAround f txt) = Eval (DropWhile f =<< DropWhileEnd f txt)


-- | DropAroundSymbol for type-level text. This takes Symbol to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (DropAroundSymbol Sym.IsDigit ('Text "34aamu12"))
-- Eval (DropAroundSymbol Sym.IsDigit ('Text "34aamu12")) :: Text
-- = 'Text "aamu"
data DropAroundSymbol :: (Symbol -> Exp Bool) -> Text -> Exp Text
type instance Eval (DropAroundSymbol f txt) =
    Eval (DropWhileSymbol f =<< DropWhileEndSymbol f txt)


-- | Strip the space, newline and tab -symbols from the beginning and and
-- of type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Strip ('Text "  aamu \n"))
-- Eval (Strip ('Text "  aamu \n")) :: Text
-- = 'Text "aamu"
data Strip :: Text -> Exp Text
type instance Eval (Strip txt) = Eval (DropAroundSymbol Sym.IsSpaceDelim txt)


-- | SplitOn for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (SplitOn ('Text "ab") ('Text "cdabfgabh"))
-- Eval (SplitOn ('Text "ab") ('Text "cdabfgabh")) :: [Text]
-- = '[ 'Text "cd", 'Text "fg", 'Text "h"]
data SplitOn :: Text -> Text -> Exp [Text]
type instance Eval (SplitOn ('Text sep) ('Text txt)) = Eval
    -- (F.FMap FromSymbolList
    (F.FMap FromSymbol =<< F.FMap S.ConcatChars
    =<< SOLoop (Eval (S.ToCharList sep)) '( '[], Eval (S.ToCharList txt)))


-- | Helper for SplitOn
--
-- >>> :kind! Eval (SOTake '[ 'a', 'b'] '[ 'c', 'd', 'a', 'b', 'f', 'g'] '[])
-- Eval (SOTake '[ 'a', 'b'] '[ 'c', 'd', 'a', 'b', 'f', 'g'] '[]) :: ([Char],
--                                                                     [Char])
-- = '( '['c', 'd'], '['f', 'g'])
data SOTake :: [Char] -> [Char] -> [Char] -> Exp ([Char], [Char])
type instance Eval (SOTake sep '[] accum) = '(accum, '[])
type instance Eval (SOTake sep (t ': txt) accum) = Eval
    (If (Eval (F.IsPrefixOf sep (t ': txt)))
        (Pure '(accum, Eval (F.Drop (Eval (F.Length sep)) (t ': txt))))
        (SOTake sep txt (Eval (accum ++ '[t])))
    )

-- | Helper for SplitOn
data SOLoop :: [Char] -> ([[Char]],[Char]) -> Exp [[Char]]
type instance Eval (SOLoop sep '(acc, '[])) = acc
type instance Eval (SOLoop sep '(acc, (t ': txt))) =
    Eval (SOLoop sep =<< First (F.Snoc acc) =<< SOTake sep (t ': txt) '[])


-- | Split for type-level text. This takes Char to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (Split C.IsSpace (Eval (FromSymbol "cd bf abh")))
-- Eval (Split C.IsSpace (Eval (FromSymbol "cd bf abh"))) :: [Text]
-- = '[ 'Text "cd", 'Text "bf", 'Text "abh"]
data Split :: (Char -> Exp Bool) -> Text -> Exp [Text]
type instance Eval (Split p ('Text sym)) = Eval
    (F.FMap FromSymbol =<< F.FMap S.ConcatChars
    =<< SplitLoop p '( '[], Eval (S.ToCharList sym)))


-- | SplitSymbol for type-level text. This takes Symbol to Bool function.
--
-- === __Example__
--
-- >>> :kind! Eval (SplitSymbol Sym.IsSpace (Eval (FromSymbol "cd bf abh")))
-- Eval (SplitSymbol Sym.IsSpace (Eval (FromSymbol "cd bf abh"))) :: [Text]
-- = '[ 'Text "cd", 'Text "bf", 'Text "abh"]
data SplitSymbol :: (Symbol -> Exp Bool) -> Text -> Exp [Text]
type instance Eval (SplitSymbol p txt) =
    Eval (F.FMap FromSymbolList =<< SplitLoop p '( '[], Eval (ToSymbolList txt)))

-- | Helper for Split
data SplitTake :: (a -> Exp Bool) -> [a] -> [a] -> Exp ([a], [a])
type instance Eval (SplitTake p '[] accum) = '(accum, '[])
type instance Eval (SplitTake p (t ': txt) accum) = Eval
    (If (Eval (p t))
        (Pure '(accum, txt))
        (SplitTake p txt (Eval (accum ++ '[t])))
    )

-- | Helper for Split
data SplitLoop :: (a -> Exp Bool) -> ([[a]],[a]) -> Exp [[a]]
type instance Eval (SplitLoop p '(acc, '[])) = acc
type instance Eval (SplitLoop p '(acc, (t ': txt))) = 
    Eval (SplitLoop p =<< First (F.Snoc acc) =<< SplitTake p (t ': txt) '[])


-- | Lines for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Lines =<< FromSymbol "ok\nhmm\nab")
-- Eval (Lines =<< FromSymbol "ok\nhmm\nab") :: [Text]
-- = '[ 'Text "ok", 'Text "hmm", 'Text "ab"]
data Lines :: Text -> Exp [Text]
type instance Eval (Lines txt) = Eval (SplitSymbol Sym.IsNewLine txt)


-- | Words for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (Words =<< FromSymbol "ok hmm\nab")
-- Eval (Words =<< FromSymbol "ok hmm\nab") :: [Text]
-- = '[ 'Text "ok", 'Text "hmm", 'Text "ab"]
data Words :: Text -> Exp [Text]
type instance Eval (Words txt) = Eval (SplitSymbol Sym.IsSpaceDelim txt)

-- | Unlines for type-level text. This adds a newline to each Text and then
-- concats them.
--
-- === __Example__
--
-- >>> :kind! Eval (Unlines '[ 'Text "ok", 'Text "hmm", 'Text "ab"])
-- Eval (Unlines '[ 'Text "ok", 'Text "hmm", 'Text "ab"]) :: Text
-- = 'Text "ok\nhmm\nab\n"
data Unlines :: [Text] -> Exp Text
type instance Eval (Unlines txts) = 
    Eval (Concat =<< F.FMap (Flip Append (FromSymbol @@ "\n")) txts)

-- | Unwords for type-level text. This uses 'Intercalate' to add space-symbol
-- between the given texts.
--
-- === __Example__
--
-- >>> :kind! Eval (Unwords '[ 'Text "ok", 'Text "hmm", 'Text "ab"])
-- Eval (Unwords '[ 'Text "ok", 'Text "hmm", 'Text "ab"]) :: Text
-- = 'Text "ok hmm ab"
data Unwords :: [Text] -> Exp Text
type instance Eval (Unwords txts) = Eval (Intercalate ('Text " ") txts)


-- | IsPrefixOf for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (IsPrefixOf ('Text "aa") ('Text "aamiainen"))
-- Eval (IsPrefixOf ('Text "aa") ('Text "aamiainen")) :: Bool
-- = 'True
data IsPrefixOf :: Text -> Text -> Exp Bool
type instance Eval (IsPrefixOf l1 l2) =
    Eval (F.IsPrefixOf (Eval (ToList l1)) (Eval (ToList l2)))


-- | IsSuffixOf for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (IsSuffixOf ('Text "nen") ('Text "aamiainen"))
-- Eval (IsSuffixOf ('Text "nen") ('Text "aamiainen")) :: Bool
-- = 'True
data IsSuffixOf :: Text -> Text -> Exp Bool
type instance Eval (IsSuffixOf l1 l2) =
    Eval (F.IsSuffixOf (Eval (ToList l1)) (Eval (ToList l2)))


-- | IsInfixOf for type-level text.
--
-- === __Example__
--
-- >>> :kind! Eval (IsInfixOf ('Text "mia") ('Text "aamiainen"))
-- Eval (IsInfixOf ('Text "mia") ('Text "aamiainen")) :: Bool
-- = 'True
data IsInfixOf :: Text -> Text -> Exp Bool
type instance Eval (IsInfixOf l1 l2) =
    Eval (F.IsInfixOf (Eval (ToList l1)) (Eval (ToList l2)))


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

#else

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

#endif

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