-- |
-- Module      : Language.Haskell.TH.Name.CamelCase
-- Copyright   : 2013 Kei Hibino
-- License     : BSD3
--
-- Maintainer  : ex8k.hibino@gmail.com
-- Stability   : experimental
-- Portability : unknown
--
-- This module provides camelcased 'Name' for Template Haskell
module Language.Haskell.TH.Name.CamelCase (
  -- * Types to wrap 'Name'
  -- $nameTypes
  ConName (ConName, conName), toConName,
  VarName (VarName, varName), toVarName,

  -- * Functions to make camel-cased names
  -- $makeNames
  conCamelcaseName, varCamelcaseName,

  varNameWithPrefix,

  -- * Functions to generate haskell template from names
  -- $makeTemplates
  toTypeCon, toDataCon,

  toVarExp, toVarPat
  ) where

import Data.Char (toUpper, toLower)
import Data.Set (Set, fromList, member)
import Language.Haskell.TH
  (Name, mkName, TypeQ, conT, ExpQ, conE, varE, PatQ, varP)

capitalize :: String -> String
capitalize (c:cs) = toUpper c : cs
capitalize ""     = ""

unCapitalize :: String -> String
unCapitalize (c:cs) = toLower c : cs
unCapitalize ""     = ""

-- | rename the string that equals to reserved identifiers.
rename :: String -> String
rename cs | cs `member` reservedIds = cs ++ "_"
          | otherwise = cs
{-# INLINE rename #-}

-- | All reserved identifiers. Taken from section 2.4 of the 2010 Report.
reservedIds :: Set String
reservedIds = fromList [ "case", "class", "data", "default", "deriving"
                       , "do", "else", "foreign", "if", "import", "in"
                       , "infix", "infixl", "infixr", "instance", "let"
                       , "module", "newtype", "of", "then", "type", "where"
                       , "_" ]
{-# INLINE reservedIds #-}

{- $nameTypes
Wrap 'Name' to distinguish constructor names and variable names.
-}

-- | Type to wrap constructor\'s 'Name'.
newtype ConName = ConName { conName :: Name {- ^ Get wrapped 'Name' -} }

-- | Make constructor name from 'String'.
toConName :: String -> ConName
toConName =  ConName . mkName . rename . capitalize

-- | Type to wrap variable\'s 'Name'.
newtype VarName = VarName { varName :: Name {- ^ Get wrapped 'Name' -} }

-- | Make variable name from 'String'.
toVarName :: String -> VarName
toVarName =  VarName . mkName . rename . unCapitalize

-- | 'Char' set used from camel-cased names.
nameChars :: String
nameChars =  '\'' : ['0' .. '9'] ++ ['A' .. 'Z'] ++  ['a' .. 'z']

-- | Split into chunks to generate camel-cased 'String'.
splitForName :: String -> [String]
splitForName str
  | rest /= [] = tk : splitForName (tail rest)
  | otherwise  = [tk]
  where
    (tk, rest) = span (`elem` nameChars) str

{- $makeNames
Make camel-cased names.
-}

-- | Convert into camel-cased 'String'.
--   First 'Char' of result is upper case.
camelcaseUpper :: String -> String
camelcaseUpper =  concatMap capitalize . splitForName

-- | Make camel-cased constructor name from 'String'.
conCamelcaseName :: String -> ConName
conCamelcaseName =  toConName . camelcaseUpper

-- | Make camel-cased variable name from 'String'.
varCamelcaseName :: String -> VarName
varCamelcaseName =  toVarName . camelcaseUpper

-- | Make camel-cased variable name with prefix like below.
--
-- >  name `varNamePrefix` prefix
--
varNameWithPrefix :: String -> String -> VarName
varNameWithPrefix n p =  toVarName $ p ++ camelcaseUpper n

{- $makeTemplates
Make haskell templates from names.
-}

-- | Make type constructor 'TypeQ' monad from constructor name type.
toTypeCon :: ConName -> TypeQ
toTypeCon =  conT . conName

-- | Make data constructor 'ExpQ' monad from constructor name type.
toDataCon :: ConName -> ExpQ
toDataCon =  conE . conName

-- | Make variable 'ExpQ' monad from variable name type.
toVarExp :: VarName -> ExpQ
toVarExp =  varE . varName

-- | Make pattern 'PatQ' monad from variable name type.
toVarPat :: VarName -> PatQ
toVarPat =  varP . varName