-- | Numeric and binary encoding formatters.
--
-- All numeric encoders are polymorphic over @IsString m@,
-- so they work with any output type (String, Text.Builder,
-- ByteString.Builder, etc.).
--
-- Naming follows C printf conventions:
--
-- * @d@, @u@, @x@ — decimal signed, unsigned, hex
-- * @hh@, @h@, @l@, @ll@ — width prefixes (8, 16, 32, 64 bit)
-- * Primed variants (@hx'@, @lx'@) — fixed-width (zero-padded)
module Data.Fmt.Code (
    -- * Generic (IsString m)
    c,
    s,

    -- * Floating point
    e,
    f,
    g,

    -- * Decimal encodings
    d,
    hhd,
    hd,
    ld,
    lld,
    u,
    hhu,
    hu,
    lu,
    llu,

    -- * Hexadecimal encodings
    x,
    hhx,
    hhx',
    hx,
    hx',
    lx,
    lx',
    llx,
    llx',

    -- * Character encodings (Builder-specific)
    c7,
    c8,
    s7,
    s8,

    -- * Binary encodings (Builder-specific)
    b,
    b',
    hhb,
    hb,
    hb',
    lb,
    lb',
    llb,
    llb',
) where

import Data.ByteString (ByteString)
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as BL
import Data.Char (intToDigit)
import Data.Fmt.Type (Fmt1, fmt1)
import Data.Int
import Data.String (IsString, fromString)
import Data.Word
import qualified Numeric as N

---------------------------------------------------------------------
-- Generic (polymorphic over IsString m)
---------------------------------------------------------------------

-- | Format a character.
{-# INLINE c #-}
c :: IsString m => Fmt1 m s Char
c :: forall m s. IsString m => Fmt1 m s Char
c = (Char -> m) -> Fmt1 m s Char
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Char -> String) -> Char -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Char -> String
forall a. a -> [a]
forall (f :: * -> *) a. Applicative f => a -> f a
pure)

-- | Format a showable value.
{-# INLINE s #-}
s :: (IsString m, Show a) => Fmt1 m s a
s :: forall m a s. (IsString m, Show a) => Fmt1 m s a
s = (a -> m) -> Fmt1 m s a
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (a -> String) -> a -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. a -> String
forall a. Show a => a -> String
show)

---------------------------------------------------------------------
-- Floating point
---------------------------------------------------------------------

-- | Scientific notation with @prec@ digits of precision.
e :: (IsString m, RealFloat a) => Int -> Fmt1 m s a
e :: forall m a s. (IsString m, RealFloat a) => Int -> Fmt1 m s a
e Int
prec = (a -> m) -> Fmt1 m s a
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 ((a -> m) -> Fmt1 m s a) -> (a -> m) -> Fmt1 m s a
forall a b. (a -> b) -> a -> b
$ String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (a -> String) -> a -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> String -> String) -> String -> a -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Maybe Int -> a -> String -> String
forall a. RealFloat a => Maybe Int -> a -> String -> String
N.showEFloat (Maybe Int -> a -> String -> String)
-> Maybe Int -> a -> String -> String
forall a b. (a -> b) -> a -> b
$ Int -> Maybe Int
forall a. a -> Maybe a
Just Int
prec) []

-- | Fixed-point notation with @prec@ digits after the decimal.
f :: (IsString m, RealFloat a) => Int -> Fmt1 m s a
f :: forall m a s. (IsString m, RealFloat a) => Int -> Fmt1 m s a
f Int
prec = (a -> m) -> Fmt1 m s a
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 ((a -> m) -> Fmt1 m s a) -> (a -> m) -> Fmt1 m s a
forall a b. (a -> b) -> a -> b
$ String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (a -> String) -> a -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> String -> String) -> String -> a -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Maybe Int -> a -> String -> String
forall a. RealFloat a => Maybe Int -> a -> String -> String
N.showFFloat (Maybe Int -> a -> String -> String)
-> Maybe Int -> a -> String -> String
forall a b. (a -> b) -> a -> b
$ Int -> Maybe Int
forall a. a -> Maybe a
Just Int
prec) []

