{-# LANGUAGE TypeSynonymInstances #-}

-- ---------------------------------------------------------------------------
-- |
-- Module      : Text.Show.ByteString
-- Copyright   : (c) 2008 Dan Doel
-- Maintainer  : Dan Doel
-- Stability   : Experimental
-- Portability : Non-portable (type synonym instances)
--
-- Efficiently convert from values to lazy byte strings.

module Text.Show.ByteString ( -- * The Show class
                              Show (..)
                            , show
                              -- * Putting Chars
                            , putAscii
                            , putUTF8
                              -- * Putting Strings
                            , putAsciiStr
                            , putUTF8Str
                              -- * Putting digits
                            , unsafePutDigit
                            , putDigit
                              -- * Putting integers
                            , showpIntAtBase
                              -- * Putting floats
                            , showpGFloat
                            , showpFFloat
                            , showpEFloat
                              -- * Combining builders
                            , unlinesP
                            , unwordsP
                            , showpParen
                              -- * Printing values
                            , print
                              -- * Put
                            , Put
                            , PutM(..)
                            , runPut
                            ) where

import Prelude hiding (Show(..), print, putStrLn)
import qualified Prelude

import Data.Binary.Put
import Data.ByteString.Lazy

import Data.Int
import Data.Word
import Data.Ratio
import Data.Complex

import Data.Array

import qualified Data.Map as M
import qualified Data.Set as S

import Text.Show.ByteString.Util ( putAscii , putUTF8
                                 , putAsciiStr, putUTF8Str
                                 , unsafePutDigit
                                 )

import Text.Show.ByteString.Char
import Text.Show.ByteString.Int
import Text.Show.ByteString.Integer
import Text.Show.ByteString.Float

-- | Conversion of values to readable byte strings.
-- Minimal complete definition: 'showp' or 'showpPrec'
class Show a where
  -- | Encodes a value to an efficient byte string builder.
  -- The precedence is used to determine where to put
  -- parentheses in a shown expression involving operators.
  --
  -- Values of type Put can be efficiently combined, so the
  -- showp functions are available for showing multiple values
  -- before producing an output byte string.
  showpPrec :: Int -> a -> Put

  -- | Encodes a value to an efficient byte string builder.
  -- Values of type Put can be efficiently combined, so this
  -- is available for building strings from multiple values.
  showp     :: a -> Put

  -- | Allows for specialized display of lists of values.
  -- This is used, for example, when showing arrays of Chars.
  showpList :: [a] -> Put

  showpPrec _ = showp

  showp = showpPrec 0

  showpList [] = putWord8 91 >> putWord8 93          -- "[]"
  showpList (x:xs) = putWord8 91 >> showp x >> go xs -- "[..
   where
   go (y:ys) = putWord8 44 >> showp y >> go ys       -- ..,..
   go [    ] = putWord8 93                           -- ..]"

-- | Encode a single value into a byte string
show :: Show a => a -> ByteString
show = runPut . showp

-- | A utility function for surrounding output by parentheses
-- conditionally.
showpParen :: Bool -> Put -> Put
showpParen b p | b         = putAscii '(' >> p >> putAscii ')'
               | otherwise = p

-- | Print a value to the standard output
print :: Show a => a -> IO ()
print = putStrLn . show

-- | Merge several string builders, separating them by newlines
unlinesP :: [Put] -> Put
unlinesP [    ] = return ()
unlinesP (p:ps) = p >> putAscii '\n' >> unlinesP ps

-- | Merge several string builders, separating them by spaces
unwordsP :: [Put] -> Put
unwordsP [    ] = return ()
unwordsP [p]    = p
unwordsP (p:ps) = p >> putAscii ' ' >> unwordsP ps

-- | Puts the digit corresponding to the Int passed in.
putDigit :: Int -> Put
putDigit i
  | i < 0     = error $ "putDigit: Negative integer: " ++ Prelude.show i
  | i > 9     = error $ "putDigit: Non-decimal digit: " ++ Prelude.show i
  | otherwise = unsafePutDigit i

-- This may be a bad idea, but I'm trying it out
instance Show Put where
  showp p = p

instance Show () where
  showp () = putAscii '(' >> putAscii ')'

instance Show Char where
  showp     = showpChar
  showpList = showpString

instance Show Bool where
  showp True  = putAsciiStr "True"
  showp False = putAsciiStr "False"

instance (Show a) => Show [a] where
  showp = showpList

instance Show Int where
  showp = showpInt

  showpPrec k i = showpParen (i < 0 && k > 0) $ showpInt i

instance Show Int8 where
  showp = showpInt8

  showpPrec k i = showpParen (i < 0 && k > 0) $ showpInt8 i

instance Show Int16 where
  showp = showpInt16

  showpPrec k i = showpParen (i < 0 && k > 0) $ showpInt16 i

