    {-# LANGUAGE CPP #-}
    {-# LANGUAGE ViewPatterns #-}
    {-# OPTIONS_HADDOCK hide #-}

    #if HLINT
    #include "cabal_macros.h"
    #endif

    module Data.Thyme.Format.Internal where

    import Prelude
    import Control.Applicative
    import Data.Attoparsec.ByteString.Char8 (Parser, Result, IResult (..))
    import qualified Data.Attoparsec.ByteString.Char8 as P
    import qualified Data.ByteString.Char8 as S
    import Data.Char
    import Data.Int
    import qualified Data.Text as Text
    import qualified Data.Text.Encoding as Text

    #if MIN_VERSION_bytestring(0,10,0)
    # if MIN_VERSION_bytestring(0,10,2)
    import qualified Data.ByteString.Builder as B
    # else
    import qualified Data.ByteString.Lazy.Builder as B
    # endif
    import qualified Data.ByteString.Lazy as L
    #endif

    {-# INLINE utf8Char #-}
    {-# INLINE utf8String #-}
    utf8Char :: Char -> S.ByteString
    utf8String :: String -> S.ByteString
    #if MIN_VERSION_bytestring(0,10,0)
    utf8Char = L.toStrict . B.toLazyByteString . B.charUtf8
    utf8String = L.toStrict . B.toLazyByteString . B.stringUtf8
    #else
    utf8Char = Text.encodeUtf8 . Text.singleton
    utf8String = Text.encodeUtf8 . Text.pack
    #endif

    ------------------------------------------------------------------------

    {-# INLINE shows02 #-}
    shows02 :: Int -> ShowS
    shows02 n = if n < 10 then (:) '0' . shows n else shows n

    {-# ANN shows_2 "HLint: ignore Use camelCase" #-}
    {-# INLINE shows_2 #-}
    shows_2 :: Int -> ShowS
    shows_2 n = if n < 10 then (:) ' ' . shows n else shows n

    {-# INLINE shows03 #-}
    shows03 :: Int -> ShowS
    shows03 n
        | n < 10 = (++) "00" . shows n
        | n < 100 = (++) "0" . shows n
        | otherwise = shows n

    {-# INLINE showsYear #-}
    showsYear :: Int -> ShowS
    showsYear n@(abs -> u)
        | u < 10 = neg . (++) "000" . shows u
        | u < 100 = neg . (++) "00" . shows u
        | u < 1000 = neg . (++) "0" . shows u
        | otherwise = neg . shows u
        where neg = if n < 0 then (:) '-' else id

    {-# INLINE fills06 #-}
    fills06 :: Int64 -> ShowS
    fills06 n
        | n < 10 = (++) "00000"
        | n < 100 = (++) "0000"
        | n < 1000 = (++) "000"
        | n < 10000 = (++) "00"
        | n < 100000 = (++) "0"
        | otherwise = id

    {-# INLINE drops0 #-}
    drops0 :: Int64 -> ShowS
    drops0 n = case divMod n 10 of
        (q, 0) -> drops0 q
        _ -> shows n

    ------------------------------------------------------------------------

    {-# INLINEABLE parserToReadS #-}
    parserToReadS :: Parser a -> ReadS a
    parserToReadS = go . P.parse where
        {-# INLINEABLE go #-}
        go :: (S.ByteString -> Result a) -> ReadS a
        go k (splitAt 32 -> (h, t)) = case k (utf8String h) of
            -- `date -R | wc -c` is 32 characters
            Fail rest cxts msg -> fail $ concat [ "parserToReadS: ", msg
                , "; remaining: ", show (utf8Decode rest), "; stack: ", show cxts ]
            Partial k' -> go k' t
            Done rest a -> return (a, utf8Decode rest ++ t)

        {-# INLINE utf8Decode #-}
        utf8Decode :: S.ByteString -> String
        utf8Decode = Text.unpack . Text.decodeUtf8

    {-# INLINE indexOf #-}
    indexOf :: [String] -> Parser Int
    indexOf = P.choice . zipWith (\ i s -> i <$ P.string (S.pack s)) [0..]

    {-# INLINE indexOfCI #-}
    indexOfCI :: [String] -> Parser Int
    indexOfCI = P.choice . zipWith (\ i s -> i <$ stringCI s) [0..]

    -- | Case-insensitive UTF-8 ByteString parser
    --
    -- Matches one character at a time. Slow.
    {-# INLINE stringCI #-}
    stringCI :: String -> Parser ()
    stringCI = foldl (\ p c -> p *> charCI c) (pure ())

    -- | Case-insensitive UTF-8 ByteString parser
    --
    -- We can't easily perform upper/lower case conversion on the input, so
    -- instead we accept either one of @toUpper c@ and @toLower c@.
    {-# INLINE charCI #-}
    charCI :: Char -> Parser ()
    charCI c = if u == l then charU8 c else charU8 l <|> charU8 u where
        l = toLower c
        u = toUpper c

    {-# INLINE charU8 #-}
    charU8 :: Char -> Parser ()
    charU8 c = () <$ P.string (utf8Char c)

    -- | Number may be prefixed with '-'
    {-# INLINE negative #-}
    negative :: (Integral n) => Parser n -> Parser n
    negative p = ($) <$> (negate <$ P.char '-' <|> pure id) <*> p

    -- | Fixed-length 0-padded decimal
    {-# INLINE dec0 #-}
    dec0 :: Int -> Parser Int
    dec0 n = either fail return . P.parseOnly P.decimal =<< P.take n

    -- | Fixed-length space-padded decimal
    {-# INLINE dec_ #-}
    dec_ :: Int -> Parser Int
    dec_ n = either fail return . P.parseOnly P.decimal
        =<< S.dropWhile isSpace <$> P.take n