{-# LANGUAGE GADTs #-}
{-# LANGUAGE FlexibleInstances #-}

-- | Internal format starters.

module Formatting.Internal where

import           Control.Category (Category(..))
import           Data.Monoid
import           Data.String
import qualified Data.Text as S (Text)
import           Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy as TL
import           Data.Text.Lazy.Builder (Builder)
import qualified Data.Text.Lazy.Builder as T
import qualified Data.Text.Lazy.Builder as TLB
import qualified Data.Text.Lazy.IO as T
import           Prelude hiding ((.),id)
import           System.IO

-- | A formatter. The @r@ type means the returned value at the
-- end. The more formatters you compose, the more this wil build up
-- arguments from @r@ to @Int -> r@ to @Char -> (Int -> r)@, etc.
newtype Format r a =
  Format {runFormat :: (Builder -> r) -> a}

-- | Useful instance for applying two formatters to the same input
-- argument. For example: @format (year <> "/" % month) now@ will
-- yield @"2015/01"@.
instance Monoid (Format r (a -> r)) where
  mappend m n =
    Format (\k a ->
              runFormat m (\b1 -> runFormat n (\b2 -> k (b1 <> b2)) a) a)
  mempty = Format (\k _ -> k mempty)

-- | Useful instance for writing format string. With this you can
-- write @"Foo"@ instead of @now "Foo!"@.
instance (a ~ r) => IsString (Format r a) where
  fromString = now . fromString

-- | The same as (%). At present using 'Category' has an import
-- overhead, but one day it might be imported as standard.
instance Category Format where
  id = now mempty
  f . g =
    f `bind`
    \a ->
      g `bind`
      \b -> now (a `mappend` b)

-- | Composition operator. 'Format' is an instance of 'Category', but
-- that is (at present) inconvenient to use with regular "Prelude". So
-- this function is provided as a convenience.
(%) :: Format r a -> Format r' r -> Format r' a
(%) = (.)
infixr 9 %

-- | Function compose two formatters. Will feed the result of one
-- formatter into another.
(%.) :: Format r (Builder -> r') -> Format r' a -> Format r a
(%.) (Format a) (Format b) = Format (b . a)
infixr 8 %.

-- | Insert a constant monoidal value.
now :: Builder -> Format r r
now a = Format ($ a)

-- | Monadic indexed bind for holey monoids.
bind :: Format r a -> (Builder -> Format r' r) -> Format r' a
m `bind` f = Format $ \k -> runFormat m (\a -> runFormat (f a) k)

-- | Insert a function which accepts some argument and produces a
-- 'Builder' which is appended to the output at the end.
--
-- @later (f :: Int -> Builder)@ produces @Format r (Int -> r)@.
later :: (a -> Builder) -> Format r (a -> r)
later f = Format (. f)

-- | Run the formatter and return a lazy 'Text' value.
format :: Format Text a -> a
format m = runFormat m T.toLazyText

-- | Run the formatter and return a strict 'S.Text' value.
sformat :: Format S.Text a -> a
sformat m = runFormat m (T.toStrict . T.toLazyText)

-- | Run the formatter and return a 'Builder' value.
bprint :: Format Builder a -> a
bprint m = runFormat m id

-- | Run the formatter and print out the text to stdout.
fprint :: Format (IO ()) a -> a
fprint m = runFormat m (T.putStr . T.toLazyText)

-- | Run the formatter and put the output onto the given 'Handle'.
hprint :: Handle -> Format (IO ()) a -> a
hprint h m = runFormat m (T.hPutStr h . T.toLazyText)

-- | Run the formatter and return a list of characters.
formatToString :: Format [Char] a -> a
formatToString m = runFormat m (TL.unpack . TLB.toLazyText)