-----------------------------------------------------------------------------
-- |
-- Module      :  System.IO.UTF8
-- Copyright   :  (c) Eric Mertens 2007
-- License     :  BSD3-style (see LICENSE)
-- 
-- Maintainer:    emertens@galois.com
-- Stability   :  experimental
-- Portability :  portable
--
-- String IO preserving UTF8 encoding.
--

module System.IO.UTF8 (
      print
    , putStr
    , putStrLn
    , getLine
    , readLn
    , openBinaryFile
    , withBinaryFile
    , readFile
    , writeFile
    , appendFile
    , interact
    , getContents
    , hGetLine
    , hGetContents
    , hPutStr
    , hPutStrLn
  ) where

import Control.Monad (liftM)
import Data.Word (Word8)
import Prelude (String, (=<<), (.), map, Enum(toEnum, fromEnum), Read,
                Show(..))
import System.IO (Handle, IO, FilePath, IOMode(AppendMode, ReadMode, WriteMode))
import qualified System.IO as IO
import Control.Exception (bracket)

import Codec.Binary.UTF8.String (encode, decode)


-- | Encode a string in UTF8 form.
encodeString :: String -> String
encodeString xs = bytesToString (encode xs)

-- | Decode a string from UTF8
decodeString :: String -> String
decodeString xs = decode (stringToBytes xs)

-- | Convert a list of bytes to a String
bytesToString :: [Word8] -> String
bytesToString xs = map (toEnum . fromEnum) xs

-- | String to list of bytes.
stringToBytes :: String -> [Word8]
stringToBytes xs = map (toEnum . fromEnum) xs

-- | The 'print' function outputs a value of any printable type to the
-- standard output device. This function differs from the
-- System.IO.print in that it preserves any UTF8 encoding of the shown value.
--
print :: Show a => a -> IO ()
print x = putStrLn (show x)

-- | Write a UTF8 string to the standard output device
putStr :: String -> IO ()
putStr x = IO.putStr (encodeString x)

-- | The same as 'putStr', but adds a newline character.
putStrLn :: String -> IO ()
putStrLn x = IO.putStrLn (encodeString x)

-- | Read a UTF8 line from the standard input device
getLine :: IO String
getLine = liftM decodeString IO.getLine

-- | The 'readLn' function combines 'getLine' and 'readIO', preserving UTF8
readLn :: Read a => IO a
readLn = IO.readIO =<< getLine

openBinaryFile :: FilePath -> IOMode -> IO Handle
openBinaryFile n m = IO.openBinaryFile (encodeString n) m

withBinaryFile :: FilePath -> IOMode -> (Handle -> IO a) -> IO a
withBinaryFile n m f = bracket (openBinaryFile n m) IO.hClose f

-- | The 'readFile' function reads a file and
-- returns the contents of the file as a UTF8 string.
-- The file is read lazily, on demand, as with 'getContents'.
readFile :: FilePath -> IO String
readFile n = hGetContents =<< openBinaryFile n ReadMode

-- | The computation 'writeFile' @file str@ function writes the UTF8 string @str@,
-- to the file @file@.
writeFile :: FilePath -> String -> IO ()
writeFile n s = withBinaryFile n WriteMode (\ h -> hPutStr h s)

-- | The computation 'appendFile' @file str@ function appends the UTF8 string @str@,
-- to the file @file@.
appendFile :: FilePath -> String -> IO ()
appendFile n s = withBinaryFile n AppendMode (\ h -> hPutStr h s)

-- | Read a UTF8 line from a Handle
hGetLine :: Handle -> IO String
hGetLine h = liftM decodeString (IO.hGetLine h)

-- | Lazily read a UTF8 string from a Handle
hGetContents :: Handle -> IO String
hGetContents h = liftM decodeString (IO.hGetContents h)

-- | Write a UTF8 string to a Handle.
hPutStr :: Handle -> String -> IO ()
hPutStr h s = IO.hPutStr h (encodeString s)

-- | Write a UTF8 string to a Handle, appending a newline.
hPutStrLn :: Handle -> String -> IO ()
hPutStrLn h s = IO.hPutStrLn h (encodeString s)

-- | Lazily read stdin as a UTF8 string.
getContents :: IO String
getContents = liftM decodeString IO.getContents

interact :: (String -> String) -> IO ()
interact f = IO.interact (encodeString . f . decodeString)
