-- | Functions for working with characters. Character literals are enclosed in @\'a\'@ pair of single quotes.
--
-- Since we don't have a browser and we don't have a default locale, functions
-- like @toLocaleUpper@ and @toLocaleUpper@ are not supported. If you need
-- something like that you can check out the 'text-icu' package which provides
-- functions like @toUpper :: LocaleName -> Text -> Text@.
module Char
  ( -- * Characters
    Char,

    -- * ASCII Letters
    isUpper,
    isLower,
    isAlpha,
    isAlphaNum,

    -- * Digits
    isDigit,
    isOctDigit,
    isHexDigit,

    -- * Conversion
    toUpper,
    toLower,

    -- * Unicode Code Points
    toCode,
    fromCode,
  )
where

import Basics
  ( (&&),
    (<<),
    (<=),
    Bool (..),
    Int,
  )
import qualified Data.Char
import Prelude (Char, otherwise)
import qualified Prelude

-- | Detect upper case ASCII characters.
--
-- > isUpper 'A' == True
-- > isUpper 'B' == True
-- > ...
-- > isUpper 'Z' == True
--
-- > isUpper '0' == False
-- > isUpper 'a' == False
-- > isUpper '-' == False
-- > isUpper 'Σ' == False
isUpper :: Char -> Bool
isUpper = Data.Char.isUpper

-- | Detect lower case ASCII characters.
--
-- > isLower 'a' == True
-- > isLower 'b' == True
-- > ...
-- > isLower 'z' == True
--
-- > isLower '0' == False
-- > isLower 'A' == False
-- > isLower '-' == False
-- > isLower 'π' == False
isLower :: Char -> Bool
isLower = Data.Char.isLower

-- | Detect upper case and lower case ASCII characters.
--
-- > isAlpha 'a' == True
-- > isAlpha 'b' == True
-- > isAlpha 'E' == True
-- > isAlpha 'Y' == True
--
-- > isAlpha '0' == False
-- > isAlpha '-' == False
-- > isAlpha 'π' == False
isAlpha :: Char -> Bool
isAlpha = Data.Char.isAlpha

-- | Detect upper case and lower case ASCII characters.
--
-- > isAlphaNum 'a' == True
-- > isAlphaNum 'b' == True
-- > isAlphaNum 'E' == True
-- > isAlphaNum 'Y' == True
-- > isAlphaNum '0' == True
-- > isAlphaNum '7' == True
--
-- > isAlphaNum '-' == False
-- > isAlphaNum 'π' == False
isAlphaNum :: Char -> Bool
isAlphaNum = Data.Char.isAlphaNum

-- | Detect digits @0123456789@
--
-- > isDigit '0' == True
-- > isDigit '1' == True
-- > ...
-- > isDigit '9' == True
--
-- > isDigit 'a' == False
-- > isDigit 'b' == False
-- > isDigit 'A' == False
isDigit :: Char -> Bool
isDigit = Data.Char.isDigit

-- | Detect octal digits @01234567@
--
-- > isOctDigit '0' == True
-- > isOctDigit '1' == True
-- > ...
-- > isOctDigit '7' == True
--
-- > isOctDigit '8' == False
-- > isOctDigit 'a' == False
-- > isOctDigit 'A' == False
isOctDigit :: Char -> Bool
isOctDigit = Data.Char.isOctDigit

-- | Detect hexadecimal digits @0123456789abcdefABCDEF@
isHexDigit :: Char -> Bool
isHexDigit = Data.Char.isHexDigit

-- | Convert to upper case.
toUpper :: Char -> Char
toUpper = Data.Char.toUpper

-- | Convert to lower case.
toLower :: Char -> Char
toLower = Data.Char.toLower

-- | Convert to the corresponding Unicode [code point](https://en.wikipedia.org/wiki/Code_point).
--
-- > toCode 'A' == 65
-- > toCode 'B' == 66
-- > toCode '木' == 26408
-- > toCode '𝌆' == 0x1D306
-- > toCode '😃' == 0x1F603
toCode :: Char -> Int
toCode = Prelude.fromIntegral << Data.Char.ord

-- | Convert a Unicode [code point](https://en.wikipedia.org/wiki/Code_point) to a character.
--
-- > fromCode 65      == 'A'
-- > fromCode 66      == 'B'
-- > fromCode 0x6728  == '\26408'  -- '木'
-- > fromCode 0x1D306 == '\119558' -- '𝌆'
-- > fromCode 0x1F603 == '\128515' -- '😃'
-- > fromCode (-1)    == '\65533'  -- '�'
--
-- The full range of unicode is from @0@ to @0x10FFFF@. With numbers outside that
-- range, you get [the replacement character](https://en.wikipedia.org/wiki/Specials_(Unicode_block)#Replacement_character).
fromCode :: Int -> Char
fromCode value
  | 0 <= value && value <= 0x10FFFF = Data.Char.chr (Prelude.fromIntegral value)
  | otherwise = '\xfffd'