{-# LANGUAGE Safe #-}

{-|
Module      : Data.Char.Small
Description : A module used to render subscript and superscript in Unicode.
Maintainer  : hapytexeu+gh@gmail.com
Stability   : experimental
Portability : POSIX

One can make use of a <https://www.unicode.org/charts/PDF/U2070.pdf block of Unicode characters> to /emulate/ subscript and superscript. Note that the subscript and superscript will be
aligned with the /baseline/ and the /cap line/ respectively, and is thus not equivalent to @<sub>...</sub>@ and @<sup>...</sup>@ in HTML. Furthermore only a small subset of characters
is supported.

This module allows one to map certain characters to their subscript and superscript counterpart, and furthermore makes it more convenient to transform a number (both positive and negative)
to a 'Text' that specifies this number in subscript and superscript.
-}

module Data.Char.Small (
  -- * Convert characters to their subscript and superscript counterpart
    toSub, toSup
  -- * Numbers as subscript and superscript.
  , asSub, asSup
  -- * Ratio formatting
  , ratioToUnicode
  ) where

import Data.Char(chr, isDigit, ord)
import Data.Ratio(Ratio, denominator, numerator)
import Data.Text(Text, cons, snoc, singleton)

-- | Convert a set of characters to their superscript counterpart, given that
-- characters exists.
toSup
    :: Char  -- ^ The given character to convert to its superscript counterpart.
    -> Maybe Char -- ^ A character wrapped in a 'Just' given the counterpart exists, 'Nothing' otherwise.
toSup 'i' = Just '\x2071'
toSup '+' = Just '\x207a'
toSup '-' = Just '\x207b'
toSup '\x2212' = Just '\x207b'
toSup '=' = Just '\x207c'
toSup '(' = Just '\x207d'
toSup ')' = Just '\x207e'
toSup 'n' = Just '\x207f'
toSup c | isDigit c = Just (_digitToSub (ord c - ord '0'))
        | otherwise = Nothing

-- | Convert a set of characters to their subscript counterpart, given that
-- characters exists.
toSub
    :: Char  -- ^ The given character to convert to its subscript counterpart.
    -> Maybe Char -- ^ A character wrapped in a 'Just' given the counterpart exists, 'Nothing' otherwise.
toSub '+' = Just '\x208a'
toSub '-' = Just '\x208b'
toSub '\x2212' = Just '\x208b'
toSub '=' = Just '\x208c'
toSub '(' = Just '\x208d'
toSub ')' = Just '\x208e'
toSub 'a' = Just '\x2090'
toSub 'e' = Just '\x2091'
toSub 'o' = Just '\x2092'
toSub 'x' = Just '\x2093'
toSub '\x259' = Just '\x2094'
toSub 'h' = Just '\x2095'
toSub 'k' = Just '\x2095'
toSub 'l' = Just '\x2095'
toSub 'm' = Just '\x2095'
toSub 'n' = Just '\x2095'
toSub 'p' = Just '\x2095'
toSub 's' = Just '\x2095'
toSub 't' = Just '\x2095'
toSub c | isDigit c = Just (_digitToSub (ord c - ord '0'))
        | otherwise = Nothing

_value :: Integral i => (Int -> Char) -> i -> Text
_value f = go
    where f' = f . fromIntegral
          go n | n <= 9 = singleton (f' n)
               | otherwise = snoc (go q) (f' r)
               where (q,r) = quotRem n 10

_prefixSign :: Integral i => Char -> (Int -> Char) -> i -> Text
_prefixSign c f v
    | v < 0 = cons c (f' (-v))
    | otherwise = f' v
    where f' = _value f

-- | Format a given 'Ratio' object to a 'Text' value that formats the ratio with
-- superscript and subscript.
ratioToUnicode :: Integral i
    => Ratio i -- ^ The given 'Ratio' value to format.
    -> Text -- ^ The 'Text' block that contains a textual representation of the 'Ratio'.
ratioToUnicode dn = asSup (numerator dn) <> cons '\x2044' (asSub (denominator dn))

-- | Convert a number (positive or negative) to a 'Text' that specifies that
-- number in superscript characters.
asSup :: Integral i
    => i -- ^ The number to convert.
    -> Text -- ^ A 'Text' value that contains the number as a sequence of superscript characters.
asSup = _prefixSign '\x207b' _digitToSup

-- | Convert a number (positive or negative) to a 'Text' that specifies that
-- number in subscript characters.
asSub :: Integral i
    => i -- ^ The number to convert.
    -> Text -- ^ A 'Text' value that contains the number as a sequence of subscript characters.
asSub = _prefixSign '\x208b' _digitToSub

_digitToSub :: Int -> Char
_digitToSub = chr . (8320+)

_digitToSup :: Int -> Char
_digitToSup 0 = '\x2070'
_digitToSup 1 = '\xb9'
_digitToSup n | n <= 3 = chr (176+n)
              | otherwise = chr (8304+n)
