{-# LANGUAGE ExplicitForAll        #-}
{-# LANGUAGE FlexibleInstances     #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE TypeSynonymInstances  #-}
{-# LANGUAGE UndecidableInstances  #-}

{-| Assuming UTF8 encoding the following types are isomorphic

  - Data.ByteString.Lazy.ByteString
  - Data.ByteString.ByteString
  - Data.Text.Lazy.Text
  - Data.Text.Text
  - String

Yet working with them and converting is a pain!

This package exposes this 5 way isomorphism to make conversion easy.
-}
module Text.Isomorphic
  ( module Data.Types.Injective
  , module Data.Types.Isomorphic
  , as, as2, as3, as4 ) where

import qualified Data.ByteString            as BS
import qualified Data.ByteString.Char8      as BSC
import qualified Data.ByteString.Lazy       as BL
import qualified Data.ByteString.Lazy.Char8 as BLC
import qualified Data.Char                  as C
import qualified Data.String                as S
import qualified Data.Text                  as TS
import qualified Data.Text.Encoding         as TSE
import qualified Data.Text.Lazy             as TL
import qualified Data.Text.Lazy.Encoding    as TLE
import           Data.Types.Injective
import           Data.Types.Isomorphic

instance {-# OVERLAPPABLE #-} S.IsString s => Injective String s where to = S.fromString


instance Injective BS.ByteString String where
  to = BSC.unpack
  {-# INLINE to #-}
instance Injective String BS.ByteString where
  to = BSC.pack
  {-# INLINE to #-}
instance Iso String BS.ByteString
instance Iso BS.ByteString String

instance Injective BL.ByteString String where
  to = BLC.unpack
  {-# INLINE to #-}
instance Injective String BL.ByteString where
  to = BLC.pack
  {-# INLINE to #-}
instance Iso String BL.ByteString
instance Iso BL.ByteString String

instance Injective TS.Text BS.ByteString where
  to = TSE.encodeUtf8
  {-# INLINE to #-}
instance Injective BS.ByteString TS.Text where
  to = TSE.decodeUtf8
  {-# INLINE to #-}
instance Iso TS.Text BS.ByteString
instance Iso BS.ByteString TS.Text

instance Injective TS.Text BL.ByteString where
  to = BL.fromStrict . to
  {-# INLINE to #-}
instance Injective BL.ByteString TS.Text where
  to = to . BL.toStrict
  {-# INLINE to #-}
instance Iso TS.Text BL.ByteString
instance Iso BL.ByteString TS.Text

instance Injective TL.Text BS.ByteString where
  to = BL.toStrict . to
  {-# INLINE to #-}
instance Injective BS.ByteString TL.Text where
  to = to . BL.fromStrict
  {-# INLINE to #-}
instance Iso TL.Text BS.ByteString
instance Iso BS.ByteString TL.Text

instance Injective TL.Text BL.ByteString where
  to = TLE.encodeUtf8
  {-# INLINE to #-}
instance Injective BL.ByteString TL.Text where
  to = TLE.decodeUtf8
  {-# INLINE to #-}
instance Iso TL.Text BL.ByteString
instance Iso BL.ByteString TL.Text

instance Injective BS.ByteString BL.ByteString where
  to = BL.fromStrict
  {-# INLINE to #-}
instance Injective BL.ByteString BS.ByteString where
  to = BL.toStrict
  {-# INLINE to #-}
instance Iso BS.ByteString BL.ByteString
instance Iso BL.ByteString BS.ByteString

{-| This is useful for any iso, but in the case of string like stuff.
    You can now work with a function from one string type, while not having to do
    any conversion.

@
  f :: ByteString -> ByteString
  a :: Text
  as f a :: Text
@
-}
as :: forall a b. (Iso a b, Iso b a) => (a -> a) -> b -> b
as f = from . f . to
{-# INLINABLE as #-}

{-| Like @liftA2@ -}
as2 :: forall a b. (Iso a b, Iso b a) => (a -> a -> a) -> b -> b -> b
as2 f x y = from $ f (to x) (to y)

{-| Like @liftA3@ -}
as3 :: forall a b. (Iso a b, Iso b a) => (a -> a -> a -> a) -> b -> b -> b -> b
as3 f x y z = from $ f (to x) (to y) (to z)

{-| Like @liftA4@ -}
as4 :: forall a b. (Iso a b, Iso b a) => (a -> a -> a -> a -> a) -> b -> b -> b -> b -> b
as4 f w x y z = from $ f (to w) (to x) (to y) (to z)