{- |

/Case/ is a property of letters. /A-Z/ are /upper case/ letters, and /a-z/ are /lower case/ letters. No other ASCII characters have case.

-}

module ASCII.Case ( Case (..), letterCase, isCase, toCase ) where

import ASCII.Char    ( Char (..) )
import Data.Bool     ( Bool, otherwise )
import Data.Data     ( Data )
import Data.Eq       ( Eq )
import Data.Function ( (.) )
import Data.Hashable ( Hashable )
import Data.Ord      ( Ord, (<=), (>=) )
import GHC.Generics  ( Generic )
import Prelude       ( Enum, Bounded, Int, (+), (-) )
import Text.Show     ( Show )
import Data.Maybe    ( Maybe (..) )

import qualified ASCII.Char as Char
import qualified Data.Bool  as Bool

data Case =
    UpperCase -- ^ The letters from 'CapitalLetterA' to 'CapitalLetterZ'.
  | LowerCase -- ^ The letters from 'SmallLetterA' to 'SmallLetterZ'.

deriving stock instance Eq Case

deriving stock instance Ord Case

deriving stock instance Enum Case

deriving stock instance Bounded Case

deriving stock instance Show Case

deriving stock instance Data Case

deriving stock instance Generic Case

deriving anyclass instance Hashable Case

{- | Determines whether a character is a letter, and if so, whether it is upper or lower case.

>>> map letterCase [CapitalLetterR, SmallLetterR, DollarSign]
[Just UpperCase,Just LowerCase,Nothing]

-}

letterCase :: Char -> Maybe Case
letterCase :: Char -> Maybe Case
letterCase Char
x | Case -> Char -> Bool
isCase Case
UpperCase Char
x = Case -> Maybe Case
forall a. a -> Maybe a
Just Case
UpperCase
             | Case -> Char -> Bool
isCase Case
LowerCase Char
x = Case -> Maybe Case
forall a. a -> Maybe a
Just Case
LowerCase
             | Bool
otherwise          = Maybe Case
forall a. Maybe a
Nothing

{- | Determines whether a character is a letter of a particular case.

>>> map (isCase UpperCase) [CapitalLetterR,SmallLetterR,DollarSign]
[True,False,False]

-}

isCase :: Case -> Char -> Bool
isCase :: Case -> Char -> Bool
isCase Case
c Char
x = Bool -> Bool -> Bool
(Bool.&&) ( Char
x Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
>= Char
a ) ( Char
x Char -> Char -> Bool
forall a. Ord a => a -> a -> Bool
<= Char
z ) where (Char
a, Char
z) = Case -> (Char, Char)
az Case
c

az :: Case -> (Char, Char)
az :: Case -> (Char, Char)
az Case
UpperCase = (Char
CapitalLetterA, Char
CapitalLetterZ)
az Case
LowerCase = (Char
SmallLetterA, Char
SmallLetterZ)

{- | Maps a letter character to its upper/lower case equivalent.

>>> toCase UpperCase SmallLetterX
CapitalLetterX

>>> toCase LowerCase CapitalLetterF
SmallLetterF

Characters that are already in the requested case are unmodified by this transformation.

>>> toCase UpperCase CapitalLetterA
CapitalLetterA

Characters that are not letters, such as exclamation mark, are unmodified by this transformation.

>>> toCase UpperCase ExclamationMark
ExclamationMark

-}

toCase :: Case -> Char -> Char
toCase :: Case -> Char -> Char
toCase Case
c Char
x = if Case -> Char -> Bool
isCase (Case -> Case
opposite Case
c) Char
x then Case -> Char -> Char
changeCaseUnsafe Case
c Char
x else Char
x

-- | Change a letter to the given case, assuming that the input character is a letter of the opposite case.

changeCaseUnsafe :: Case -> Char -> Char
changeCaseUnsafe :: Case -> Char -> Char
changeCaseUnsafe Case
c = (Int -> Int) -> Char -> Char
charAsIntUnsafe (Case -> Int -> Int
changeCaseInt Case
c)

changeCaseInt :: Case -> Int -> Int
changeCaseInt :: Case -> Int -> Int
changeCaseInt Case
LowerCase Int
i = Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
+ Int
32
changeCaseInt Case
UpperCase Int
i = Int
i Int -> Int -> Int
forall a. Num a => a -> a -> a
- Int
32

opposite :: Case -> Case
opposite :: Case -> Case
opposite Case
UpperCase = Case
LowerCase
opposite Case
LowerCase = Case
UpperCase

charAsIntUnsafe :: (Int -> Int) -> (Char -> Char)
charAsIntUnsafe :: (Int -> Int) -> Char -> Char
charAsIntUnsafe Int -> Int
f = Int -> Char
Char.fromIntUnsafe (Int -> Char) -> (Char -> Int) -> Char -> Char
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Int
f (Int -> Int) -> (Char -> Int) -> Char -> Int
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> Int
Char.toInt