{-# LANGUAGE TypeSynonymInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE CPP #-}

-- This module provides two type classes: 'StringLike' and 'ToString' and
-- helper functions.
--
-- Type class 'StringLike' used for defining any string like
-- type that can be obtained via lazy 'LT.Text'. There are default
-- implementations for lazy 'LT.Text', strict 'ST.Text', lazy 'LB.ByteString'
-- and strict 'SB.ByteString'.
--
-- Type class 'ToString' used for defining a way to convert any type to
-- lazy 'LT.Text'.
--
-- For example:
--
-- > {-# LANGUAGE OverloadedStrings #-}
-- >
-- > module Main where
-- >
-- > import Data.ByteString.Lazy
-- >
-- > data Foo = Bar | Baz
-- >
-- > instance ToString Foo where
-- >     toText Bar = "bar"
-- >     toText Baz = "baz"
-- >
-- > test :: ByteString -> ()
-- > test = const ()
-- >
-- > main :: IO ()
-- > main = do
-- >     test $ string Bar
-- >     test $ lbs Baz
--

module Data.String.Like
    ( StringLike(..)
    , ToString(..)
    , string
    , text, ltext
    , bs, lbs
    , readFile
    , writeFile
    , appendFile
    ) where

import Data.Int (Int8, Int16, Int32, Int64)
import Data.Word (Word, Word8, Word16, Word32, Word64)
import qualified Data.ByteString as SB
import qualified Data.ByteString.Lazy as LB

import Data.Text.Lazy.Builder (toLazyText)
import Data.Text.Lazy.Builder.Int (decimal)
import Data.Text.Lazy.Builder.RealFloat (realFloat)
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT
import qualified Data.Text as ST
import qualified Data.Text.Encoding as ST

-------------------------------------------------------------------------------
-- * Type classes

-- | This type class can be used to transform any string like from
-- lazy 'LT.Text', there is no default implementation for 'String' consciously,
-- beacause we don't want to incite 'String' using.
class StringLike a where
    fromLazyText :: LT.Text -> a

-- | This type class can be used to transform any type to 'StringLike' type.
-- Minimal complete definition: 'toText'.
class ToString a where
    toText :: a -> LT.Text

-------------------------------------------------------------------------------
-- * Utilities

-- | Transform any 'ToString' type to strict 'ST.Text'
text :: ToString a => a -> ST.Text
text = string

-- | Transform any 'ToString' type to lazy 'LT.Text'
ltext :: ToString a => a -> LT.Text
ltext = string

-- | Transform any 'ToString' type to strict 'SB.ByteString'
bs :: ToString a => a -> SB.ByteString
bs = string

-- | Transform any 'ToString' type to lazy 'LB.ByteString'
lbs :: ToString a => a -> LB.ByteString
lbs = string

-- | Transform any 'ToString' type to any 'StringLike' type, it can be inferred
-- or should be explicitly defined.
string  :: (ToString a, StringLike b) => a -> b
string = fromLazyText . toText

-------------------------------------------------------------------------------
-- * Instances

instance StringLike ST.Text where
    fromLazyText = LT.toStrict

instance StringLike LT.Text where
    fromLazyText = id

instance StringLike LB.ByteString where
    fromLazyText = LT.encodeUtf8

instance StringLike SB.ByteString where
    fromLazyText = lazyByteStringToStrict . fromLazyText

instance ToString Int where
    toText = toLazyText . decimal

instance ToString Int8 where
    toText = toLazyText . decimal

instance ToString Int16 where
    toText = toLazyText . decimal

instance ToString Int32 where
    toText = toLazyText . decimal

instance ToString Int64 where
    toText = toLazyText . decimal

instance ToString Word where
    toText = toLazyText . decimal

instance ToString Word8 where
    toText = toLazyText . decimal

instance ToString Word16 where
    toText = toLazyText . decimal

instance ToString Word32 where
    toText = toLazyText . decimal

instance ToString Word64 where
    toText = toLazyText . decimal

instance ToString Integer where
    toText = toLazyText . decimal

instance ToString Double where
    toText = toLazyText . realFloat

instance ToString Float where
    toText = toLazyText . realFloat

instance ToString String where
    toText = LT.pack

instance ToString LT.Text where
    toText = id

instance ToString ST.Text where
    toText = LT.fromStrict

instance ToString SB.ByteString where
    toText = LT.fromStrict . ST.decodeUtf8

instance ToString LB.ByteString where
    toText = LT.decodeUtf8

-------------------------------------------------------------------------------
-- * Utils

lazyByteStringToStrict :: LB.ByteString -> SB.ByteString
#if MIN_VERSION_bytestring(0, 10, 0)
lazyByteStringToStrict = LB.toStrict
#else
lazyByteStringToStrict = SB.concat . LB.toChunks
#endif
{-# INLINE lazyByteStringToStrict #-}