{-# LANGUAGE DataKinds, TypeFamilies, TypeOperators #-}
module Text.Printf.Safe.Combinators (-- * Smart constructors
                                     type (<>), (><), (%), (+++),
                                     -- * Basic formatters
                                     s, _S, _shows,
                                     -- ** Number Formatters
                                     base, d, d', o, o', b, b', h, h', f) where
import Data.Char             (intToDigit)
import Data.Type.Equality    ((:~:) (..), gcastWith)
import Numeric               (showFloat, showIntAtBase)
import Text.Printf.Safe.Core (Printf (..), Formatter)

-- * Smart constructors
type family (<>) xs ys where
  (<>) '[] xs = xs
  (<>) (x ': xs) ys = x ': (xs <> ys)

appPrf :: Printf ts -> Printf ps -> Printf (ts <> ps)
appPrf EOS ps = ps
appPrf (str :<> ts) ps = str :<> appPrf ts ps
appPrf (fm :% ts)   ps = fm :% appPrf ts ps

appNil :: Printf ts -> (ts <> '[]) :~: ts
appNil EOS = Refl
appNil (_ :<> a) = appNil a
appNil (_ :% bs) = case appNil bs of Refl -> Refl


-- | Append plain string to format.
(><) :: Printf ts -> String -> Printf ts
xs >< str = gcastWith (appNil xs) $ appPrf xs (str :<> EOS)

-- | Append formatter to format.
(%) :: Printf ts -> (a -> String) -> Printf (ts <> '[a])
(%) xs p = appPrf xs (p :% EOS)

-- | Concatenate two prtinf formats.
(+++) :: Printf ts -> Printf ps -> Printf (ts <> ps)
(+++) = appPrf
infixr 5 +++

infixl 5 ><, %

-- | Format @String@ as it is.
s :: Formatter String
s = id

-- | Formatter for @Show@ instances.
_S :: Show a => Formatter a
_S = show

-- | Converts @'ShowS'@ function to @'Formatter'@.
_shows :: (a -> ShowS) -> Formatter a
_shows fmt = flip fmt ""

-- | Format @Integral@s at base.
base :: (Show a, Integral a)
     => a                       -- ^ Base
     -> Maybe (Char, Int)       -- ^ Padding settings.
     -> Formatter a
base adic mpd a =
  let ans = showIntAtBase adic intToDigit a ""
  in maybe "" (\(c,dig) -> replicate (dig - length ans) c) mpd ++ ans

-- | Decimal formatter with padding.
d' :: (Show a, Integral a) => Char -> Int -> Formatter a
d' c i = base 10 (Just (c,i))

-- | No padding version of @'d''@.
d :: (Show a, Integral a) => Formatter a
d = base 10 Nothing

-- | Octal formatter with padding.
o' :: (Show a, Integral a) => Char -> Int -> Formatter a
o' c i = base 8 (Just (c,i))

-- | No padding version of @'o''@.
o :: (Show a, Integral a) => Formatter a
o = base 8 Nothing

-- | Binary formatter with padding.
b' :: (Show a, Integral a) => Char -> Int -> Formatter a
b' c i = base 2 (Just (c,i))

-- | No padding version of @'b''@.
b :: (Show a, Integral a) => Formatter a
b = base 2 Nothing

-- | Binary formatter with padding.
h' :: (Show a, Integral a) => Char -> Int -> Formatter a
h' c i = base 16 (Just (c,i))

-- | No padding version of @'b''@.
h :: (Show a, Integral a) => Formatter a
h = base 16 Nothing

-- | @RealFloat@ formatter.
f :: RealFloat a => Formatter a
f = _shows showFloat