    -- |
    -- 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