{-# LANGUAGE GADTs                #-}
{-# LANGUAGE KindSignatures       #-}
{-# LANGUAGE NoStarIsType         #-}
{-# LANGUAGE TypeFamilies         #-}
{-# LANGUAGE TypeInType           #-}
{-# LANGUAGE TypeOperators        #-}
{-# LANGUAGE UndecidableInstances #-}

module GHC.TypeLits.Printf.Internal.Parser where

import           Data.Kind
import           Data.Type.Bool
import           Data.Type.Equality
import           GHC.TypeLits

-- | A type synonym for a single-character symbol.  Ideally this would just
-- be 'Char', but we don't have chars at the type level.  So, if you see
-- 'SChar' in a type signature, it means that it's expected to be
-- a symbol/string with only one single character.
type SChar = Symbol

type Parser a = a -> Type

type family RunParser (p :: Parser a) (str :: [SChar]) :: Maybe (a, [SChar])

data Pure :: a -> Parser a
type instance RunParser (Pure x) str = 'Just '(x, str)

data Sym :: SChar -> Parser SChar
type instance RunParser (Sym c) (d ': cs) = If (c == d) ('Just '(c, cs)) 'Nothing
type instance RunParser (Sym c) '[]       = 'Nothing

data NotSym :: SChar -> Parser SChar
type instance RunParser (NotSym c) (d ': cs) = If (c == d) 'Nothing ('Just '(c, cs))
type instance RunParser (NotSym c) '[]       = 'Nothing

data AnySym :: Parser SChar
type instance RunParser AnySym (c ': cs) = 'Just '(c, cs)
type instance RunParser AnySym '[]       = 'Nothing

data Alpha :: Parser SChar
type instance RunParser Alpha (c ': cs) = If (IsAlpha c) ('Just '(c, cs)) 'Nothing
type instance RunParser Alpha '[]       = 'Nothing

data (<$) :: b -> Parser a -> Parser b
type family RepHelp (x :: b) (r :: Maybe (a, [SChar])) :: Maybe (b, [SChar]) where
    RepHelp x 'Nothing        = 'Nothing
    RepHelp x ('Just '(y, s)) = 'Just '(x, s)
type instance RunParser (x <$ p) str = RepHelp x (RunParser p str)

data (<|>) :: Parser a -> Parser a -> Parser a
type family ChoiceMaybe (x :: Maybe a) (y :: Maybe a) :: Maybe a where
    ChoiceMaybe ('Just x) y = 'Just x
    ChoiceMaybe 'Nothing  y = y
type instance RunParser (x <|> y) str = ChoiceMaybe (RunParser x str) (RunParser y str)

type Optional p = ('Just <$> p) <|> Pure 'Nothing

data (*>) :: Parser a -> Parser b -> Parser b
type family SeqHelp (p :: Parser b) (r :: Maybe (a, [SChar])) :: Maybe (b, [SChar]) where
    SeqHelp p 'Nothing          = 'Nothing
    SeqHelp p ('Just '(x, str)) = RunParser p str
type instance RunParser (x *> y) str = SeqHelp y (RunParser x str)

-- | Parse a single digit
data Digit :: Parser Nat
type family DigitHelp (d :: Maybe Nat) (cs :: [SChar]) :: Maybe (Nat, [SChar]) where
    DigitHelp 'Nothing  cs = 'Nothing
    DigitHelp ('Just d) cs = 'Just '(d, cs)
type instance RunParser Digit '[]       = 'Nothing
type instance RunParser Digit (c ': cs) = DigitHelp (CharDigit c) cs

data (<$>) :: (a -> b) -> Parser a -> Parser b
type family MapConHelp (f :: a -> b) (r :: Maybe (a, [SChar])) :: Maybe (b, [SChar]) where
    MapConHelp f 'Nothing = 'Nothing
    MapConHelp f ('Just '(x, str)) = 'Just '(f x, str)
type instance RunParser (f <$> p) str = MapConHelp f (RunParser p str)

data (<*>) :: Parser (a -> b) -> Parser a -> Parser b
type family ApHelp (r :: Maybe (a -> b, [SChar])) (q :: Parser a) :: Maybe (b, [SChar]) where
    ApHelp 'Nothing q          = 'Nothing
    ApHelp ('Just '(f, str)) q = RunParser (f <$> q) str
type instance RunParser (p <*> q) str = ApHelp (RunParser p str) q

data Many :: Parser a -> Parser [a]
type instance RunParser (Many p) str = RunParser (Some p <|> Pure '[]) str
data Some :: Parser a -> Parser [a]
type instance RunParser (Some p) str = RunParser ('(:) <$> p <*> Many p) str

-- | Parse a number
data Number :: Parser Nat
type family NumberHelp (xs :: Maybe ([Nat], [SChar])) :: Maybe (Nat, [SChar]) where
    NumberHelp 'Nothing           = 'Nothing
    NumberHelp ('Just '(ns, str)) = 'Just '(FromDigits ns 0, str)
type instance RunParser Number str = NumberHelp (RunParser (Some Digit) str)

data Cat :: Parser [SChar] -> Parser Symbol
type family CatHelp (xs :: Maybe ([SChar], [SChar])) :: Maybe (Symbol, [SChar]) where
    CatHelp 'Nothing           = 'Nothing
    CatHelp ('Just '(cs, str)) = 'Just '(CatChars cs, str)
type instance RunParser (Cat p) str = CatHelp (RunParser p str)

type family EvalHelp (r :: Maybe (a, [SChar])) :: Maybe a where
    EvalHelp 'Nothing          = 'Nothing
    EvalHelp ('Just '(x, str)) = 'Just x
type EvalParser (p :: Parser a) str = EvalHelp (RunParser p str) :: Maybe a

type family EvalHelp_ (r :: Maybe (a, [SChar])) :: a where
    EvalHelp_ ('Just '(x, str)) = x
    EvalHelp_ 'Nothing          = TypeError ('Text "Parse failed")
type EvalParser_ (p :: Parser a) str = EvalHelp_ (RunParser p str) :: a

type family CharDigit (c :: SChar) :: Maybe Nat where
    CharDigit "0" = 'Just 0
    CharDigit "1" = 'Just 1
    CharDigit "2" = 'Just 2
    CharDigit "3" = 'Just 3
    CharDigit "4" = 'Just 4
    CharDigit "5" = 'Just 5
    CharDigit "6" = 'Just 6
    CharDigit "7" = 'Just 7
    CharDigit "8" = 'Just 8
    CharDigit "9" = 'Just 9
    CharDigit c   = 'Nothing

type family FromDigits (xs :: [Nat]) (n :: Nat) :: Nat where
    FromDigits '[]       n = n
    FromDigits (a ': bs) n = FromDigits bs (n * 10 + a)

type family CatChars (cs :: [SChar]) :: Symbol where
    CatChars '[]       = ""
    CatChars (c ': cs) = AppendSymbol c (CatChars cs)

type family IsAlpha (c :: SChar) :: Bool where
    IsAlpha "a" = 'True
    IsAlpha "b" = 'True
    IsAlpha "c" = 'True
    IsAlpha "d" = 'True
    IsAlpha "e" = 'True
    IsAlpha "f" = 'True
    IsAlpha "g" = 'True
    IsAlpha "h" = 'True
    IsAlpha "i" = 'True
    IsAlpha "j" = 'True
    IsAlpha "k" = 'True
    IsAlpha "l" = 'True
    IsAlpha "m" = 'True
    IsAlpha "n" = 'True
    IsAlpha "o" = 'True
    IsAlpha "p" = 'True
    IsAlpha "q" = 'True
    IsAlpha "r" = 'True
    IsAlpha "s" = 'True
    IsAlpha "t" = 'True
    IsAlpha "u" = 'True
    IsAlpha "v" = 'True
    IsAlpha "w" = 'True
    IsAlpha "x" = 'True
    IsAlpha "y" = 'True
    IsAlpha "z" = 'True
    IsAlpha "A" = 'True
    IsAlpha "B" = 'True
    IsAlpha "C" = 'True
    IsAlpha "D" = 'True
    IsAlpha "E" = 'True
    IsAlpha "F" = 'True
    IsAlpha "G" = 'True
    IsAlpha "H" = 'True
    IsAlpha "I" = 'True
    IsAlpha "J" = 'True
    IsAlpha "K" = 'True
    IsAlpha "L" = 'True
    IsAlpha "M" = 'True
    IsAlpha "N" = 'True
    IsAlpha "O" = 'True
    IsAlpha "P" = 'True
    IsAlpha "Q" = 'True
    IsAlpha "R" = 'True
    IsAlpha "S" = 'True
    IsAlpha "T" = 'True
    IsAlpha "U" = 'True
    IsAlpha "V" = 'True
    IsAlpha "W" = 'True
    IsAlpha "X" = 'True
    IsAlpha "Y" = 'True
    IsAlpha "Z" = 'True
    IsAlpha a   = 'False