{-# LANGUAGE CPP #-}
module Bio.Prelude (
    module Bio.Base,
    module BasePrelude,
    module Data.List.NonEmpty,
    module Data.Semigroup,
    module System.IO,
    module System.Posix.Files,
    module System.Posix.IO,
    module System.Posix.Types,

    Bytes, LazyBytes,
    HashMap,
    HashSet,
    IntMap,
    IntSet,
    Text, LazyText,
    Pair(..),

#if !MIN_VERSION_base(4,8,0)
    first,
    second,
#endif

    decodeBytes,
    encodeBytes,

    Hashable(..),
    Unpack(..),
    fdGet,
    fdPut,
    fdPutLazy,
    withFd
                   ) where

import BasePrelude
#if MIN_VERSION_base(4,9,0)
                    hiding ( (<>), EOF, log1p, log1pexp, log1mexp, expm1 )
#else
                    hiding ( (<>), EOF )
#endif

#if !MIN_VERSION_base(4,8,0)
-- Not as nice as Data.Bifunctor, but still useful.
import Control.Arrow       ( first, second )
#endif

import Bio.Base
import Data.ByteString     ( ByteString )
import Data.ByteString.Internal ( createAndTrim )
import Data.List.NonEmpty  ( NonEmpty(..) )
import Data.Semigroup      ( Semigroup(..) )
import Data.Text           ( Text )
import Data.Hashable       ( Hashable(..) )
import Data.HashMap.Strict ( HashMap )
import Data.HashSet        ( HashSet )
import Data.IntMap         ( IntMap )
import Data.IntSet         ( IntSet )
import Data.Text.Encoding  ( encodeUtf8, decodeUtf8With )
import Foreign.C.Error     ( throwErrnoIf_ )
import System.IO           ( hPrint, hPutStr, hPutStrLn, stderr, stdout, stdin )
import System.Posix.Files
import System.Posix.IO
import System.Posix.Types

import qualified Data.ByteString.Unsafe as B
import qualified Data.ByteString.Lazy   as BL
import qualified Data.ByteString.Char8  as S
import qualified Data.Text              as T
import qualified Data.Text.Lazy         as TL

type Bytes     =    ByteString
type LazyBytes = BL.ByteString
type LazyText  = TL.Text

infixl 2 :!:

-- | A strict pair.
data Pair a b = !a :!: !b deriving(Eq, Ord, Show, Read, Bounded, Ix)

-- | Class of things that can be unpacked into 'String's.  Kind of the
-- opposite of 'IsString'.
class Unpack s where unpack :: s -> String

instance Unpack ByteString where unpack = S.unpack
instance Unpack Text       where unpack = T.unpack
instance Unpack String     where unpack = id

-- | @fdGet bs fd@ reads up to @bs@ 'Bytes' from file descriptor @Fd@.
-- Returns an empty 'Bytes' at end of file.
fdGet :: Int -> Fd -> IO Bytes
fdGet bs fd =
    createAndTrim bs $ \p ->
        fromIntegral <$> fdReadBuf fd (castPtr p) (fromIntegral bs)

fdPut :: Fd -> Bytes -> IO ()
fdPut fd s = B.unsafeUseAsCStringLen s $ \(p,l) ->
             throwErrnoIf_ (/= fromIntegral l) "fdPut" $
             fdWriteBuf fd (castPtr p) (fromIntegral l)

fdPutLazy :: Fd -> LazyBytes -> IO ()
fdPutLazy fd = mapM_ (fdPut fd) . BL.toChunks

withFd :: FilePath -> OpenMode -> Maybe FileMode -> OpenFileFlags
       -> (Fd -> IO a) -> IO a
withFd fp om fm ff = bracket (openFd fp om fm ff) closeFd

-- | Converts 'Bytes' into 'Text'.  This uses UTF8, but if there is an
-- error, it pretends it was Latin1.  Evil as this is, it tends to Just
-- Work on files where nobody ever wasted a thought on encodings.
decodeBytes :: Bytes -> Text
decodeBytes = decodeUtf8With (const $ fmap w2c)

-- | Converts 'Text' into 'Bytes'.  This uses UTF8.
encodeBytes :: Text -> Bytes
encodeBytes = encodeUtf8