instance Show Int32 where
  showp = showpInt32

  showpPrec k i = showpParen (i < 0 && k > 0) $ showpInt32 i

instance Show Int64 where
  showp = showpInt64

  showpPrec k i = showpParen (i < 0 && k > 0) $ showpInt64 i

instance Show Word where
  showp = showpWord

instance Show Word8 where
  showp = showpWord8

instance Show Word16 where
  showp = showpWord16

instance Show Word32 where
  showp = showpWord32

instance Show Word64 where
  showp = showpWord64

instance Show Integer where
  showp = showpInteger

  showpPrec k i = showpParen (i < 0 && k > 0) $ showpInteger i

instance Show Float where
  showp = showpGFloat Nothing

  showpPrec k f = showpParen (f < 0 && k > 0) $ showpGFloat Nothing f

instance Show Double where
  showp = showpGFloat Nothing

  showpPrec k f = showpParen (f < 0 && k > 0) $ showpGFloat Nothing f

instance (Show a, Integral a) => Show (Ratio a) where
  showpPrec k q = showpParen (k > 7) $ showpPrec 8 (numerator q) >>
                  putAscii '%' >> showp (denominator q)

instance (Show a, RealFloat a) => Show (Complex a) where
  showpPrec k (a :+ b) = showpParen (k > 6) $ showpPrec 7 a >>
                         putAscii ' ' >> putAscii ':' >> putAscii '+' >> putAscii ' ' >>
                         showpPrec 7 b

instance Show a => Show (Maybe a) where
  showpPrec _ Nothing  = putAsciiStr "Nothing"
  showpPrec k (Just a) = showpParen (k > 10) $ putAsciiStr "Just " >> showpPrec 11 a

instance (Show a, Show b) => Show (Either a b) where
  showpPrec k (Left a)  = showpParen (k > 10) $ putAsciiStr "Left " >> showpPrec 11 a
  showpPrec k (Right b) = showpParen (k > 10) $ putAsciiStr "Right " >> showpPrec 11 b

instance Show Ordering where
  showp LT = putAscii 'L' >> putAscii 'T'
  showp EQ = putAscii 'E' >> putAscii 'Q'
  showp GT = putAscii 'G' >> putAscii 'T'

instance (Show a, Show b) => Show (a,b) where
  showp (a,b) = putAscii '(' >> showp a >> putAscii ',' >> showp b >> putAscii ')'

instance (Show a, Show b, Show c) => Show (a,b,c) where
  showp (a,b,c) = putAscii '(' >> showp a >>
                  putAscii ',' >> showp b >>
                  putAscii ',' >> showp c >>
                  putAscii ')'

instance (Show a, Show b, Show c, Show d) => Show (a,b,c,d) where
  showp (a,b,c,d) =
    putAscii '(' >> showp a >>
    putAscii ',' >> showp b >>
    putAscii ',' >> showp c >>
    putAscii ',' >> showp d >>
    putAscii ')'

instance (Show a, Show b, Show c, Show d, Show e) => Show (a,b,c,d,e) where
  showp (a,b,c,d,e) =
    putAscii '(' >> showp a >>
    putAscii ',' >> showp b >>
    putAscii ',' >> showp c >>
    putAscii ',' >> showp d >>
    putAscii ',' >> showp e >>
    putAscii ')'

instance (Show a, Show b, Show c, Show d, Show e, Show f) => Show (a,b,c,d,e,f) where
  showp (a,b,c,d,e,f) =
    putAscii '(' >> showp a >>
    putAscii ',' >> showp b >>
    putAscii ',' >> showp c >>
    putAscii ',' >> showp d >>
    putAscii ',' >> showp e >>
    putAscii ',' >> showp f >>
    putAscii ')'

instance (Show a, Show b, Show c, Show d, Show e, Show f, Show g) => Show (a,b,c,d,e,f,g) where
  showp (a,b,c,d,e,f,g) =
    putAscii '(' >> showp a >>
    putAscii ',' >> showp b >>
    putAscii ',' >> showp c >>
    putAscii ',' >> showp d >>
    putAscii ',' >> showp e >>
    putAscii ',' >> showp f >>
    putAscii ',' >> showp g >>
    putAscii ')'

instance (Show i, Show e, Ix i) => Show (Array i e) where
  showpPrec k a = showpParen (k > 10) $ putAsciiStr "array " >>
                  showp (bounds a) >> putAscii ' ' >> showp (assocs a)

instance (Show k, Show v) => Show (M.Map k v) where
  showpPrec k m = showpParen (k > 10) $ putAsciiStr "fromList " >>
                  showp (M.toList m)

instance (Show e) => Show (S.Set e) where
  showpPrec k s = showpParen (k > 10) $ putAsciiStr "fromList " >>
                  showp (S.toList s)