{-# LANGUAGE AllowAmbiguousTypes #-}

module Binrep.Type.Text.Internal where

import Data.Text ( Text )
import Data.ByteString qualified as B
import Refined
import Refined.Unsafe ( reallyUnsafeRefine )

import System.IO.Unsafe qualified
import Control.Exception qualified
import Data.Text.Encoding.Error qualified
import Data.Bifunctor ( bimap )

type Bytes = B.ByteString

-- | A string of a given encoding, stored in the 'Text' type.
--
-- Essentially 'Text' carrying a proof that it can be successfully encoded into
-- the given encoding. For example, @'AsText' 'ASCII'@ means the 'Text' stored
-- is pure ASCII.
type AsText enc = Refined enc Text

-- | Bytestring encoders for text validated for a given encoding.
class Encode enc where
    -- | Encode text to bytes. Internal function, use 'encode'.
    encode' :: Text -> Bytes

class Decode enc where
    -- | Decode a 'ByteString' to 'Text' with an explicit encoding.
    --
    -- This is intended to be used with visible type applications.
    decode :: Bytes -> Either String (AsText enc)

--------------------------------------------------------------------------------
-- Internal helpers

-- | Helper for decoding a 'Bytes' to a 'Text' tagged with its encoding.
decodeText
    :: forall enc e
    .  (e -> String) -> (Bytes -> Either e Text) -> Bytes
    -> Either String (AsText enc)
decodeText :: forall {k} (enc :: k) e.
(e -> String)
-> (Bytes -> Either e Text) -> Bytes -> Either String (AsText enc)
decodeText e -> String
g Bytes -> Either e Text
f = (e -> String)
-> (Text -> AsText enc)
-> Either e Text
-> Either String (AsText enc)
forall a b c d. (a -> b) -> (c -> d) -> Either a c -> Either b d
forall (p :: Type -> Type -> Type) a b c d.
Bifunctor p =>
(a -> b) -> (c -> d) -> p a c -> p b d
bimap e -> String
g Text -> AsText enc
forall {k} x (p :: k). x -> Refined p x
reallyUnsafeRefine (Either e Text -> Either String (AsText enc))
-> (Bytes -> Either e Text) -> Bytes -> Either String (AsText enc)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bytes -> Either e Text
f

-- | Run an unsafe decoder safely.
--
-- Copied from @Data.Text.Encoding.decodeUtf8'@, so should be bulletproof?
wrapUnsafeDecoder
    :: (Bytes -> Text)
    -> Bytes -> Either Data.Text.Encoding.Error.UnicodeException Text
wrapUnsafeDecoder :: (Bytes -> Text) -> Bytes -> Either UnicodeException Text
wrapUnsafeDecoder Bytes -> Text
f =
      IO (Either UnicodeException Text) -> Either UnicodeException Text
forall a. IO a -> a
System.IO.Unsafe.unsafeDupablePerformIO
    (IO (Either UnicodeException Text) -> Either UnicodeException Text)
-> (Bytes -> IO (Either UnicodeException Text))
-> Bytes
-> Either UnicodeException Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. IO Text -> IO (Either UnicodeException Text)
forall e a. Exception e => IO a -> IO (Either e a)
Control.Exception.try
    (IO Text -> IO (Either UnicodeException Text))
-> (Bytes -> IO Text) -> Bytes -> IO (Either UnicodeException Text)
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Text -> IO Text
forall a. a -> IO a
Control.Exception.evaluate
    (Text -> IO Text) -> (Bytes -> Text) -> Bytes -> IO Text
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Bytes -> Text
f