{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE QuasiQuotes #-}

-- | Exposes naming cases.
module Data.String.Cases where

import Cases.Megaparsec
import Data.Char.Cases
import qualified Data.String as String
import Data.Text.Prettyprint.Doc (Pretty(..))
import Language.Haskell.TH
import Language.Haskell.TH.Quote (QuasiQuoter(..))
import qualified Text.Megaparsec as P

-- $setup
-- >>> :set -XQuasiQuotes

-- | Non empty PascalCase names "[A-Z][a-zA-Z0-9]*"
data Pascal = Pascal UpperChar [AlphaNumChar]
  deriving (Show, Eq)

instance Pretty Pascal where
  pretty = String.fromString . unPascal

unPascal :: Pascal -> String
unPascal (Pascal x xs) =
  upperToChar x : map alphaNumToChar xs

parsePascal :: CodeParsing m => m Pascal
parsePascal =
  Pascal <$> upperChar <*> P.many alphaNumChar

-- |
-- Simular to 'nonEmptyQ',
-- but naming outsides of 'Pascal' will be rejected.
--
-- >>> [pascalQ|Pascal|]
-- Pascal P [AlphaNumAlpha (AlphaLower A_),AlphaNumAlpha (AlphaLower S_),AlphaNumAlpha (AlphaLower C_),AlphaNumAlpha (AlphaLower A_),AlphaNumAlpha (AlphaLower L_)]
pascalQ :: QuasiQuoter
pascalQ = QuasiQuoter
  { quoteExp  = expQ
  , quotePat  = error "not supported"
  , quoteType = error "not supported"
  , quoteDec  = error "not supported"
  }
  where
    expQ :: String -> Q Exp
    expQ [] = fail "pascalQ required a non empty string, but the empty string is specified."
    expQ (x : xs) = do
      z <- (quoteExp upperCharQ) [x]
      zs <- mapM (quoteExp alphaNumCharQ) $ map (: []) xs
      pure $ ConE (mkName "Pascal") `AppE` z `AppE` ListE zs


-- | Non empty names ".+"
data NonEmpty = NonEmpty Char String
  deriving (Show, Eq)

instance Pretty NonEmpty where
  pretty = String.fromString . unNonEmpty

unNonEmpty :: NonEmpty -> String
unNonEmpty (NonEmpty x xs) = x : xs

parseNonEmpty :: CodeParsing m => m NonEmpty
parseNonEmpty =
  NonEmpty <$> P.anySingle <*> P.many P.anySingle

fromString :: String -> Maybe NonEmpty
fromString "" = Nothing
fromString (x : xs) = Just $ NonEmpty x xs

-- |
-- Makes a non empty string from String on the compile time.
-- Also throws compile error if the empty string is passed.
--
-- >>> [nonEmptyQ|x|]
-- NonEmpty 'x' ""
--
-- >>> [nonEmptyQ|foo|]
-- NonEmpty 'f' "oo"
--
-- >>> [nonEmptyQ|Bar|]
-- NonEmpty 'B' "ar"
nonEmptyQ :: QuasiQuoter
nonEmptyQ = QuasiQuoter
  { quoteExp  = expQ
  , quotePat  = error "not supported"
  , quoteType = error "not supported"
  , quoteDec  = error "not supported"
  }
  where
    expQ :: String -> Q Exp
    expQ [] = fail "nonEmptyQ required a non empty string, but the empty string is specified."
    expQ (x : xs) =
      pure $ ConE (mkName "NonEmpty")
        `AppE` LitE (CharL x)
        `AppE` ListE (map (LitE . CharL) xs)


-- | Non empty camelCase names "[a-zA-Z][a-zA-Z0-9]*"
data Camel = Camel AlphaChar [AlphaNumChar]
  deriving (Eq)

-- To easy to debug.
--
-- To strictly check, remove this instance and use `deriving (Show)`.
instance Show Camel where
  show (Camel x xs) = '"' : alphaToChar x : map alphaNumToChar xs <> "\""

instance Pretty Camel where
  pretty = String.fromString . unCamel

unCamel :: Camel -> String
unCamel (Camel x xs) = alphaToChar x : map alphaNumToChar xs

parseCamel :: CodeParsing m => m Camel
parseCamel =
  Camel <$> alphaChar <*> P.many alphaNumChar

-- |
-- Simular to 'nonEmptyQ',
-- but naming outsides of 'Camel' will be rejected.
--
-- >>> [camelQ|camel|]
-- "camel"
--
-- >>> [camelQ|Pascal|]
-- "Pascal"
camelQ :: QuasiQuoter
camelQ = QuasiQuoter
  { quoteExp  = expQ
  , quotePat  = error "not supported"
  , quoteType = error "not supported"
  , quoteDec  = error "not supported"
  }
  where
    expQ :: String -> Q Exp
    expQ [] = fail "camelQ required a non empty string, but the empty string is specified."
    expQ (x : xs) = do
      z <- (quoteExp alphaCharQ) [x]
      zs <- mapM (quoteExp alphaNumCharQ) $ map (: []) xs
      pure $ ConE (mkName "Camel") `AppE` z `AppE` ListE zs


-- | Non empty sneak_case names "[a-zA-Z_][a-zA-Z0-9_]*"
data Sneak = Sneak SneakHeadChar [SneakChar]
  deriving (Show, Eq)

instance Pretty Sneak where
  pretty = String.fromString . unSneakCase

unSneakCase :: Sneak -> String
unSneakCase (Sneak x xs) =
  unSneakHeadChar x : map unSneakChar xs

parseSneakCase :: CodeParsing m => m Sneak
parseSneakCase =
  Sneak <$>
  sneakHeadChar <*>
  P.many sneakChar

-- |
-- Simular to 'nonEmptyQ',
-- but naming outsides of 'Sneak' will be rejected.
--
-- >>> [sneakQ|foo_bar|]
-- Sneak (SneakHeadAlpha (AlphaLower F_)) [SneakAlphaNum (AlphaNumAlpha (AlphaLower O_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower O_)),SneakUnderscore,SneakAlphaNum (AlphaNumAlpha (AlphaLower B_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower A_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower R_))]
--
-- >>> [sneakQ|__constructor|]
-- Sneak SneakHeadUnderscore [SneakUnderscore,SneakAlphaNum (AlphaNumAlpha (AlphaLower C_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower O_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower N_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower S_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower T_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower R_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower U_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower C_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower T_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower O_)),SneakAlphaNum (AlphaNumAlpha (AlphaLower R_))]
--
-- >>> [sneakQ|FOO_MEE_9|]
-- Sneak (SneakHeadAlpha (AlphaUpper F)) [SneakAlphaNum (AlphaNumAlpha (AlphaUpper O)),SneakAlphaNum (AlphaNumAlpha (AlphaUpper O)),SneakUnderscore,SneakAlphaNum (AlphaNumAlpha (AlphaUpper M)),SneakAlphaNum (AlphaNumAlpha (AlphaUpper E)),SneakAlphaNum (AlphaNumAlpha (AlphaUpper E)),SneakUnderscore,SneakAlphaNum (AlphaNumDigit D9)]
sneakQ :: QuasiQuoter
sneakQ = QuasiQuoter
  { quoteExp  = expQ
  , quotePat  = error "not supported"
  , quoteType = error "not supported"
  , quoteDec  = error "not supported"
  }
  where
    expQ :: String -> Q Exp
    expQ [] = fail "sneakQ required a non empty string, but the empty string is specified."
    expQ (x : xs) = do
      z <- (quoteExp sneakHeadCharQ) [x]
      zs <- mapM (quoteExp sneakCharQ) $ map (: []) xs
      pure $ ConE (mkName "Sneak") `AppE` z `AppE` ListE zs


-- | Non empty "veryflatten" names [a-z]+
data LowerString = LowerString LowerChar [LowerChar]
  deriving (Show, Eq)

instance Pretty LowerString where
  pretty (LowerString x xs) = String.fromString $ map lowerToChar (x : xs)

unLowerString :: LowerString -> String
unLowerString (LowerString x xs) = lowerToChar x : map lowerToChar xs

parseLowerString :: CodeParsing m => m LowerString
parseLowerString = LowerString <$> lowerChar <*> P.many lowerChar

-- |
-- Simular to 'nonEmptyQ',
-- but naming outsides of 'LowerString' will be rejected.
--
-- >>> [lowerStringQ|imavimmer|]
-- LowerString I_ [M_,A_,V_,I_,M_,M_,E_,R_]
lowerStringQ :: QuasiQuoter
lowerStringQ = QuasiQuoter
  { quoteExp  = expQ
  , quotePat  = error "not supported"
  , quoteType = error "not supported"
  , quoteDec  = error "not supported"
  }
  where
    expQ :: String -> Q Exp
    expQ [] = fail "lowerStringQ required a non empty string, but the empty string is specified."
    expQ (x : xs) = do
      z <- (quoteExp lowerCharQ) [x]
      zs <- mapM (quoteExp lowerCharQ) $ map (: []) xs
      pure $ ConE (mkName "LowerString") `AppE` z `AppE` ListE zs