{-# LANGUAGE Safe #-}

-- |
-- Module      : Data.Char.Control
-- Description : Visualizing control characters.
-- Maintainer  : hapytexeu+gh@gmail.com
-- Stability   : experimental
-- Portability : POSIX
--
-- Unicode has a <https://www.unicode.org/charts/PDF/U2400.pdf block> named /Control Pictures/ that visualizes control characters such as NULL, SUB, LF, DEL, etc.
-- This module aims to make it more convenient to convert the control characters to their visualization and vice versa. Only ASCII control characters and
-- the space are supported.
module Data.Char.Control
  ( -- * Conversion to control pictures
    controlPicture,
    controlPicture',
    convertToControlPictures,

    -- * Conversion from control picturesa
    fromControlPicture,
    fromControlPicture',

    -- * Check if a 'Char' is a control 'Char'
    isAsciiControl,
    isControl,
    hasControlVisualization,

    -- * Alternative characters
    blankSymbol,
    openBox,
    newLine,
    alternativeDelete,
    alternativeSubstitute,
  )
where

import Data.Bits ((.&.), (.|.))
import Data.Char (chr, isControl, ord)
import Data.Maybe (fromMaybe)
import Data.Text as T

-- | Check if the given 'Char' is a control character in the ASCII range.
isAsciiControl ::
  -- | The given 'Char' to check.
  Char ->
  -- | 'True' if the given 'Char' is a control character in the ASCII range; otherwise 'False'.
  Bool
isAsciiControl :: Char -> Bool
isAsciiControl Char
c = Char
c forall a. Ord a => a -> a -> Bool
<= Char
'\x7f' Bool -> Bool -> Bool
&& Char -> Bool
isControl Char
c

-- | Check if for the given 'Char' there is a visualization.
hasControlVisualization ::
  -- | The given 'Char' to check.
  Char ->
  -- | 'True' if the given control character can be visualized; 'False' otherwise.
  Bool
hasControlVisualization :: Char -> Bool
hasControlVisualization Char
' ' = Bool
True
hasControlVisualization Char
c = Char -> Bool
isAsciiControl Char
c

-- | Another symbol used to denote a /space/ that works with @␢@. The 'controlPicture' function uses @␠@.
blankSymbol ::
  -- | Another character for /space/.
  Char
blankSymbol :: Char
blankSymbol = Char
'\x2422'

-- | Another symbol used to denote a /space/ that works with @␣@. The 'controlPicture' function uses @␠@.
openBox ::
  -- | Another character for /space/.
  Char
openBox :: Char
openBox = Char
'\x2423'

-- | Another symbol used to denote a /new line/ that works with @␤@. The control picture function uses @␊@.
newLine ::
  -- | Another character for a /new line/.
  Char
newLine :: Char
newLine = Char
'\x2424'

-- | Another symbol used to denote a /delete/ character that works with @␥@. The control picture function uses @␡@.
alternativeDelete ::
  -- | Another character for /delete/.
  Char
alternativeDelete :: Char
alternativeDelete = Char
'\x2425'

-- | Another symbol used to denote a /substitute/ character that works with @␦@. The control picture function uses @␚@.
alternativeSubstitute ::
  -- | Another character for /substitute/.
  Char
alternativeSubstitute :: Char
alternativeSubstitute = Char
'\x2426'

-- | Convert the given control 'Char' to a 'Char' that visualizes that characters.
-- This is sometimes done by diagonal lettering of the characters denoting the control
-- character. If the given 'Char' is not a control character, 'Nothing' is returned.
controlPicture ::
  -- | The given control 'Char' to convert.
  Char ->
  Maybe Char -- The corresponding 'Char' that visualizes the control 'Char' wrapped in a 'Just'; 'Nothing' if the given 'Char' is not a control character.
controlPicture :: Char -> Maybe Char
controlPicture Char
c
  | Char
c forall a. Ord a => a -> a -> Bool
<= Char
' ' = forall a. a -> Maybe a
Just (Char -> Char
controlPicture' Char
c)
  | Bool
otherwise = forall a. Maybe a
Nothing

-- | Convert the given control 'Char' to a 'Char' that visualizes that character.
-- If the given 'Char' is not a control character, it is unspecified what happens.
controlPicture' ::
  -- | The given control 'Char'.
  Char ->
  -- | The corresponding 'Char' that visualizes the control 'Char'.
  Char
controlPicture' :: Char -> Char
controlPicture' Char
c
  | Char
c forall a. Ord a => a -> a -> Bool
<= Char
' ' = Int -> Char
chr (Int
0x2400 forall a. Bits a => a -> a -> a
.|. Char -> Int
ord Char
c)
  | Bool
otherwise = Char
'\x2421'

-- | Convert the given visualization of a control 'Char' to that control 'Char' wrapped
-- in a 'Just'. If the given 'Char' is not a visualization of a control character,
-- 'Nothing' is returned.
fromControlPicture ::
  -- | The given /visualization/ of control 'Char'.
  Char ->
  -- | The corresponding control 'Char' wrapped in a 'Just' if the given character is the visualization of a control character; otherwise 'Nothing'.
  Maybe Char
fromControlPicture :: Char -> Maybe Char
fromControlPicture Char
c
  | Char
'\x2400' forall a. Ord a => a -> a -> Bool
<= Char
c Bool -> Bool -> Bool
&& Char
c forall a. Ord a => a -> a -> Bool
<= Char
'\x2426' = forall a. a -> Maybe a
Just (Char -> Char
fromControlPicture' Char
c)
  | Bool
otherwise = forall a. Maybe a
Nothing

-- | Convert the given visualization of a control 'Char' to that control 'Char'.
-- If the given 'Char' is not a visualization of a control character,
-- it is unspecified what happens.
fromControlPicture' ::
  -- | The given /visualization/ of control 'Char'.
  Char ->
  -- | The corresponding control 'Char'.
  Char
fromControlPicture' :: Char -> Char
fromControlPicture' Char
'\x2421' = Char
'\x7f'
fromControlPicture' Char
'\x2422' = Char
' '
fromControlPicture' Char
'\x2423' = Char
' '
fromControlPicture' Char
'\x2424' = Char
'\x0a'
fromControlPicture' Char
'\x2425' = Char
'\x7f'
fromControlPicture' Char
'\x2426' = Char
'\x1a'
fromControlPicture' Char
c = Int -> Char
chr (Int
0x7f forall a. Bits a => a -> a -> a
.&. Char -> Int
ord Char
c)

-- | Convert the given 'Text' to a 'Text' object where the control characters
-- that have in Unicode a control picture block item.
convertToControlPictures ::
  -- | The given 'Text' where we want to convert control characters to their control picture characters.
  Text ->
  -- | The corresponding 'Text' where the control characters are converted to their control picture characters.
  Text
convertToControlPictures :: Text -> Text
convertToControlPictures = (Char -> Char) -> Text -> Text
T.map (forall a. a -> Maybe a -> a
fromMaybe forall (f :: * -> *) a b. Applicative f => f (a -> b) -> f a -> f b
<*> Char -> Maybe Char
controlPicture)