{-# LANGUAGE LambdaCase        #-}
{-# LANGUAGE OverloadedStrings #-}

-- | Format a 'show'-generated string to make it nicer to read.
--
-- >>> :{
-- (putStrLn . prettyShow . Data.Map.fromList)
--     [("hello", Just True), ("world", Nothing), ("!", Just False)]
-- :}
-- fromList [("!",Just False)
--          ,("hello",Just True)
--          ,("world",Nothing)]
--
-- See the readme for some more examples.
module Text.Show.Prettyprint (
    prettifyShow,
    prettyShow,

    -- * Diagnostic functions
    prettifyShowErr,
    prettyShowErr,
) where



import Control.Applicative
import Data.Monoid
import Text.PrettyPrint.ANSI.Leijen as Ppr hiding ((<>))
import Text.Trifecta                as Tri



-- | Prettyprint a string produced by 'show'. On parse error, silently fall back
-- to a non-prettyprinted version.
prettifyShow :: String -> String
prettifyShow s = case parseString conP mempty s of
    Success x -> show x
    Failure _ -> s

-- | 'prettifyShow' with the 'show' baked in.
prettyShow :: Show a => a -> String
prettyShow = prettifyShow . show

-- | Attempt to prettify a string produced by 'show'. Report error information
-- on failure.
prettifyShowErr :: String -> String
prettifyShowErr s = case parseString conP mempty s of
    Success x -> show x
    Failure ErrInfo{ _errDoc = e } -> "ERROR " <> show e

-- | 'prettifyShowErr' with the 'show' baked in.
prettyShowErr :: Show a => a -> String
prettyShowErr = prettifyShowErr . show



conP :: Parser Doc
conP = do
    thing <- (token . choice)
        [ word
        , number
        , fmap (dquotes . Ppr.string) stringLiteral ]
    args <- many argP
    pure (if null args
        then thing
        else thing <+> align (sep args) )

word :: Parser Doc
word = variable <|> constructor

number :: Parser Doc
number = p <?> "number"
  where
    p = integerOrDouble >>= \case
        Left i -> pure (Ppr.integer i)
        Right d -> pure (Ppr.double d)

identifierStartingWith :: CharParsing f => f Char -> f Doc
identifierStartingWith x = liftA2 (\c cs -> Ppr.string (c:cs)) (x <|> Tri.char '_') (many (alphaNum <|> oneOf "'_"))

variable :: Parser Doc
variable = identifierStartingWith lower <?> "variable"

constructor :: Parser Doc
constructor = identifierStartingWith upper <?> "constructor"

argP :: Parser Doc
argP = (token . choice) [unitP, tupleP, listP, recordP, conP]

unitP :: Parser Doc
unitP = p <?> "()"
  where
    p = fmap Ppr.string (Tri.string "()")

tupleP :: Parser Doc
tupleP = p <?> "tuple"
  where
    p = fmap (encloseSep lparen rparen Ppr.comma) (Tri.parens (do
        x <- argP
        xs <- many (Tri.comma *> argP)
        pure (x:xs) ))

listP :: Parser Doc
listP = p <?> "list"
  where
    p = fmap (encloseSep lbracket rbracket Ppr.comma)
             (Tri.brackets (sepBy argP Tri.comma))

recordP :: Parser Doc
recordP = p <?> "{...}"
  where
    p = fmap (encloseSep lbrace rbrace Ppr.comma) (Tri.braces (sepBy recordEntryP Tri.comma))
    recordEntryP = do
        lhs <- token word
        _ <- token (Tri.char '=')
        rhs <- argP
        pure (lhs <+> Ppr.string "=" <+> rhs)