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

-- | 
-- 'ToStrInj' and 'ToStrIso' are future replacement for "Data.TypedEncoding.Common.Class.IsStringR" (currently not used).
module Data.TypedEncoding.Common.Class.Util.StringConstraints where

import qualified Data.List as L
import qualified Data.Text as T
import qualified Data.Text.Lazy as TL
import           Data.String
import           Data.Proxy
import qualified Data.ByteString as B
import qualified Data.ByteString.Lazy as BL
import qualified Data.ByteString.Char8 as B8
import qualified Data.ByteString.Lazy.Char8 as BL8

-- $setup
-- >>> :set -XScopedTypeVariables -XTypeApplications -XAllowAmbiguousTypes
-- >>> import Test.QuickCheck
-- >>> import Test.QuickCheck.Instances.Text()
-- >>> import Test.QuickCheck.Instances.ByteString()


-- * IsString reversal

-- | Reverses 'Data.String.IsString'
--
-- law for types that are also @IsString@:
-- 
-- @
--  toString . fromString == id
-- @
--
-- Note: ByteString is not a valid instance, ByteString "r-ASCII", or "r-UTF8" would
-- be needed.
-- @B8.unpack $ B8.pack "\160688" == "\176"@
--
-- This class is separated from @ToStrIso@ to allow instances from /smaller/ types
-- the can inject into the 'String' type.
--
-- @since 0.3.0.0
class ToStrInj str from where
    toString :: from -> str

prop_toStringFromString :: forall s . (IsString s, ToStrInj String s) => Proxy s -> String -> Bool
prop_toStringFromString _ x = x == (toString @String @s . fromString $ x)


-- |
-- prop> prop_toStringFromString (Proxy :: Proxy T.Text) 
instance ToStrInj String T.Text where
    toString = T.unpack

-- |
-- prop> prop_toStringFromString (Proxy :: Proxy TL.Text) 
instance ToStrInj String TL.Text where
    toString = TL.unpack


instance ToStrInj String String where
    toString = id

-- will not work!
-- prop> prop_toStringFromString (Proxy :: Proxy B.ByteString) 
-- instance ToStrInj String B.ByteString where
--     toString = B8.unpack

-- will not work!
-- prop> prop_toStringFromString (Proxy :: Proxy BL.ByteString) 
-- instance ToStrInj String BL.ByteString where
--     toString = BL8.unpack


-- | Same as @ToStrInj@ but with additional
--
-- law for types that are also @IsString@:
-- @
--  fromString . toString == id
-- @
--
-- @since 0.3.0.0
class ToStrInj str from => ToStrIso str from where

prop_fromStringToString :: forall s . (IsString s, ToStrIso String s, Eq s) => s -> Bool
prop_fromStringToString x = x == (fromString @s . toString $ x)

-- |
-- prop> prop_fromStringToString @T.Text
instance ToStrIso String T.Text where

-- |
-- prop> prop_fromStringToString @TL.Text
instance ToStrIso String TL.Text where

instance ToStrIso String String where

-- |
-- Used to find exceptions that violated "r-" encoding
-- Expected to be used to check encoding of ASCII-7 so Text and ByteString are compatible.
--
-- @since 0.3.0.0
class Char8Find str where
    find :: (Char -> Bool) -> str -> Maybe Char

instance Char8Find String where
    find = L.find

instance Char8Find T.Text where
    find = T.find

instance Char8Find TL.Text where
    find = TL.find

-- |
-- B8.pack is basically a convenient way to get Word8 elements into ByteString.
--
-- During B8.pack conversion charters are downsized to the 0-255 range (become a Word8).
-- The length is preserved. 
-- 
-- >>> B8.pack "\160582"
-- "F"
--
-- >>> B.length $ B8.pack "\160582"
-- 1
--
-- This instance allows to check elements of ByteString interpreting them as Char.
-- 
-- Safe if restricting to 7bit code points.
instance Char8Find B.ByteString where
    find = B8.find

instance Char8Find BL.ByteString where
    find = BL8.find