-- | General notation (shorter of 'e' and 'f').
g :: (IsString m, RealFloat a) => Int -> Fmt1 m s a
g :: forall m a s. (IsString m, RealFloat a) => Int -> Fmt1 m s a
g Int
prec = (a -> m) -> Fmt1 m s a
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 ((a -> m) -> Fmt1 m s a) -> (a -> m) -> Fmt1 m s a
forall a b. (a -> b) -> a -> b
$ String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (a -> String) -> a -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (a -> String -> String) -> String -> a -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip (Maybe Int -> a -> String -> String
forall a. RealFloat a => Maybe Int -> a -> String -> String
N.showGFloat (Maybe Int -> a -> String -> String)
-> Maybe Int -> a -> String -> String
forall a b. (a -> b) -> a -> b
$ Int -> Maybe Int
forall a. a -> Maybe a
Just Int
prec) []

---------------------------------------------------------------------
-- Decimal encodings
---------------------------------------------------------------------

-- | Decimal encoding of an 'Int'.
{-# INLINE d #-}
d :: IsString m => Fmt1 m s Int
d :: forall m s. IsString m => Fmt1 m s Int
d = (Int -> m) -> Fmt1 m s Int
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Int -> String) -> Int -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of an 'Int8'.
{-# INLINE hhd #-}
hhd :: IsString m => Fmt1 m s Int8
hhd :: forall m s. IsString m => Fmt1 m s Int8
hhd = (Int8 -> m) -> Fmt1 m s Int8
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Int8 -> String) -> Int8 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int8 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of an 'Int16'.
{-# INLINE hd #-}
hd :: IsString m => Fmt1 m s Int16
hd :: forall m s. IsString m => Fmt1 m s Int16
hd = (Int16 -> m) -> Fmt1 m s Int16
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Int16 -> String) -> Int16 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int16 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of an 'Int32'.
{-# INLINE ld #-}
ld :: IsString m => Fmt1 m s Int32
ld :: forall m s. IsString m => Fmt1 m s Int32
ld = (Int32 -> m) -> Fmt1 m s Int32
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Int32 -> String) -> Int32 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int32 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of an 'Int64'.
{-# INLINE lld #-}
lld :: IsString m => Fmt1 m s Int64
lld :: forall m s. IsString m => Fmt1 m s Int64
lld = (Int64 -> m) -> Fmt1 m s Int64
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Int64 -> String) -> Int64 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int64 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of a 'Word'.
{-# INLINE u #-}
u :: IsString m => Fmt1 m s Word
u :: forall m s. IsString m => Fmt1 m s Word
u = (Word -> m) -> Fmt1 m s Word
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word -> String) -> Word -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of a 'Word8'.
{-# INLINE hhu #-}
hhu :: IsString m => Fmt1 m s Word8
hhu :: forall m s. IsString m => Fmt1 m s Word8
hhu = (Word8 -> m) -> Fmt1 m s Word8
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word8 -> String) -> Word8 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word8 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of a 'Word16'.
{-# INLINE hu #-}
hu :: IsString m => Fmt1 m s Word16
hu :: forall m s. IsString m => Fmt1 m s Word16
hu = (Word16 -> m) -> Fmt1 m s Word16
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word16 -> String) -> Word16 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word16 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of a 'Word32'.
{-# INLINE lu #-}
lu :: IsString m => Fmt1 m s Word32
lu :: forall m s. IsString m => Fmt1 m s Word32
lu = (Word32 -> m) -> Fmt1 m s Word32
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word32 -> String) -> Word32 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word32 -> String
forall a. Show a => a -> String
show)

-- | Decimal encoding of a 'Word64'.
{-# INLINE llu #-}
llu :: IsString m => Fmt1 m s Word64
llu :: forall m s. IsString m => Fmt1 m s Word64
llu = (Word64 -> m) -> Fmt1 m s Word64
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word64 -> String) -> Word64 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Word64 -> String
forall a. Show a => a -> String
show)

---------------------------------------------------------------------
-- Hexadecimal encodings
---------------------------------------------------------------------

-- | Shortest hex of a 'Word', lower-case.
{-# INLINE x #-}
x :: IsString m => Fmt1 m s Word
x :: forall m s. IsString m => Fmt1 m s Word
x = (Word -> m) -> Fmt1 m s Word
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word -> String) -> Word -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word -> String -> String) -> String -> Word -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip Word -> String -> String
forall a. Integral a => a -> String -> String
N.showHex String
"")

-- | Shortest hex of a 'Word8', lower-case.
{-# INLINE hhx #-}
hhx :: IsString m => Fmt1 m s Word8
hhx :: forall m s. IsString m => Fmt1 m s Word8
hhx = (Word8 -> m) -> Fmt1 m s Word8
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word8 -> String) -> Word8 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word8 -> String -> String) -> String -> Word8 -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip Word8 -> String -> String
forall a. Integral a => a -> String -> String
N.showHex String
"")

