-- |
-- This Module converts between camelCase and snake_case.
--
-- The suffixes specify the base type the functions are working on.
-- L uses lazy 'TL.Text', S uses 'String' and B8 uses 'B8.ByteString'.
--
-- Beware, that you should not use 'B8.ByteString's for serious work, as
-- they only work with 8 bit characters. Do only use when you are
-- absolutely sure, there is no wide character in the string!
{-# LANGUAGE OverloadedStrings #-}
module Text.AnimalCase
  ( toCamelCase
  , toSnakeCase
  , toCamelCaseS
  , toSnakeCaseS
  , toCamelCaseL
  , toSnakeCaseL
  , toCamelCaseB8
  , toSnakeCaseB8
  ) where

import Control.Arrow (first)
import Data.Char (isUpper)
import Data.Monoid ((<>))
import Data.Text.Lazy.Encoding (decodeLatin1, encodeUtf8)
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy as BL
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL

useLazy :: (TL.Text -> TL.Text) -> T.Text -> T.Text
useLazy f = TL.toStrict .  f . TL.fromStrict

useLazyS :: (TL.Text -> TL.Text) -> String -> String
useLazyS f = TL.unpack . f . TL.pack

useLazyB8 :: (TL.Text -> TL.Text) -> B8.ByteString -> B8.ByteString
useLazyB8 f = BL.toStrict . encodeUtf8 . f . decodeLatin1 . BL.fromStrict

onFirstLetter :: (Char -> TL.Text) -> TL.Text -> TL.Text
onFirstLetter f = maybe "" (uncurry (<>) . first f) . TL.uncons

toCamelCase :: T.Text -> T.Text
toCamelCase = useLazy toCamelCaseL

toCamelCaseL :: TL.Text -> TL.Text
toCamelCaseL = TL.concat . map capitalizeWord . TL.splitOn "_"
  where capitalizeWord = onFirstLetter (TL.toUpper . TL.singleton)

toCamelCaseS :: String -> String
toCamelCaseS = useLazyS toCamelCaseL

toCamelCaseB8 :: B8.ByteString -> B8.ByteString
toCamelCaseB8 = useLazyB8 toCamelCaseL

toSnakeCase :: T.Text -> T.Text
toSnakeCase = useLazy toSnakeCaseL

toSnakeCaseL :: TL.Text -> TL.Text
toSnakeCaseL = TL.concatMap f
  where f c
          | isUpper c = "_" <> TL.toLower (TL.singleton c)
          | otherwise = TL.singleton c

toSnakeCaseS :: String -> String
toSnakeCaseS = useLazyS toSnakeCaseL

toSnakeCaseB8 :: B8.ByteString -> B8.ByteString
toSnakeCaseB8 = useLazyB8 toSnakeCaseL