{-# LANGUAGE DataKinds #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeOperators #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE ScopedTypeVariables #-}

-- | Strings can be encoded as 'Enc "r-ASCII"@ only if they contain only ASCII characters (first 128 characters of the Unicode character set).
--
-- This is sometimes referred to as ASCII-7 and future versions of @type-encoding@ may change @"r-ASCII"@ symbol annotation to reflect this.
--  
-- prop> B8.all ((< 128) . ord) . getPayload @ '["r-ASCII"] @() @B.ByteString
-- 
-- >>> :set -XOverloadedStrings -XMultiParamTypeClasses -XDataKinds
-- >>> encodeFAll . toEncoding () $ "Hello World" :: Either EncodeEx (Enc '["r-ASCII"] () T.Text)
-- Right (UnsafeMkEnc Proxy () "Hello World")
--
-- >>> encodeFAll . toEncoding () $ "\194\160" :: Either EncodeEx (Enc '["r-ASCII"] () T.Text)
-- Left (EncodeEx "r-ASCII" (NonAsciiChar '\194'))
--
-- @since 0.1.0.0
module Data.TypedEncoding.Instances.Restriction.ASCII where

import           Data.TypedEncoding.Instances.Support
import           Data.TypedEncoding.Common.Class.Util.StringConstraints

import           Data.TypedEncoding.Internal.Util (explainBool)
import           Data.Char


-- $setup
-- >>> :set -XDataKinds -XTypeApplications
-- >>> import qualified Data.Text as T
-- >>> import qualified Data.ByteString as B
-- >>> import qualified Data.ByteString.Char8 as B8
-- >>> import Test.QuickCheck
-- >>> import Test.QuickCheck.Instances.ByteString()
-- >>> import Data.TypedEncoding
-- >>> :{
-- instance Arbitrary (Enc '["r-ASCII"] () B.ByteString) where 
--      arbitrary =  fmap (unsafeSetPayload ()) 
--                   . flip suchThat (B8.all isAscii) 
--                        $ arbitrary 
-- :}
--


-----------------
-- Encodings  --
-----------------

newtype NonAsciiChar = NonAsciiChar Char deriving (Eq, Show)

-- * Encoding 

instance Encode (Either EncodeEx) "r-ASCII" "r-ASCII" c Char where
    encoding = encASCIIChar

instance Char8Find str => Encode (Either EncodeEx) "r-ASCII" "r-ASCII" c str where
    encoding = encASCII

encASCIIChar :: Encoding (Either EncodeEx) "r-ASCII" "r-ASCII" c Char
encASCIIChar = _implEncodingEx (\c -> explainBool NonAsciiChar (c, isAscii c))

encASCII :: Char8Find str =>  Encoding (Either EncodeEx) "r-ASCII" "r-ASCII" c str
encASCII = _implEncodingEx @"r-ASCII" encImpl

encImpl :: Char8Find str => str -> Either NonAsciiChar str
encImpl str = case find (not . isAscii) str of
    Nothing -> Right str
    Just ch -> Left $ NonAsciiChar ch

-- * Decoding

instance (Applicative f) => Decode f "r-ASCII" "r-ASCII" c str where
    decoding = decAnyR

instance (Char8Find str, RecreateErr f, Applicative f) => Validate f "r-ASCII" "r-ASCII" () str where
    validation = validR encASCII


-- tst = encodeFAll . toEncoding () $ "Hello World" :: Either EncodeEx (Enc '["r-ASCII"] () T.Text)
-- tst2 = encodeFAll . toEncoding () $ "\194\160" :: Either EncodeEx (Enc '["r-ASCII"] () T.Text)
-- tst3 = encodeFAll . toEncoding () $ "\194\160" :: Either EncodeEx (Enc '["r-ASCII"] () B.ByteString)