{-# LANGUAGE FlexibleInstances #-}

-- | Display human-readable values to the user efficiently. See the
-- 'display' method for documentation.
--
-- Use the OverloadedStrings language extension when using this module
-- to produce 'Builder' values conveniently e.g. @\"Hello\" <> display name@.

module Display
  (
  -- * Printing method
  display
  -- * Conversions
  , displayByteString
  , displayLazyByteString
  , displayString
  , displayText
  , displayLazyText
  -- * The class
  , Display
  ) where

import           Data.ByteString (ByteString)
import qualified Data.ByteString as S
import qualified Data.ByteString.Builder as B
import qualified Data.ByteString.Lazy as L
import           Data.Int (Int64, Int32, Int16, Int8)
import           Data.Text (Text)
import qualified Data.Text.Encoding as T
import qualified Data.Text.Lazy as LT
import qualified Data.Text.Lazy.Encoding as LT
import           Data.Word (Word64, Word32, Word16, Word8)

--------------------------------------------------------------------------------
-- Top-level entry points

-- | Display to a 'ByteString' value.
displayByteString :: Display a => a -> ByteString
displayByteString = L.toStrict . B.toLazyByteString . display

-- | Display to a 'ByteString' value.
displayLazyByteString :: Display a => a -> ByteString
displayLazyByteString = L.toStrict . B.toLazyByteString . display

-- | Display to a 'Text' value. Inefficient. Only use when you have to.
displayText :: Display a => a -> Text
displayText = LT.toStrict . LT.decodeUtf8 . B.toLazyByteString . display

-- | Display to a 'Text' value. Inefficient. Only use when you have to.
displayLazyText :: Display a => a -> Text
displayLazyText = LT.toStrict . LT.decodeUtf8 . B.toLazyByteString . display

-- | Display to a 'String' value. Very inefficient. Only use when you
-- are forced to by another API.
displayString :: Display a => a -> String
displayString = LT.unpack . LT.decodeUtf8 . B.toLazyByteString . display

--------------------------------------------------------------------------------
-- Class and instances

-- | Display a value in a human-readable format. This is the opposite
-- of the 'Show' class which is intended for programmers to read, and
-- can be used alongside 'Show'.
--
-- For example, consider: @Maybe String@
--
-- >>> show (Just "abc")
-- Just "abc"
-- >>> show Nothing
-- Nothing
--
-- whereas 'display' is meant for printing to users, so you might
-- write this:
--
-- >>> display (Just "abc")
-- abc
-- >>> display Nothing
-- ""
--
-- You can safely use newtype deriving with this type, e.g.
--
-- @
-- newtype Name = Name Text deriving (Show, Display)
-- @
--
-- Instances for exceptions can be written like this:
--
-- @
-- data MyException = SomeProblem deriving (Show, Typeable)
-- instance Exception MyException where displayException = displayString
-- @
--
class Display a where
  display :: a -> B.Builder
  -- ^ Display a value in a human-readable format. This is the
  -- opposite of the 'Show' class which is intended for programmers to
  -- read. See 'Display' for how to use this for your own types.
  --
  -- * Writing to file or output: The 'B.Builder' can be written
  -- directly to a handle with 'B.hPutBuilder' very efficiently as
  -- UTF-8. This is the preferred method when writing to stdout or a
  -- file.
  --
  -- * Writing to a string: Use one of the functions in Conversions
  --   like 'displayByteString' or 'displayString' (when needed).
  --
  -- Use the functions in "Data.ByteString.Builder" for hex encodings
  -- of numbers and strings.
  --
  -- To append 'Builder' you can use the 'Monoid' instance, for
  -- example to print things comma-separated, you can use
  -- 'Data.List.intersperse' and 'mconcat':
  --
  -- @
  -- mconcat (intersperse ", " (map display [1, 4, 5]))
  -- @
  --
  -- This example requires the OverloadedStrings language extension.

instance Display a => Display (Maybe a) where
  display = maybe mempty display

instance (Display a, Display b) => Display (Either a b) where
  display = either display display

instance Display Text where
  display = B.byteString . T.encodeUtf8

instance Display LT.Text where
  display = B.lazyByteString . LT.encodeUtf8

instance Display B.Builder where
  display = id

instance Display S.ByteString where
  display = B.byteString

instance Display L.ByteString where
  display = B.lazyByteString

instance Display [Char] where
  display = B.stringUtf8

instance Display Char where
  display = B.charUtf8

instance Display Int8 where
  display = B.int8Dec

instance Display Int16 where
  display = B.int16Dec

instance Display Int32 where
  display = B.int32Dec

instance Display Int64 where
  display = B.int64Dec

instance Display Int where
  display = B.intDec

instance Display Integer where
  display = B.integerDec

instance Display Word8 where
  display = B.word8Dec

instance Display Word16 where
  display = B.word16Dec

instance Display Word32 where
  display = B.word32Dec

instance Display Word64 where
  display = B.word64Dec

instance Display Word where
  display = B.wordDec

instance Display Float where
  display = B.floatDec

instance Display Double where
  display = B.doubleDec