-- | Fixed-width hex of a 'Word8' (2 nibbles).
{-# INLINE hhx' #-}
hhx' :: IsString m => Fmt1 m s Word8
hhx' :: forall m s. IsString m => Fmt1 m s Word8
hhx' = (Word8 -> m) -> Fmt1 m s Word8
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word8 -> String) -> Word8 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Word8 -> String
forall a. (Integral a, Show a) => Int -> a -> String
padHex Int
2)

-- | Shortest hex of a 'Word16', lower-case.
{-# INLINE hx #-}
hx :: IsString m => Fmt1 m s Word16
hx :: forall m s. IsString m => Fmt1 m s Word16
hx = (Word16 -> m) -> Fmt1 m s Word16
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word16 -> String) -> Word16 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word16 -> String -> String) -> String -> Word16 -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip Word16 -> String -> String
forall a. Integral a => a -> String -> String
N.showHex String
"")

-- | Fixed-width hex of a 'Word16' (4 nibbles).
{-# INLINE hx' #-}
hx' :: IsString m => Fmt1 m s Word16
hx' :: forall m s. IsString m => Fmt1 m s Word16
hx' = (Word16 -> m) -> Fmt1 m s Word16
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word16 -> String) -> Word16 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Word16 -> String
forall a. (Integral a, Show a) => Int -> a -> String
padHex Int
4)

-- | Shortest hex of a 'Word32', lower-case.
{-# INLINE lx #-}
lx :: IsString m => Fmt1 m s Word32
lx :: forall m s. IsString m => Fmt1 m s Word32
lx = (Word32 -> m) -> Fmt1 m s Word32
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word32 -> String) -> Word32 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word32 -> String -> String) -> String -> Word32 -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip Word32 -> String -> String
forall a. Integral a => a -> String -> String
N.showHex String
"")

-- | Fixed-width hex of a 'Word32' (8 nibbles).
{-# INLINE lx' #-}
lx' :: IsString m => Fmt1 m s Word32
lx' :: forall m s. IsString m => Fmt1 m s Word32
lx' = (Word32 -> m) -> Fmt1 m s Word32
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word32 -> String) -> Word32 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Word32 -> String
forall a. (Integral a, Show a) => Int -> a -> String
padHex Int
8)

-- | Shortest hex of a 'Word64', lower-case.
{-# INLINE llx #-}
llx :: IsString m => Fmt1 m s Word64
llx :: forall m s. IsString m => Fmt1 m s Word64
llx = (Word64 -> m) -> Fmt1 m s Word64
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word64 -> String) -> Word64 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. (Word64 -> String -> String) -> String -> Word64 -> String
forall a b c. (a -> b -> c) -> b -> a -> c
flip Word64 -> String -> String
forall a. Integral a => a -> String -> String
N.showHex String
"")

