--
-- Copyright 2018, akashche at redhat.com
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
-- |
-- IO utilities
--

{-# LANGUAGE DeriveGeneric #-}
{-# LANGUAGE DuplicateRecordFields #-}
{-# LANGUAGE NamedFieldPuns #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE Strict #-}

module VtUtils.IO
    ( IOWithFileException(..)
    , ioWithFileBytes
    , ioWithFileText
    ) where

import Prelude (Either(..), IO, Show, ($), return, show)
import Control.Exception (Exception, SomeException(..), throwIO, try)
import qualified Data.ByteString.Lazy as ByteStringLazy
import Data.Monoid ((<>))
import Data.Text (Text, unpack)
import Data.Text.Encoding.Error (lenientDecode)
import Data.Text.Lazy.Encoding (decodeUtf8With)
import qualified Data.Text.Lazy as TextLazy
import System.IO (IOMode(ReadMode), withBinaryFile)

import VtUtils.Error (errorShow)
import VtUtils.Text (textShow)

-- | Exception for `ioWithFileBytes` and `ioWithFileText` functions
--
data IOWithFileException = IOWithFileException
    { filePath :: Text -- ^ Specified path
    , exception :: SomeException -- ^ Exception record
    }
instance Exception IOWithFileException
instance Show IOWithFileException where
    show e@(IOWithFileException {filePath, exception}) = errorShow e $
               "Error reading file,"
            <> " path: [" <> filePath <> "],"
            <> " exception: [" <> textShow(exception) <> "]"

-- | Reads contents of a specified file as a lazy @ByteString@ (with streaming)
-- and provides it to the specified callback
--
-- Throws @IOWithFileBytesException@ if specified file cannot be read
--
-- Arguments:
--
--    * @path :: Text@: path to file
--    * @fun :: (Data.ByteString.Lazy.ByteString -> IO a)@: callback to process the file data
--
-- Return value: Result of the callback invocation
--
ioWithFileBytes :: Text -> (ByteStringLazy.ByteString -> IO a) -> IO a
ioWithFileBytes path fun = do
    outcome <- try $
        withBinaryFile (unpack path) ReadMode $ \ha -> do
            bs <- ByteStringLazy.hGetContents ha
            res <- fun bs
            return res
    case outcome of
        Left e -> throwIO $ IOWithFileException path e
        Right res -> return res

-- | Reads contents of a specified file as a lazy @Text@ (with streaming)
-- and provides it to the specified callback
--
-- File contents are decoded as @UTF-8@
--
-- Throws @IOWithFileBytesException@ if specified file cannot be read
--
-- Arguments:
--
--    * @path :: Text@: path to file
--    * @fun :: (Data.Text.Lazy.Text -> IO a)@: callback to process the file data
--
-- Return value: Result of the callback invocation
--
ioWithFileText :: Text -> (TextLazy.Text -> IO a) -> IO a
ioWithFileText path fun =
    ioWithFileBytes path $ \bs -> do
        let tx = decodeUtf8With lenientDecode bs
        res <- fun tx
        return res