module ASCII.Refinement
  (
    {- * ASCII type constructor -} ASCII, lift, asciiUnsafe,
    {- * Character functions -} validateChar, fromChar, toChar, substituteChar, asChar,
    {- * String functions -} validateString, fromCharList, toCharList, substituteString, mapChars
  )
  where

import qualified ASCII.Char as ASCII
import qualified ASCII.Superset as S
import qualified ASCII.Isomorphism as I

import ASCII.Superset    ( CharSuperset, StringSuperset )
import Data.Bool         ( Bool (..) )
import Data.Data         ( Data )
import Data.Eq           ( Eq )
import Data.Function     ( (.), ($), id )
import Data.Hashable     ( Hashable )
import Data.List         ( map )
import Data.Maybe        ( Maybe (..) )
import Data.Monoid       ( Monoid )
import Data.Ord          ( Ord, (>) )
import Data.Semigroup    ( Semigroup )
import GHC.Generics      ( Generic )
import Prelude           ( succ )
import Text.Show         ( Show, showString, showsPrec, showParen, showList )

{- $setup

>>> :set -XOverloadedStrings
>>> import ASCII.Char (Char (..))
>>> import Data.List (map)
>>> import Data.Int (Int)
>>> import Data.String (String)
>>> import Data.Text (Text)

-}

{- | This type constructor indicates that a value from some ASCII superset is valid ASCII. The type parameter is the ASCII superset, which should be a type with an instance of either 'CharSuperset' or 'StringSuperset'.

For example, whereas a 'Data.Text.Text' value may contain a combination of ASCII and non-ASCII characters, a value of type @'ASCII' 'Data.Text.Text'@ may contain only ASCII characters.

-}

newtype ASCII superset = ASCII_Unsafe { lift :: superset }

deriving stock instance Eq superset => Eq (ASCII superset)

deriving stock instance Ord superset => Ord (ASCII superset)

deriving newtype instance Hashable superset => Hashable (ASCII superset)

deriving newtype instance Semigroup superset => Semigroup (ASCII superset)

deriving newtype instance Monoid superset => Monoid (ASCII superset)

deriving stock instance Data superset => Data (ASCII superset)

deriving stock instance Generic (ASCII superset)

instance Show superset => Show (ASCII superset)
  where
    showsPrec d x = showParen (d > app_prec) $
        showString "asciiUnsafe " . showsPrec (succ app_prec) (lift x)
      where app_prec = 10

    showList x = showString "asciiUnsafe " . showList (map lift x)

instance CharSuperset char => CharSuperset (ASCII char)
  where
    isAsciiChar _ = True
    fromChar = asciiUnsafe . S.fromChar
    toCharUnsafe = S.toCharUnsafe . lift

instance CharSuperset char => I.CharIso (ASCII char)
  where
    toChar = S.toCharUnsafe

instance StringSuperset string => StringSuperset (ASCII string)
  where
    isAsciiString _ = True
    fromCharList = asciiUnsafe . S.fromCharList
    toCharListUnsafe = S.toCharListUnsafe . lift
    toCharListSub = S.toCharListUnsafe . lift
    substituteString = id

instance StringSuperset string => I.StringIso (ASCII string)
  where
    toCharList = S.toCharListUnsafe
    mapChars = S.mapCharsUnsafe

asciiUnsafe :: superset -> ASCII superset
asciiUnsafe = ASCII_Unsafe

{- |

>>> map validateChar [-1, 65, 97, 128] :: [Maybe (ASCII Int)]
[Nothing,Just (asciiUnsafe 65),Just (asciiUnsafe 97),Nothing]

-}

validateChar :: CharSuperset superset => superset -> Maybe (ASCII superset)
validateChar x = if S.isAsciiChar x then Just (asciiUnsafe x) else Nothing

substituteChar :: CharSuperset superset => superset -> ASCII superset
substituteChar x = if S.isAsciiChar x then asciiUnsafe x else fromChar ASCII.Substitute

fromChar :: CharSuperset superset => ASCII.Char -> ASCII superset
fromChar = asciiUnsafe . S.fromChar

toChar :: CharSuperset superset => ASCII superset -> ASCII.Char
toChar = S.toCharUnsafe . lift

{- |

>>> fromCharList [CapitalLetterH,SmallLetterI,ExclamationMark] :: ASCII Text
asciiUnsafe "Hi!"

-}

fromCharList :: StringSuperset superset => [ASCII.Char] -> ASCII superset
fromCharList = asciiUnsafe . S.fromCharList

{- |

>>> toCharList (substituteString "Piñata" :: ASCII Text)
[CapitalLetterP,SmallLetterI,Substitute,SmallLetterA,SmallLetterT,SmallLetterA]

-}

toCharList :: StringSuperset superset => ASCII superset -> [ASCII.Char]
toCharList = S.toCharListUnsafe . lift

{- | Forces a string from a larger character set into ASCII by using the 'ASCII.Substitute' character in place of any non-ASCII characters.

>>> substituteString "Cristóbal" :: ASCII Text
asciiUnsafe "Crist\SUBbal"

-}

substituteString :: StringSuperset superset => superset -> ASCII superset
substituteString = asciiUnsafe . S.substituteString

{- |

>>> map validateString ["Hello", "Cristóbal"] :: [Maybe (ASCII Text)]
[Just (asciiUnsafe "Hello"),Nothing]

>>> map validateString ["Hello", "Cristóbal"] :: [Maybe (ASCII String)]
[Just (asciiUnsafe "Hello"),Nothing]

-}

validateString :: StringSuperset superset => superset -> Maybe (ASCII superset)
validateString x = if S.isAsciiString x then Just (asciiUnsafe x) else Nothing

asChar :: CharSuperset superset => (ASCII.Char -> ASCII.Char) -> ASCII superset -> ASCII superset
asChar f = asciiUnsafe . S.asCharUnsafe f . lift

mapChars :: StringSuperset superset => (ASCII.Char -> ASCII.Char) -> ASCII superset -> ASCII superset
mapChars f = asciiUnsafe . S.mapCharsUnsafe f . lift