-- | Fixed-width hex of a 'Word64' (16 nibbles).
{-# INLINE llx' #-}
llx' :: IsString m => Fmt1 m s Word64
llx' :: forall m s. IsString m => Fmt1 m s Word64
llx' = (Word64 -> m) -> Fmt1 m s Word64
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 (String -> m
forall a. IsString a => String -> a
fromString (String -> m) -> (Word64 -> String) -> Word64 -> m
forall b c a. (b -> c) -> (a -> b) -> a -> c
. Int -> Word64 -> String
forall a. (Integral a, Show a) => Int -> a -> String
padHex Int
16)

---------------------------------------------------------------------
-- Character encodings (Builder-specific)
---------------------------------------------------------------------

-- | ASCII encode a 'Char'.
{-# INLINE c7 #-}
c7 :: Fmt1 B.Builder s Char
c7 :: forall s. Fmt1 Builder s Char
c7 = (Char -> Builder) -> Fmt1 Builder s Char
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Char -> Builder
B.char7

-- | Latin-1 (ISO/IEC 8859-1) encode a 'Char'.
{-# INLINE c8 #-}
c8 :: Fmt1 B.Builder s Char
c8 :: forall s. Fmt1 Builder s Char
c8 = (Char -> Builder) -> Fmt1 Builder s Char
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Char -> Builder
B.char8

-- | ASCII encode a 'String'.
{-# INLINE s7 #-}
s7 :: Fmt1 B.Builder s String
s7 :: forall s. Fmt1 Builder s String
s7 = (String -> Builder) -> Fmt1 Builder s String
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 String -> Builder
B.string7

-- | Latin-1 (ISO/IEC 8859-1) encode a 'String'.
{-# INLINE s8 #-}
s8 :: Fmt1 B.Builder s String
s8 :: forall s. Fmt1 Builder s String
s8 = (String -> Builder) -> Fmt1 Builder s String
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 String -> Builder
B.string8

---------------------------------------------------------------------
-- Binary encodings (Builder-specific)
---------------------------------------------------------------------

-- | Embed a lazy 'BL.ByteString'.
{-# INLINE b #-}
b :: Fmt1 B.Builder s BL.ByteString
b :: forall s. Fmt1 Builder s ByteString
b = (ByteString -> Builder) -> Fmt1 Builder s ByteString
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 ByteString -> Builder
B.lazyByteString

-- | Embed a strict 'ByteString'.
{-# INLINE b' #-}
b' :: Fmt1 B.Builder s ByteString
b' :: forall s. Fmt1 Builder s ByteString
b' = (ByteString -> Builder) -> Fmt1 Builder s ByteString
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 ByteString -> Builder
B.byteString

-- | Encode a 'Word8' as a single byte.
{-# INLINE hhb #-}
hhb :: Fmt1 B.Builder s Word8
hhb :: forall s. Fmt1 Builder s Word8
hhb = (Word8 -> Builder) -> Fmt1 Builder s Word8
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word8 -> Builder
B.word8

-- | Encode a 'Word16' little-endian.
{-# INLINE hb #-}
hb :: Fmt1 B.Builder s Word16
hb :: forall s. Fmt1 Builder s Word16
hb = (Word16 -> Builder) -> Fmt1 Builder s Word16
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word16 -> Builder
B.word16LE

-- | Encode a 'Word16' big-endian.
{-# INLINE hb' #-}
hb' :: Fmt1 B.Builder s Word16
hb' :: forall s. Fmt1 Builder s Word16
hb' = (Word16 -> Builder) -> Fmt1 Builder s Word16
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word16 -> Builder
B.word16BE

-- | Encode a 'Word32' little-endian.
{-# INLINE lb #-}
lb :: Fmt1 B.Builder s Word32
lb :: forall s. Fmt1 Builder s Word32
lb = (Word32 -> Builder) -> Fmt1 Builder s Word32
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word32 -> Builder
B.word32LE

-- | Encode a 'Word32' big-endian.
{-# INLINE lb' #-}
lb' :: Fmt1 B.Builder s Word32
lb' :: forall s. Fmt1 Builder s Word32
lb' = (Word32 -> Builder) -> Fmt1 Builder s Word32
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word32 -> Builder
B.word32BE

-- | Encode a 'Word64' little-endian.
{-# INLINE llb #-}
llb :: Fmt1 B.Builder s Word64
llb :: forall s. Fmt1 Builder s Word64
llb = (Word64 -> Builder) -> Fmt1 Builder s Word64
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word64 -> Builder
B.word64LE

-- | Encode a 'Word64' big-endian.
{-# INLINE llb' #-}
llb' :: Fmt1 B.Builder s Word64
llb' :: forall s. Fmt1 Builder s Word64
llb' = (Word64 -> Builder) -> Fmt1 Builder s Word64
forall a m s. (a -> m) -> Fmt1 m s a
fmt1 Word64 -> Builder
B.word64BE

---------------------------------------------------------------------
-- Helpers
---------------------------------------------------------------------

-- | Pad a hex string to a fixed width with leading zeros.
padHex :: (Integral a, Show a) => Int -> a -> String
padHex :: forall a. (Integral a, Show a) => Int -> a -> String
padHex Int
width a
n = let hex :: String
hex = a -> String -> String
forall a. Integral a => a -> String -> String
N.showHex a
n String
""
                  in Int -> Char -> String
forall a. Int -> a -> [a]
replicate (Int -> Int -> Int
forall a. Ord a => a -> a -> a
max Int
0 (Int
width Int -> Int -> Int
forall a. Num a => a -> a -> a
- String -> Int
forall a. [a] -> Int
forall (t :: * -> *) a. Foldable t => t a -> Int
length String
hex)) Char
'0' String -> String -> String
forall a. [a] -> [a] -> [a]
++ String
hex