module ASCII.Caseless
  ( -- * The @CaselessChar@ type
    CaselessChar (..),

    -- * Enumeration
    allCharacters,

    -- * Conversion
    disregardCase,
    toCase,
    assumeCaseUnsafe,
  )
where

import ASCII.Case (Case (..))
import ASCII.Char qualified as ASCII
import Data.Bool (otherwise)
import Data.Bool qualified as Bool
import Data.Char qualified as C
import Data.Data (Data)
import Data.Eq (Eq, (/=), (==))
import Data.Hashable (Hashable)
import Data.Int (Int)
import Data.Maybe (Maybe (..))
import Data.Ord (Ord, (<), (<=), (>))
import GHC.Generics (Generic)
import Text.Show (Show)
import Prelude (Bounded, Enum, enumFromTo, fromEnum, maxBound, minBound, toEnum, (+), (-))
import Prelude qualified as Enum (Enum (..))

-- | A character in the ASCII character set, without an
-- upper/lower case distinction for letters
data CaselessChar =
      Null | StartOfHeading | StartOfText | EndOfText
    | EndOfTransmission | Enquiry | Acknowledgement | Bell
    | Backspace | HorizontalTab | LineFeed | VerticalTab
    | FormFeed | CarriageReturn | ShiftOut | ShiftIn | DataLinkEscape

    | DeviceControl1 | DeviceControl2 | DeviceControl3 | DeviceControl4

    | NegativeAcknowledgement | SynchronousIdle | EndOfTransmissionBlock
    | Cancel | EndOfMedium | Substitute | Escape

    | FileSeparator | GroupSeparator | RecordSeparator | UnitSeparator

    | Space | ExclamationMark | QuotationMark | NumberSign
    | DollarSign | PercentSign | Ampersand | Apostrophe
    | LeftParenthesis | RightParenthesis | Asterisk
    | PlusSign | Comma | HyphenMinus | FullStop | Slash

    | Digit0 | Digit1 | Digit2 | Digit3 | Digit4
    | Digit5 | Digit6 | Digit7 | Digit8 | Digit9

    | Colon | Semicolon | LessThanSign | EqualsSign
    | GreaterThanSign | QuestionMark | AtSign

    | LetterA | LetterB | LetterC | LetterD | LetterE | LetterF
    | LetterG | LetterH | LetterI | LetterJ | LetterK | LetterL
    | LetterM | LetterN | LetterO | LetterP | LetterQ | LetterR
    | LetterS | LetterT | LetterU | LetterV | LetterW | LetterX
    | LetterY | LetterZ

    | LeftSquareBracket | Backslash | RightSquareBracket
    | Caret | Underscore | GraveAccent

    | LeftCurlyBracket | VerticalLine | RightCurlyBracket
    | Tilde | Delete

-- | ASCII characters can be compared for equality using '(==)'.
deriving stock instance Eq CaselessChar

-- | Caseless ASCII characters are ordered; for example, the letter
-- /A/ is "less than" ('<') the letter /B/ because it appears earlier
-- in the list. The ordering of caseless ASCII characters is roughly
-- the same as the ordering of the corresponding Unicode 'C.Char's,
-- with caseless letters appearing in the place of case-sensitive
-- capital letters.
deriving stock instance Ord CaselessChar

-- | The 'Enum' instance allows us to use range syntax, for example
-- @['LetterA' .. 'LetterZ']@ is a list all letters from /a/ to /z/.
deriving stock instance Enum CaselessChar

-- | You can write @(['minBound' .. 'maxBound'] :: ['CaselessChar'])@
-- to get a list of all the caseless ASCII characters.
deriving stock instance Bounded CaselessChar

-- | 'show' produces the name of a constructor. For example,
-- the character @e@ is shown as “@LetterE@”.
-- See "ASCII.Caseless" for the complete list of constructor names.
deriving stock instance Show CaselessChar

deriving stock instance Data CaselessChar

deriving stock instance Generic CaselessChar

deriving anyclass instance Hashable CaselessChar

-- | There are 102 characters in total.
allCharacters :: [CaselessChar]
allCharacters :: [CaselessChar]
allCharacters = forall a. Enum a => a -> a -> [a]
Prelude.enumFromTo forall a. Bounded a => a
Prelude.minBound forall a. Bounded a => a
Prelude.maxBound

disregardCase :: ASCII.Char -> CaselessChar
disregardCase :: Char -> CaselessChar
disregardCase Char
x
  | Char
x forall a. Ord a => a -> a -> Bool
<= Char
ASCII.GraveAccent = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum Char
x)
  | Char
x forall a. Ord a => a -> a -> Bool
<= Char
ASCII.SmallLetterZ = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum Char
x forall a. Num a => a -> a -> a
- Int
32)
  | Bool
Bool.otherwise = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum Char
x forall a. Num a => a -> a -> a
- Int
26)

-- | Like 'disregardCase', but defined only where the character
-- is either a letter in the given case or a non-letter
--
-- For upper case, this is slightly more efficient than 'disregardCase'.
assumeCaseUnsafe :: Case -> ASCII.Char -> CaselessChar
assumeCaseUnsafe :: Case -> Char -> CaselessChar
assumeCaseUnsafe Case
LowerCase Char
x = Char -> CaselessChar
disregardCase Char
x
assumeCaseUnsafe Case
UpperCase Char
x
  | Char
x forall a. Ord a => a -> a -> Bool
<= Char
ASCII.GraveAccent = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum Char
x)
  | Bool
Bool.otherwise = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum Char
x forall a. Num a => a -> a -> a
- Int
26)

toCase :: Case -> CaselessChar -> ASCII.Char
toCase :: Case -> CaselessChar -> Char
toCase Case
UpperCase CaselessChar
x
  | CaselessChar
x forall a. Ord a => a -> a -> Bool
<= CaselessChar
GraveAccent = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum CaselessChar
x)
  | Bool
Bool.otherwise = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum CaselessChar
x forall a. Num a => a -> a -> a
+ Int
26)
toCase Case
LowerCase CaselessChar
x
  | CaselessChar
x forall a. Ord a => a -> a -> Bool
<= CaselessChar
AtSign = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum CaselessChar
x)
  | CaselessChar
x forall a. Ord a => a -> a -> Bool
<= CaselessChar
LetterZ = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum CaselessChar
x forall a. Num a => a -> a -> a
+ Int
32)
  | CaselessChar
x forall a. Ord a => a -> a -> Bool
<= CaselessChar
GraveAccent = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum CaselessChar
x)
  | Bool
Bool.otherwise = forall a. Enum a => Int -> a
Enum.toEnum (forall a. Enum a => a -> Int
Enum.fromEnum CaselessChar
x forall a. Num a => a -> a -> a
+ Int
26)