{-# LANGUAGE TypeFamilies, FlexibleContexts, TypeSynonymInstances, ExistentialQuantification, DeriveDataTypeable, FlexibleInstances, UndecidableInstances #-}

module Data.String.Class
    ( Stringy
    , StringCells(..)
    , StringCell(..)
    , StringRWIO(..)
    , ConvGenString(..)
    , ConvString(..)
    , ConvStrictByteString(..)
    , ConvLazyByteString(..)
    , ConvText(..)
    , GenString(..)
    , GenStringDefault
    ) where

import Prelude hiding (head, tail, last, init, take, drop, length, null, concat, putStr, getContents)
import Control.Applicative hiding (empty)
import qualified Data.ByteString as S
import qualified Data.ByteString.Char8 as SC
import qualified Data.ByteString.Internal as BI
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as LC
import Data.Int
import qualified Data.List as List
import Data.Monoid
import Data.String (IsString)
import qualified Data.String
import Data.Tagged
import qualified Data.Text as T
import qualified Data.Text.Encoding as TE
import qualified Data.Text.Encoding.Error as TEE
import qualified Data.Text.IO as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LTE
import qualified Data.Text.Lazy.IO as LT
import Data.Typeable
import Data.Word
import qualified System.IO as IO

-- | String super class
class    (StringCells s, StringRWIO s) => Stringy s
instance (StringCells s, StringRWIO s) => Stringy s

-- | Minimal complete definition: StringCellChar; StringCellAltChar; toStringCells; fromStringCells; toMainChar; toAltChar; cons; snoc; either all of head, tail, last, and init, or all of uncons and unsnoc; take, take64 or genericTake; drop, drop64, or genericDrop; and length, length64, or genericLength
class (Eq s, Monoid s, IsString s, Typeable s, StringCell (StringCellChar s), StringCell (StringCellAltChar s), ConvGenString s, ConvString s, ConvStrictByteString s, ConvLazyByteString s, ConvText s, ConvLazyText s) => StringCells s where
    type StringCellChar s
    type StringCellAltChar s

    toStringCells   :: (StringCells s2) => s  -> s2
    fromStringCells :: (StringCells s2) => s2 -> s

    infixr 9 `cons`
    infixr 9 `uncons`
    infixr 9 `altCons`
    infixr 9 `altUncons`
    cons      :: StringCellChar s -> s -> s
    uncons    :: s -> (StringCellChar s, s)
    snoc      :: s -> StringCellChar s -> s
    unsnoc    :: s -> (s, StringCellChar s)
    altCons   :: StringCellAltChar s -> s -> s
    altUncons :: s -> (StringCellAltChar s, s)
    altSnoc   :: s -> StringCellAltChar s -> s
    altUnsnoc :: s -> (s, StringCellAltChar s)

    toMainChar :: (StringCell c) => c -> Tagged s (StringCellChar s)
    toAltChar  :: (StringCell c) => c -> Tagged s (StringCellAltChar s)

    -- | Append two strings
    infixr 9 `append`
    append :: s -> s -> s
    concat :: [s] -> s

    empty :: s
    null :: s -> Bool

    head :: s -> StringCellChar s
    tail :: s -> s
    last :: s -> StringCellChar s
    init :: s -> s
    altHead :: s -> StringCellAltChar s
    altLast :: s -> StringCellAltChar s

    -- | Construction of a string; implementations should behave safely with incorrect lengths
    --
    -- The default implementation of 'unfoldr' is independent from that of 'altUnfoldr',
    -- as well as 'unfoldrN' as and 'altUnfoldrN'.
    unfoldr       ::          (a -> Maybe (StringCellChar    s, a)) -> a -> s
    altUnfoldr    ::          (a -> Maybe (StringCellAltChar s, a)) -> a -> s
    unfoldrN      :: Int   -> (a -> Maybe (StringCellChar    s, a)) -> a -> s
    altUnfoldrN   :: Int   -> (a -> Maybe (StringCellAltChar s, a)) -> a -> s
    unfoldrN64    :: Int64 -> (a -> Maybe (StringCellChar    s, a)) -> a -> s
    altUnfoldrN64 :: Int64 -> (a -> Maybe (StringCellAltChar s, a)) -> a -> s

    unfoldr f b =
        case f b of
            (Just (a, new_b)) -> a `cons` unfoldr f new_b
            (Nothing)         -> empty

    altUnfoldr f b =
        case f b of
            (Just (a, new_b)) -> a `altCons` altUnfoldr f new_b
            (Nothing)         -> empty
    unfoldrN    = const unfoldr
    altUnfoldrN = const altUnfoldr

    unfoldrN64 l f z = unfoldrN (fromIntegral l) f z

    altUnfoldrN64 l f z = altUnfoldrN (fromIntegral l) f z

    -- | Get the character at the given position
    --
    -- Just like 'drop', 'drop64', and the variants of those functions, the
    -- default definitions of these three variants are independent of each
    -- other, and are defined in terms of 'head' and 'tail', which can be
    -- inefficient.
    index   :: s -> Int   -> StringCellChar s
    index64 :: s -> Int64 -> StringCellChar s
    -- | Index a string at any location
    --
    -- Just like the other 'generic' functions of this module, this function
    -- can be significantly slower than 'index', since the function must be
    -- able to support arbitrarily large indices.  Consider using 'index' or
    -- 'index64', even if you need to coerce the index to an 'Int'.
    genericIndex :: (Integral i) => s -> i -> StringCellChar s

    take        :: Int -> s -> s
    take64      :: Int64 -> s -> s
    genericTake :: (Integral i) => i -> s -> s
    drop        :: Int -> s -> s
    drop64      :: Int64 -> s -> s
    genericDrop :: (Integral i) => i -> s -> s

    length        :: s -> Int
    length64      :: s -> Int64
    genericLength :: (Integral i) => s -> i

    safeUncons        :: s -> Maybe ((StringCellChar s), s)
    safeUnsnoc        :: s -> Maybe (s, (StringCellChar s))
    safeAltUncons     :: s -> Maybe ((StringCellAltChar s), s)
    safeAltUnsnoc     :: s -> Maybe (s, (StringCellAltChar s))
    safeHead          :: s -> Maybe (StringCellChar s)
    safeTail          :: s -> Maybe s
    safeLast          :: s -> Maybe (StringCellChar s)
    safeInit          :: s -> Maybe s
    safeAltHead       :: s -> Maybe (StringCellAltChar s)
    safeAltLast       :: s -> Maybe (StringCellAltChar s)
    safeIndex         :: s -> Int   -> Maybe (StringCellChar s)
    safeIndex64       :: s -> Int64 -> Maybe (StringCellChar s)
    safeGenericIndex  :: (Integral i) => s -> i -> Maybe (StringCellChar s)
    safeTake          :: Int -> s -> Maybe s
    safeTake64        :: Int64 -> s -> Maybe s
    safeGenericTake   :: (Integral i) => i -> s -> Maybe s
    safeDrop          :: Int -> s -> Maybe s
    safeDrop64        :: Int64 -> s -> Maybe s
    safeGenericDrop   :: (Integral i) => i -> s -> Maybe s
    safeUncons2       :: s -> Maybe ((StringCellChar s), (StringCellChar s), s)
    safeUncons3       :: s -> Maybe ((StringCellChar s), (StringCellChar s), (StringCellChar s), s)
    safeUncons4       :: s -> Maybe ((StringCellChar s), (StringCellChar s), (StringCellChar s), (StringCellChar s), s)

    infixr 9 `cons2`
    infixr 9 `cons3`
    infixr 9 `cons4`
    infixr 9 `uncons2`
    infixr 9 `uncons3`
    infixr 9 `uncons4`
    cons2   :: StringCellChar s -> StringCellChar s -> s -> s
    cons3   :: StringCellChar s -> StringCellChar s -> StringCellChar s -> s -> s
    cons4   :: StringCellChar s -> StringCellChar s -> StringCellChar s -> StringCellChar s -> s -> s
    uncons2 :: s -> (StringCellChar s, StringCellChar s, s)
    uncons3 :: s -> (StringCellChar s, StringCellChar s, StringCellChar s, s)
    uncons4 :: s -> (StringCellChar s, StringCellChar s, StringCellChar s, StringCellChar s, s)

    altCons c s = cons (s `untagTypeOf` toMainChar c) s
    altSnoc s c = snoc s (s `untagTypeOf` toMainChar c)
    altUncons s = (\ ~(a, s') -> (s `untagTypeOf` toAltChar a, s')) $ uncons s
    altUnsnoc s = (\ ~(s', a) -> (s', s `untagTypeOf` toAltChar a)) $ unsnoc s

    append = mappend
    concat = mconcat
    empty  = mempty
    null   = (== mempty)

    head = fst . uncons
    tail = snd . uncons
    last = snd . unsnoc
    init = fst . unsnoc
    altHead s = (s `untagTypeOf`) . toAltChar . head $ s
    altLast s = (s `untagTypeOf`) . toAltChar . last $ s

    index        s 0 = head s
    index        s n = (flip index $ pred n) . tail $ s
    index64      s 0 = head s
    index64      s n = (flip index64 $ pred n) . tail $ s
    genericIndex s 0 = head s
    genericIndex s n = (flip genericIndex $ pred n) . tail $ s

    take        n s = take64      (fromIntegral n) s
    take64      n s = genericTake (fromIntegral n  :: Integer) s
    genericTake n s = take        (fromIntegral n) s
    drop        n s = drop64      (fromIntegral n) s
    drop64      n s = genericDrop (fromIntegral n  :: Integer) s
    genericDrop n s = drop        (fromIntegral n) s

    length        = fromIntegral . length64
    length64      = (fromIntegral :: Integer -> Int64) . genericLength
    genericLength = fromIntegral . length

    {-
    -- More efficient default implementation provided above
    append a b = case safeUncons a of
        (Just (c, cs)) -> c `cons` append cs b
        (Nothing)      -> a

    concat = foldr append empty
    -}

    uncons s = (head s, tail s)
    unsnoc s = (init s, last s)

    cons2 a b s = a `cons` b `cons` s
    cons3 a b c s = a `cons` b `cons` c `cons` s
    cons4 a b c d s = a `cons` b `cons` c `cons` d `cons` s
    uncons2 s       =
        let (a, s')   = uncons s
            (b, s'')  = uncons s'
        in  (a, b, s'')
    uncons3 s       =
        let (a, s')   = uncons s
            (b, s'')  = uncons s'
            (c, s''') = uncons s''
        in  (a, b, c, s''')
    uncons4 s       =
        let (a, s')    = uncons s
            (b, s'')   = uncons s'
            (c, s''')  = uncons s''
            (d, s'''') = uncons s'''
        in  (a, b, c, d, s'''')

    safeUncons s
        | null s    = Nothing
        | otherwise = Just $ uncons s
    safeUnsnoc s
        | null s    = Nothing
        | otherwise = Just $ unsnoc s
    safeAltUncons s
        | null s    = Nothing
        | otherwise = Just $ altUncons s
    safeAltUnsnoc s
        | null s    = Nothing
        | otherwise = Just $ altUnsnoc s
    safeHead s
        | null s    = Nothing
        | otherwise = Just $ head s
    safeTail s
        | null s    = Nothing
        | otherwise = Just $ tail s
    safeLast s
        | null s    = Nothing
        | otherwise = Just $ last s
    safeInit s
        | null s    = Nothing
        | otherwise = Just $ init s
    safeAltHead s
        | null s    = Nothing
        | otherwise = Just $ altHead s
    safeAltLast s
        | null s    = Nothing
        | otherwise = Just $ altLast s
    safeIndex s n
        | length s <= n = Nothing
        | otherwise     = Just $ s `index` n
    safeIndex64 s n
        | length64 s <= n = Nothing
        | otherwise     = Just $ s `index64` n
    safeGenericIndex s n
        | genericLength s <= n = Nothing
        | otherwise            = Just $ s `genericIndex` n
    safeTake n s
        | n > length s = Nothing
        | otherwise    = Just $ take n s
    safeTake64 n s
        | n > length64 s = Nothing
        | otherwise      = Just $ take64 n s
    safeGenericTake n s
        | n > genericLength s = Nothing
        | otherwise           = Just $ genericTake n s
    safeDrop n s
        | n > length s = Nothing
        | otherwise    = Just $ drop n s
    safeDrop64 n s
        | n > length64 s = Nothing
        | otherwise      = Just $ drop64 n s
    safeGenericDrop n s
        | n > genericLength s = Nothing
        | otherwise           = Just $ genericDrop n s
    safeUncons2 s = do
        (a, s')    <- safeUncons s
        (b, s'')   <- safeUncons s'
        return (a, b, s'')
    safeUncons3 s = do
        (a, s')    <- safeUncons s
        (b, s'')   <- safeUncons s'
        (c, s''')  <- safeUncons s''
        return (a, b, c, s''')
    safeUncons4 s = do
        (a, s')    <- safeUncons s
        (b, s'')   <- safeUncons s'
        (c, s''')  <- safeUncons s''
        (d, s'''') <- safeUncons s'''
        return (a, b, c, d, s'''')

class StringCell c where
    toChar     :: c      -> Char
    toWord8    :: c      -> Word8
    toWord16   :: c      -> Word16
    toWord32   :: c      -> Word32
    toWord64   :: c      -> Word64
    fromChar   :: Char   -> c
    fromWord8  :: Word8  -> c
    fromWord16 :: Word16 -> c
    fromWord32 :: Word32 -> c
    fromWord64 :: Word64 -> c

class ConvGenString s where
    toGenString   :: s -> GenString
    fromGenString :: GenString -> s

class ConvString s where
    toString   :: s -> String
    fromString :: String -> s

class ConvStrictByteString s where
    toStrictByteString :: s -> S.ByteString
    fromStrictByteString :: S.ByteString -> s

class ConvLazyByteString s where
    toLazyByteString :: s -> L.ByteString
    fromLazyByteString :: L.ByteString -> s

class ConvText s where
    toText :: s -> T.Text
    fromText :: T.Text -> s

class ConvLazyText s where
    toLazyText :: s -> LT.Text
    fromLazyText :: LT.Text -> s

-- | Minimal complete definition: 'hGetContents', 'hGetLine', 'hPutStr', and 'hPutStrLn'
class StringRWIO s where
    --- Handles

    -- | Read n bytes *or* characters, depending on the implementation into a
    -- ByteString, directly from the specified Handle
    --
    -- Whether or not this function is lazy depends on the instance; laziness
    -- is preferred.
    hGetContents :: IO.Handle -> IO s

    -- | Read a single line from a handle
    hGetLine :: IO.Handle -> IO s

    -- | Write a string to a handle
    hPutStr :: IO.Handle -> s -> IO ()

    -- | Write a string to a handle, followed by a newline
    --
    -- N.B.: implementations might not define this atomically.  If the state
    -- of being atomic is necessary, one possible solution is to convert a
    -- string to an efficient type for which 'hPutStrLn' is atomic.
    hPutStrLn :: IO.Handle -> s -> IO ()

    --- Special cases for standard input and output

    -- | Take a function of type Text -> Text as its argument
    --
    -- The entire input from the standard input device is passed to this
    -- function as its argument, and the resulting string is output on the
    -- standard output device.
    interact :: (s -> s) -> IO ()
    interact f = putStr . f =<< getContents

    -- | Read all user input on 'stdin' as a single string
    getContents :: IO s
    getContents = hGetContents IO.stdin

    -- | Read a single line of user input from 'stdin'
    getLine :: IO s
    getLine = hGetLine IO.stdin

    -- | Write a string to 'stdout'
    putStr :: s -> IO ()
    putStr = hPutStr IO.stdout

    -- | Write a string to 'stdout', followed by a newline
    putStrLn :: s -> IO ()
    putStrLn = hPutStrLn IO.stdout

    ---

    -- | Read a file and returns the contents of the file as a string
    --
    -- Depending on the instance, this function might expect the file to be
    -- non-binary.  The default definition uses 'openFile' to open the file.
    readFile :: FilePath -> IO s
    readFile fn = hGetContents =<< IO.openFile fn IO.ReadMode

    -- | Write a string to a file
    --
    -- The file is truncated to zero length before writing begins.
    -- The default definition uses 'withFile' to open the file.
    writeFile :: FilePath -> s -> IO ()
    writeFile fn s = IO.withFile fn IO.WriteMode $ \hdl -> hPutStr hdl s

    -- | Write a string to the end of a file
    --
    -- The default definition uses 'withFile' to open the file.
    appendFile :: FilePath -> s -> IO ()
    appendFile fn s = IO.withFile fn IO.AppendMode $ \hdl -> hPutStr hdl s



instance StringCells String where
    type StringCellChar    String = Char
    type StringCellAltChar String = Char

    toStringCells   = fromString
    fromStringCells = toString

    length = List.genericLength
    empty  = []
    null   = List.null
    cons          = (:)
    snoc s c      = s ++ [c]
    safeUncons (x:xs) = Just (x, xs)
    safeUncons _      = Nothing
    uncons (x:xs) = (x, xs)
    uncons _      = error "String.uncons: null string"
    toMainChar    = Tagged . toChar
    toAltChar     = Tagged . toChar
    head          = List.head
    tail          = List.tail
    init          = List.init
    last          = List.last
    unfoldr       = List.unfoldr
    index         = (List.!!)
    index64 s     = index s . fromIntegral
    genericIndex  = List.genericIndex
    take          = List.take
    genericTake   = List.genericTake
    drop          = List.drop
    genericDrop   = List.genericDrop
    append        = (List.++)
    concat        = List.concat

instance StringCells S.ByteString where
    type StringCellChar    S.ByteString = Word8
    type StringCellAltChar S.ByteString = Char

    toStringCells   = fromStrictByteString
    fromStringCells = toStrictByteString

    length          = S.length
    empty           = S.empty
    null            = S.null
    cons            = S.cons
    snoc            = S.snoc
    safeUncons      = S.uncons
    uncons          = maybe (error "StringCells.Data.ByteString.ByteString.uncons: string is null") id . safeUncons
    toMainChar      = Tagged . toWord8
    toAltChar       = Tagged . toChar
    head            = S.head
    tail            = S.tail
    init            = S.init
    last            = S.last
    unfoldr         = S.unfoldr
    altUnfoldr      = SC.unfoldr
    unfoldrN        = ((fst .) .) . S.unfoldrN
    altUnfoldrN     = ((fst .) .) . SC.unfoldrN
    index           = S.index
    index64 s       = index s . fromIntegral
    take            = S.take
    drop            = S.drop
    append          = S.append
    concat          = S.concat

instance StringCells L.ByteString where
    type StringCellChar    L.ByteString = Word8
    type StringCellAltChar L.ByteString = Char

    toStringCells   = fromLazyByteString
    fromStringCells = toLazyByteString

    length64        = L.length
    length          = fromIntegral . length64
    empty           = L.empty
    null            = L.null
    cons            = L.cons
    snoc            = L.snoc
    safeUncons      = L.uncons
    uncons          = maybe (error "StringCells.Data.ByteString.Lazy.ByteString.uncons: string is null") id . safeUncons
    toMainChar      = Tagged . toWord8
    toAltChar       = Tagged . toChar
    head            = L.head
    tail            = L.tail
    init            = L.init
    last            = L.last
    unfoldr         = L.unfoldr
    altUnfoldr      = LC.unfoldr
    index s         = index64 s . fromIntegral
    index64         = L.index
    take64          = L.take
    drop64          = L.drop
    append          = L.append
    concat          = L.concat

instance StringCells T.Text where
    type StringCellChar    T.Text = Char
    type StringCellAltChar T.Text = Char

    toStringCells   = fromText
    fromStringCells = toText

    length          = T.length
    empty           = T.empty
    null            = T.null
    cons            = T.cons
    safeUncons      = T.uncons
    uncons          = maybe (error "StringCells.Data.Text.Text.uncons: string is null") id . safeUncons
    snoc            = T.snoc
    altSnoc         = T.snoc
    toMainChar      = Tagged . toChar
    toAltChar       = Tagged . toChar
    head            = T.head
    tail            = T.tail
    init            = T.init
    last            = T.last
    unfoldr         = T.unfoldr
    altUnfoldr      = T.unfoldr
    unfoldrN        = T.unfoldrN
    altUnfoldrN     = T.unfoldrN
    index           = T.index
    index64 s       = index s . fromIntegral
    append          = T.append
    concat          = T.concat

instance StringCells LT.Text where
    type StringCellChar    LT.Text = Char
    type StringCellAltChar LT.Text = Char

    toStringCells   = fromLazyText
    fromStringCells = toLazyText

    length64        = LT.length
    empty           = LT.empty
    null            = LT.null
    cons            = LT.cons
    safeUncons      = LT.uncons
    uncons          = maybe (error "StringCells.Data.Text.Lazy.Text.uncons: string is null") id . safeUncons
    snoc            = LT.snoc
    altSnoc         = LT.snoc
    toMainChar      = Tagged . toChar
    toAltChar       = Tagged . toChar
    head            = LT.head
    tail            = LT.tail
    init            = LT.init
    last            = LT.last
    unfoldr         = LT.unfoldr
    altUnfoldr      = LT.unfoldr
    unfoldrN64      = LT.unfoldrN
    altUnfoldrN64   = LT.unfoldrN
    index s         = index64 s . fromIntegral
    index64         = LT.index
    append          = LT.append
    concat          = LT.concat

instance StringCell Char where
    toChar     = id
    toWord8    = BI.c2w
    toWord16   = fromIntegral . toWord8
    toWord32   = fromIntegral . toWord8
    toWord64   = fromIntegral . toWord8
    fromChar   = id
    fromWord8  = BI.w2c
    fromWord16 = BI.w2c . fromIntegral
    fromWord32 = BI.w2c . fromIntegral
    fromWord64 = BI.w2c . fromIntegral

instance StringCell Word8 where
    toChar     = BI.w2c
    toWord8    = id
    toWord16   = fromIntegral
    toWord32   = fromIntegral
    toWord64   = fromIntegral
    fromChar   = BI.c2w
    fromWord8  = id
    fromWord16 = fromIntegral
    fromWord32 = fromIntegral
    fromWord64 = fromIntegral

instance StringCell Word16 where
    toChar     = BI.w2c . fromIntegral
    toWord8    = fromIntegral
    toWord16   = id
    toWord32   = fromIntegral
    toWord64   = fromIntegral
    fromChar   = fromIntegral . BI.c2w
    fromWord8  = fromIntegral
    fromWord16 = id
    fromWord32 = fromIntegral
    fromWord64 = fromIntegral

instance StringCell Word32 where
    toChar     = BI.w2c . fromIntegral
    toWord8    = fromIntegral
    toWord16   = fromIntegral
    toWord32   = id
    toWord64   = fromIntegral
    fromChar   = fromIntegral . BI.c2w
    fromWord8  = fromIntegral
    fromWord16 = fromIntegral
    fromWord32 = id
    fromWord64 = fromIntegral

instance StringCell Word64 where
    toChar     = BI.w2c . fromIntegral
    toWord8    = fromIntegral
    toWord16   = fromIntegral
    toWord32   = fromIntegral
    toWord64   = id
    fromChar   = fromIntegral . BI.c2w
    fromWord8  = fromIntegral
    fromWord16 = fromIntegral
    fromWord32 = fromIntegral
    fromWord64 = id

instance ConvGenString GenString where
    toGenString   = id
    fromGenString = id

instance ConvGenString String where
    toGenString      = GenString
    fromGenString _s = case _s of
        (GenString _s) -> toStringCells _s

instance ConvGenString SC.ByteString where
    toGenString      = GenString
    fromGenString _s = case _s of
        (GenString _s) -> toStringCells _s

instance ConvGenString LC.ByteString where
    toGenString      = GenString
    fromGenString _s = case _s of
        (GenString _s) -> toStringCells _s

instance ConvGenString T.Text where
    toGenString      = GenString
    fromGenString _s = case _s of
        (GenString _s) -> toStringCells _s

instance ConvGenString LT.Text where
    toGenString      = GenString
    fromGenString _s = case _s of
        (GenString _s) -> toStringCells _s

instance ConvString GenString where
    toString   = fromGenString
    fromString = toGenString

instance ConvString String where
    toString   = id
    fromString = id

instance ConvString SC.ByteString where
    toString   = SC.unpack
    fromString = SC.pack

instance ConvString LC.ByteString where
    toString   = LC.unpack
    fromString = LC.pack

instance ConvString T.Text where
    toString   = T.unpack
    fromString = T.pack

instance ConvString LT.Text where
    toString   = LT.unpack
    fromString = LT.pack

instance ConvStrictByteString GenString where
    toStrictByteString   = fromGenString
    fromStrictByteString = toGenString

instance ConvStrictByteString String where
    toStrictByteString   = SC.pack
    fromStrictByteString = SC.unpack

instance ConvStrictByteString S.ByteString where
    toStrictByteString   = id
    fromStrictByteString = id

instance ConvStrictByteString L.ByteString where
    toStrictByteString   = L.toStrict
    fromStrictByteString = toLazyByteString

instance ConvStrictByteString T.Text where
    toStrictByteString   = TE.encodeUtf8
    fromStrictByteString = toText

instance ConvStrictByteString LT.Text where
    toStrictByteString   = toStrictByteString . LTE.encodeUtf8
    fromStrictByteString = toLazyText

instance ConvLazyByteString GenString where
    toLazyByteString   = fromGenString
    fromLazyByteString = toGenString

instance ConvLazyByteString String where
    toLazyByteString   = LC.pack
    fromLazyByteString = LC.unpack

instance ConvLazyByteString S.ByteString where
    toLazyByteString   = L.fromStrict
    fromLazyByteString = toStrictByteString

instance ConvLazyByteString L.ByteString where
    toLazyByteString   = id
    fromLazyByteString = id

instance ConvLazyByteString T.Text where
    toLazyByteString   = toLazyByteString . toStrictByteString
    fromLazyByteString = toText

instance ConvLazyByteString LT.Text where
    toLazyByteString   = toLazyByteString . toStrictByteString
    fromLazyByteString = toLazyText

instance ConvText GenString where
    toText   = fromGenString
    fromText = toGenString

instance ConvText String where
    toText   = T.pack
    fromText = T.unpack

instance ConvText S.ByteString where
    toText   = TE.decodeUtf8With TEE.lenientDecode
    fromText = toStrictByteString

instance ConvText L.ByteString where
    toText   = toText . toStrictByteString
    fromText = toLazyByteString

instance ConvText T.Text where
    toText   = id
    fromText = id

instance ConvText LT.Text where
    toText   = LT.toStrict
    fromText = toLazyText

instance ConvLazyText GenString where
    toLazyText   = fromGenString
    fromLazyText = toGenString

instance ConvLazyText String where
    toLazyText   = LT.pack
    fromLazyText = LT.unpack

instance ConvLazyText S.ByteString where
    toLazyText   = LTE.decodeUtf8With TEE.lenientDecode . toLazyByteString
    fromLazyText = toStrictByteString

instance ConvLazyText L.ByteString where
    toLazyText   = LTE.decodeUtf8With TEE.lenientDecode
    fromLazyText = toLazyByteString

instance ConvLazyText T.Text where
    toLazyText   = LT.fromStrict
    fromLazyText = fromLazyText

instance ConvLazyText LT.Text where
    toLazyText   = id
    fromLazyText = id

-- |
--
-- This is minimally defined with 'GenStringDefault'.
instance StringRWIO GenString where
    hGetContents h = genStringFromConConv <$> hGetContents h

    hGetLine h = genStringFromConConv <$> hGetLine h

    hPutStr h s = hPutStr h (genStringConConv s)

    hPutStrLn h s = hPutStrLn h (genStringConConv s)

-- | Type-restricted string conversion used by 'GenString's instance definition for 'StringRWIO'
genStringConConv :: GenString -> GenStringDefault
genStringConConv = toStringCells

-- | Type-restricted string conversion used by 'GenString's instance definition for 'StringRWIO'
genStringFromConConv :: GenStringDefault -> GenString
genStringFromConConv = toStringCells

-- |
--
-- See 'System.IO for documentation of behaviour.
instance StringRWIO String where
    hGetContents = IO.hGetContents

    hGetLine     = IO.hGetLine

    hPutStr      = IO.hPutStr

    hPutStrLn    = IO.hPutStrLn

    interact     = IO.interact

    getContents  = IO.getContents

    getLine      = IO.getLine

    putStr       = IO.putStr

    putStrLn     = IO.putStrLn

    readFile     = IO.readFile

    writeFile    = IO.writeFile

    appendFile   = IO.appendFile

-- |
--
-- See 'Data.ByteString' for documentation of behaviour.
instance StringRWIO S.ByteString where
    hGetContents = S.hGetContents

    hGetLine     = S.hGetLine

    hPutStr      = S.hPutStr

    hPutStrLn    = SC.hPutStrLn

    interact     = S.interact

    getContents  = S.getContents

    getLine      = S.getLine

    putStr       = S.putStr

    putStrLn     = SC.putStrLn

    readFile     = S.readFile

    writeFile    = S.writeFile

    appendFile   = S.appendFile

-- |
--
-- See 'Data.ByteString.Lazy' for documentation of behaviour.
--
-- 'hGetLine' and 'getLine' are defined in terms of 'toStringCells' and the equivalent methods of 'Data.ByteString'.
-- 'hPutStrLn' is defined non-atomically: it is defined as an action that puts the string and then separately puts a newline character string.
instance StringRWIO L.ByteString where
    hGetContents = L.hGetContents

    hGetLine     = (toStringCells <$>) . S.hGetLine

    hPutStr      = L.hPutStr

    hPutStrLn h  = (>> hPutStr h ((toStringCells :: String -> L.ByteString) ['\n'])) . hPutStr h

    interact     = L.interact

    getContents  = L.getContents

    getLine      = toStringCells <$> S.getLine

    putStr       = L.putStr

    putStrLn     = LC.putStrLn

    readFile     = L.readFile

    writeFile    = L.writeFile

    appendFile   = L.appendFile

-- |
--
-- See 'Data.Text.IO' for documentation of behaviour.
instance StringRWIO T.Text where
    hGetContents = T.hGetContents

    hGetLine     = T.hGetLine

    hPutStr      = T.hPutStr

    hPutStrLn    = T.hPutStrLn

    interact     = T.interact

    getContents  = T.getContents

    getLine      = T.getLine

    putStr       = T.putStr

    putStrLn     = T.putStrLn

    readFile     = T.readFile

    writeFile    = T.writeFile

    appendFile   = T.appendFile

-- |
--
-- See 'Data.Text.Lazy.IO' for documentation of behaviour.
instance StringRWIO LT.Text where
    hGetContents = LT.hGetContents

    hGetLine     = LT.hGetLine

    hPutStr      = LT.hPutStr

    hPutStrLn    = LT.hPutStrLn

    interact     = LT.interact

    getContents  = LT.getContents

    getLine      = LT.getLine

    putStr       = LT.putStr

    putStrLn     = LT.putStrLn

    readFile     = LT.readFile

    writeFile    = LT.writeFile

    appendFile   = LT.appendFile

-- | Polymorphic container of a string
--
-- When operations take place on multiple 'GenString's, they are first
-- converted to the type 'GenStringDefault', which are lazy bytestrings,
-- whenever absolutely necessary (which includes testing for equality,
-- appending strings, concatenating lists of strings, empty strings with
-- 'empty', and unfolding), making them the most efficient type for this
-- polymorphic container.
data GenString = forall s. (Stringy s) => GenString {gen_string :: s}
    deriving (Typeable)

toGenDefaultString :: (Stringy s) => s -> GenStringDefault
toGenDefaultString = toStringCells

instance Eq GenString where
    _a == _b = case (_a, _b) of
        ((GenString _a), (GenString _b)) -> toGenDefaultString _a == toGenDefaultString _b
    _a /= _b = case (_a, _b) of
        ((GenString _a), (GenString _b)) -> toGenDefaultString _a /= toGenDefaultString _b

instance IsString GenString where
    fromString = GenString

instance Monoid GenString where
    mempty  = GenString $ (empty :: GenStringDefault)
    mappend a b = case (a, b) of
        (GenString _a, GenString _b) -> GenString $ append (toGenDefaultString _a) (toGenDefaultString _b)
    mconcat ss = GenString $ concat . map toGenDefaultString $ ss

instance StringCells GenString where
    -- These associated types were rather arbitrarily chosen
    type StringCellChar GenString = Char
    type StringCellAltChar GenString = Word8

    toStringCells   = fromGenString
    fromStringCells = toGenString

    cons c _s = case _s of
        (GenString _s) -> GenString $ cons (_s `untagTypeOf` toMainChar c) _s
    uncons _s = case _s of
        (GenString _s) -> let (c, s') = uncons _s
                          in  (genStringPhantom `untagTypeOf` toMainChar c, GenString s')
    snoc _s c = case _s of
        (GenString _s) -> GenString $ snoc _s (_s `untagTypeOf` toMainChar c)
    unsnoc _s = case _s of
        (GenString _s) -> let (s', c) = unsnoc _s
                          in  (GenString s', genStringPhantom `untagTypeOf` toMainChar c)

    altCons c _s = case _s of
        (GenString _s) -> GenString $ cons (fromWord8 c) _s
    altUncons _s = case _s of
        (GenString _s) -> let (c, s') = uncons _s
                          in  (genStringPhantom `untagTypeOf` toAltChar c, GenString s')
    altSnoc _s c = case _s of
        (GenString _s) -> GenString $ snoc _s (fromWord8 c)
    altUnsnoc _s = case _s of
        (GenString _s) -> let (s', c) = unsnoc _s
                          in  (GenString s', genStringPhantom `untagTypeOf` toAltChar c)

    toMainChar = Tagged . toChar
    toAltChar  = Tagged . toWord8

    null _s = case _s of
        (GenString _s) -> null _s

    head _s = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toMainChar (head _s)
    tail _s = case _s of
        (GenString _s) -> GenString $ tail _s
    last _s = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toMainChar (last _s)
    init _s = case _s of
        (GenString _s) -> GenString $ init _s
    altHead _s = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toAltChar (head _s)
    altLast _s = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toAltChar (last _s)

    unfoldr       f z = GenString $ (altUnfoldr    f z  :: GenStringDefault)
    altUnfoldr    f z = GenString $ (unfoldr       f z  :: GenStringDefault)
    unfoldrN    n f z = GenString $ (altUnfoldrN n f z  :: GenStringDefault)
    altUnfoldrN n f z = GenString $ (unfoldrN    n f z  :: GenStringDefault)

    index _s i = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toMainChar (index _s i)
    index64 _s i = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toMainChar (index64 _s i)
    genericIndex _s i = case _s of
        (GenString _s) -> genStringPhantom `untagTypeOf` toMainChar (genericIndex _s i)

    take n _s = case _s of
        (GenString _s) -> GenString $ take n _s
    take64 n _s = case _s of
        (GenString _s) -> GenString $ take64 n _s
    genericTake n _s = case _s of
        (GenString _s) -> GenString $ genericTake n _s
    drop n _s = case _s of
        (GenString _s) -> GenString $ drop n _s
    drop64 n _s = case _s of
        (GenString _s) -> GenString $ drop64 n _s
    genericDrop n _s = case _s of
        (GenString _s) -> GenString $ genericDrop n _s

    length _s = case _s of
        (GenString _s) -> length _s
    length64 _s = case _s of
        (GenString _s) -> length64 _s
    genericLength _s = case _s of
        (GenString _s) -> genericLength _s

    safeUncons _s = case _s of
        (GenString _s) -> (\(c, s') -> (genStringPhantom `untagTypeOf` toMainChar c, GenString s')) <$> safeUncons _s
    safeUnsnoc _s = case _s of
        (GenString _s) -> (\(s', c) -> (GenString s', genStringPhantom `untagTypeOf` toMainChar c)) <$> safeUnsnoc _s
    safeAltUncons _s = case _s of
        (GenString _s) -> (\(c, s') -> (genStringPhantom `untagTypeOf` toAltChar c, GenString s')) <$> safeAltUncons _s
    safeAltUnsnoc _s = case _s of
        (GenString _s) -> (\(s', c) -> (GenString s', genStringPhantom `untagTypeOf` toAltChar c)) <$> safeAltUnsnoc _s
    safeHead _s = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toMainChar <$> safeHead _s
    safeTail _s = case _s of
        (GenString _s) -> GenString <$> safeTail _s
    safeLast _s = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toMainChar <$> safeLast _s
    safeInit _s = case _s of
        (GenString _s) -> GenString <$> safeInit _s
    safeAltHead _s = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toAltChar  <$> safeAltHead _s
    safeAltLast _s = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toAltChar  <$> safeAltLast _s
    safeIndex _s i = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toMainChar <$> safeIndex _s i
    safeIndex64 _s i = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toMainChar <$> safeIndex64 _s i
    safeGenericIndex _s i = case _s of
        (GenString _s) -> (genStringPhantom `untagTypeOf`) . toMainChar <$> safeGenericIndex _s i
    safeTake n _s = case _s of
        (GenString _s) -> GenString <$> safeTake n _s
    safeTake64 n _s = case _s of
        (GenString _s) -> GenString <$> safeTake64 n _s
    safeGenericTake n _s = case _s of
        (GenString _s) -> GenString <$> safeGenericTake n _s
    safeDrop n _s = case _s of
        (GenString _s) -> GenString <$> safeDrop n _s
    safeDrop64 n _s = case _s of
        (GenString _s) -> GenString <$> safeDrop64 n _s
    safeGenericDrop n _s = case _s of
        (GenString _s) -> GenString <$> safeGenericDrop n _s
    safeUncons2 _s = case _s of
        (GenString _s) -> (\(a, b, s') -> (genStringPhantom `untagTypeOf` toMainChar a, genStringPhantom `untagTypeOf` toMainChar b, GenString s')) <$> safeUncons2 _s
    safeUncons3 _s = case _s of
        (GenString _s) -> (\(a, b, c, s') -> (genStringPhantom `untagTypeOf` toMainChar a, genStringPhantom `untagTypeOf` toMainChar b, genStringPhantom `untagTypeOf` toMainChar c, GenString s')) <$> safeUncons3 _s
    safeUncons4 _s = case _s of
        (GenString _s) -> (\(a, b, c, d, s') -> (genStringPhantom `untagTypeOf` toMainChar a, genStringPhantom `untagTypeOf` toMainChar b, genStringPhantom `untagTypeOf` toMainChar c, genStringPhantom `untagTypeOf` toMainChar d, GenString s')) <$> safeUncons4 _s

    cons2 a b _s = case _s of
        (GenString _s) -> GenString $ cons2 (_s `untagTypeOf` toMainChar a) (_s `untagTypeOf` toMainChar b) _s
    cons3 a b c _s = case _s of
        (GenString _s) -> GenString $ cons3 (_s `untagTypeOf` toMainChar a) (_s `untagTypeOf` toMainChar b) (_s `untagTypeOf` toMainChar c) _s
    cons4 a b c d _s = case _s of
        (GenString _s) -> GenString $ cons4 (_s `untagTypeOf` toMainChar a) (_s `untagTypeOf` toMainChar b) (_s `untagTypeOf` toMainChar c) (_s `untagTypeOf` toMainChar d) _s
    uncons2 _s = case _s of
        (GenString _s) -> let (a, b, s') = uncons2 _s
                          in  (genStringPhantom `untagTypeOf` toMainChar a, genStringPhantom `untagTypeOf` toMainChar b, GenString s')
    uncons3 _s = case _s of
        (GenString _s) -> let (a, b, c, s') = uncons3 _s
                          in  (genStringPhantom `untagTypeOf` toMainChar a, genStringPhantom `untagTypeOf` toMainChar b, genStringPhantom `untagTypeOf` toMainChar c, GenString s')
    uncons4 _s = case _s of
        (GenString _s) -> let (a, b, c, d, s') = uncons4 _s
                          in  (genStringPhantom `untagTypeOf` toMainChar a, genStringPhantom `untagTypeOf` toMainChar b, genStringPhantom `untagTypeOf` toMainChar c, genStringPhantom `untagTypeOf` toMainChar d, GenString s')

-- | Untag a type with a type restriction
--
-- The first argument is guaranteed to be ignored; thus the value 'undefined'
-- can be passed in its place.
untagTypeOf :: s -> Tagged s b -> b
untagTypeOf _ = untag

-- | Phantom, undefined value only used for convenience
--
-- Users should be careful that this value is never evaluated when using this.
genStringPhantom :: GenString
genStringPhantom = undefined

-- | This type is used by 'GenString' when a concrete string type is needed
type GenStringDefault = L.ByteString
