{-# LANGUAGE RankNTypes, TypeSynonymInstances #-}

-------------------------------------------------------------------------
-- Wrapper module around pretty printing
-------------------------------------------------------------------------

module CHR.Pretty
  ( -- module UU.Pretty
    -- module UHC.Util.Chitil.Pretty
    module CHR.Pretty.Simple

  , PP_DocL

  -- * Choice combinators
  , (>-|-<)
  , (>-#-<)
  
  -- * General PP for list
  , ppListSep, ppListSepV, ppListSepVV
  
  -- * Pack PP around
  , ppCurlys
  , ppPacked
  , ppPackedWithStrings
  , ppParens
  , ppCurly
  , ppBrackets
  , ppVBar
  
  -- * Block, horizontal/vertical as required
  , ppBlock, ppBlockH
  , ppBlock'
  , ppBlockWithStrings
  , ppBlockWithStrings'
  , ppBlockWithStringsH
  
  , ppParensCommasBlock
  , ppCurlysBlock
  , ppCurlysSemisBlock
  , ppCurlysCommasBlock
  , ppParensSemisBlock
  , ppBracketsCommasBlock
  
  , ppParensCommasBlockH
  , ppCurlysBlockH
  , ppCurlysSemisBlockH
  , ppCurlysCommasBlockH
  , ppParensSemisBlockH
  , ppBracketsCommasBlockH
  
  , ppBracketsCommasV
  
  -- * Vertical PP of list only
  , ppVertically
  
  -- * Horizontal PP of list only
  , ppCommas, ppCommas'
  , ppSemis, ppSemis'
  , ppSpaces
  , ppCurlysCommas, ppCurlysCommas', ppCurlysCommasWith
  , ppCurlysSemis, ppCurlysSemis'
  , ppParensSpaces
  , ppParensCommas, ppParensCommas'
  , ppBracketsCommas
  , ppBracketsCommas'
  , ppHorizontally
  , ppListSepFill

  -- * Conditional
  , ppMbPre, ppMbPost
  , ppListPre, ppListPost

  -- * Misc
  , ppDots, ppMb, ppUnless, ppWhen

  -- * Render
  , showPP
  
  -- * IO
  , hPutWidthPPLn, putWidthPPLn
  , hPutPPLn, putPPLn
  , hPutPPFile, putPPFile
  -- , putPPFPath
  )
  where

-- import UU.Pretty
-- import UHC.Util.Chitil.Pretty
import           CHR.Pretty.Simple
-- import           CHR.Utils
-- import           UHC.Util.FPath
-- import           UHC.Util.Time
import           System.IO
import           Data.List
import           Data.Word
import qualified Data.Set as Set

-------------------------------------------------------------------------
-- PP utils for lists
-------------------------------------------------------------------------

type PP_DocL = [PP_Doc]

-- | PP list with open, separator, and close
ppListSep :: (PP s, PP c, PP o, PP a) => o -> c -> s -> [a] -> PP_Doc
ppListSep = ppListSepWith pp -- o >|< hlist (intersperse (pp s) (map pp pps)) >|< c
{-
ppListSep o c s pps
  = o >|< l pps >|< c
  where l []      = empty
        l [p]     = pp p
        l (p:ps)  = pp p >|< map (s >|<) ps
-}

-- | PP list with open, separator, and close, and explicit PP function
ppListSepWith :: (PP s, PP c, PP o) => (a->PP_Doc) -> o -> c -> s -> [a] -> PP_Doc
ppListSepWith ppa o c s pps = o >|< hlist (intersperse (pp s) (map ppa pps)) >|< c

{-# DEPRECATED ppListSepFill "Use ppListSep" #-}
ppListSepFill :: (PP s, PP c, PP o, PP a) => o -> c -> s -> [a] -> PP_Doc
ppListSepFill o c s pps
  = l pps
  where l []      = o >|< c
        l [p]     = o >|< pp p >|< c
        l (p:ps)  = hlist ((o >|< pp p) : map (s >|<) ps) >|< c

-- | PP in a blocklike fashion, possibly on a single horizontal line if indicated, yielding the lines of the block
ppBlock'' :: (PP ocs, PP a) => Bool -> ocs -> ocs -> ocs -> ocs -> [a] -> [PP_Doc]
ppBlock'' _    osngl _ c _ []                   = [osngl >|< c]
ppBlock'' _    osngl o c _ [a] | isSingleLine x = [osngl >|< x >|< c]
                               | otherwise      = [o >|< x] ++ [pp c]
                               where x = pp a
ppBlock'' hori osngl o c s aa@(a:as)               -- = [o >|< a] ++ map (s >|<) as ++ [pp c]
                               | hori && all isSingleLine xx = [osngl >|< x >|< hlist (map (s >|<) xs) >|< c]
                               | otherwise                   = [o >|< x] ++ map (s >|<) xs ++ [pp c]
                               where xx@(x:xs) = map pp aa

-- | PP in a blocklike fashion, vertically
ppBlock' :: (PP ocs, PP a) => ocs -> ocs -> ocs -> ocs -> [a] -> [PP_Doc]
ppBlock' = ppBlock'' False
{-# INLINE ppBlock' #-}

-- | PP in a blocklike fashion, vertically, possibly horizontally
ppBlockH' :: (PP ocs, PP a) => ocs -> ocs -> ocs -> ocs -> [a] -> [PP_Doc]
ppBlockH' = ppBlock'' True
{-# INLINE ppBlockH' #-}

-- | PP list with open, separator, and close in a possibly multiline block structure
ppBlock :: (PP ocs, PP a) => ocs -> ocs -> ocs -> [a] -> PP_Doc
ppBlock o c s = vlist . ppBlock' o o c s

-- | PP list with open, separator, and close in a possibly multiline block structure
ppBlockH :: (PP ocs, PP a) => ocs -> ocs -> ocs -> [a] -> PP_Doc
ppBlockH o c s = vlist . ppBlockH' o o c s

-- | See 'ppBlock', but with string delimiters aligned properly, yielding a list of elements
ppBlockWithStrings'' :: (PP a) => Bool -> String -> String -> String -> [a] -> [PP_Doc]
ppBlockWithStrings'' hori o c s = ppBlock'' hori o (pad o) c (pad s)
  where l = maximum $ map length [o,s]
        pad s = s ++ replicate (l - length s) ' '

-- | See 'ppBlock', but with string delimiters aligned properly, yielding a list of elements
ppBlockWithStrings' :: (PP a) => String -> String -> String -> [a] -> [PP_Doc]
ppBlockWithStrings' = ppBlockWithStrings'' False
{-# INLINE ppBlockWithStrings' #-}

-- | See 'ppBlock', but with string delimiters aligned properly, yielding a list of elements, preferring single line horizontal placement
ppBlockWithStringsH' :: (PP a) => String -> String -> String -> [a] -> [PP_Doc]
ppBlockWithStringsH' = ppBlockWithStrings'' True
{-# INLINE ppBlockWithStringsH' #-}

-- | See 'ppBlock', but with string delimiters aligned properly
ppBlockWithStrings :: (PP a) => String -> String -> String -> [a] -> PP_Doc
ppBlockWithStrings o c s = vlist . ppBlockWithStrings' o c s

-- | See 'ppBlock', but with string delimiters aligned properly, preferring single line horizontal placement
ppBlockWithStringsH :: (PP a) => String -> String -> String -> [a] -> PP_Doc
ppBlockWithStringsH o c s = vlist . ppBlockWithStringsH' o c s

-- | PP horizontally: list separated by comma
ppCommas :: PP a => [a] -> PP_Doc
ppCommas = ppListSep "" "" ","

-- | PP horizontally: list separated by comma + single blank
ppCommas' :: PP a => [a] -> PP_Doc
ppCommas' = ppListSep "" "" ", "

-- | PP horizontally: list separated by semicolon
ppSemis :: PP a => [a] -> PP_Doc
ppSemis = ppListSep "" "" ";"

-- | PP horizontally: list separated by semicolon + single blank
ppSemis' :: PP a => [a] -> PP_Doc
ppSemis' = ppListSep "" "" "; "

-- | PP horizontally: list separated by single blank
ppSpaces :: PP a => [a] -> PP_Doc
ppSpaces = ppListSep "" "" " "

-- | PP horizontally or vertically with "{", " ", and "}" in a possibly multiline block structure
ppCurlysBlock :: PP a => [a] -> PP_Doc
ppCurlysBlock = ppBlockWithStrings "{" "}" "  "
{-# INLINE ppCurlysBlock #-}

-- | PP horizontally or vertically with "{", " ", and "}" in a possibly multiline block structure, preferring single line horizontal placement
ppCurlysBlockH :: PP a => [a] -> PP_Doc
ppCurlysBlockH = ppBlockWithStringsH "{" "}" "  "
{-# INLINE ppCurlysBlockH #-}

-- | PP horizontally or vertically with "{", ";", and "}" in a possibly multiline block structure
ppCurlysSemisBlock :: PP a => [a] -> PP_Doc
ppCurlysSemisBlock = ppBlockWithStrings "{" "}" "; "
{-# INLINE ppCurlysSemisBlock #-}

-- | PP horizontally or vertically with "{", ";", and "}" in a possibly multiline block structure, preferring single line horizontal placement
ppCurlysSemisBlockH :: PP a => [a] -> PP_Doc
ppCurlysSemisBlockH = ppBlockWithStringsH "{" "}" "; "
{-# INLINE ppCurlysSemisBlockH #-}

-- | PP horizontally or vertically with "{", ",", and "}" in a possibly multiline block structure
ppCurlysCommasBlock :: PP a => [a] -> PP_Doc
ppCurlysCommasBlock = ppBlockWithStrings "{" "}" ", "
{-# INLINE ppCurlysCommasBlock #-}

-- | PP horizontally or vertically with "{", ",", and "}" in a possibly multiline block structure, preferring single line horizontal placement
ppCurlysCommasBlockH :: PP a => [a] -> PP_Doc
ppCurlysCommasBlockH = ppBlockWithStringsH "{" "}" ", "
{-# INLINE ppCurlysCommasBlockH #-}

-- | PP horizontally or vertically with "(", ";", and ")" in a possibly multiline block structure
ppParensSemisBlock :: PP a => [a] -> PP_Doc
ppParensSemisBlock = ppBlockWithStrings "(" ")" "; "
{-# INLINE ppParensSemisBlock #-}

-- | PP horizontally or vertically with "(", ";", and ")" in a possibly multiline block structure, preferring single line horizontal placement
ppParensSemisBlockH :: PP a => [a] -> PP_Doc
ppParensSemisBlockH = ppBlockWithStringsH "(" ")" "; "
{-# INLINE ppParensSemisBlockH #-}

-- | PP horizontally or vertically with "(", ",", and ")" in a possibly multiline block structure
ppParensCommasBlock :: PP a => [a] -> PP_Doc
ppParensCommasBlock = ppBlockWithStrings "(" ")" ", "
{-# INLINE ppParensCommasBlock #-}

-- | PP horizontally or vertically with "(", ",", and ")" in a possibly multiline block structure, preferring single line horizontal placement
ppParensCommasBlockH :: PP a => [a] -> PP_Doc
ppParensCommasBlockH = ppBlockWithStringsH "(" ")" ", "
{-# INLINE ppParensCommasBlockH #-}

-- | PP horizontally or vertically with "[", ",", and "]" in a possibly multiline block structure
ppBracketsCommasV, ppBracketsCommasBlock, ppBracketsCommasBlockH :: PP a => [a] -> PP_Doc
ppBracketsCommasBlock = ppBlockWithStrings "[" "]" ", "
{-# INLINE ppBracketsCommasBlock #-}
ppBracketsCommasBlockH = ppBlockWithStringsH "[" "]" ", "
{-# INLINE ppBracketsCommasBlockH #-}
ppBracketsCommasV = ppBracketsCommasBlock
{-# DEPRECATED ppBracketsCommasV "Use ppBracketsCommasBlock" #-}

-- | PP horizontally with "[", ",", and "]"
ppBracketsCommas :: PP a => [a] -> PP_Doc
ppBracketsCommas = ppListSep "[" "]" ","

-- | PP horizontally with "[", ", ", and "]"
ppBracketsCommas' :: PP a => [a] -> PP_Doc
ppBracketsCommas' = ppListSep "[" "]" ", "

-- | PP horizontally with "(", " ", and ")"
ppParensSpaces :: PP a => [a] -> PP_Doc
ppParensSpaces = ppListSep "(" ")" " "

-- | PP horizontally with "(", ",", and ")"
ppParensCommas :: PP a => [a] -> PP_Doc
ppParensCommas = ppListSep "(" ")" ","

-- | PP horizontally with "(", ", ", and ")"
ppParensCommas' :: PP a => [a] -> PP_Doc
ppParensCommas' = ppListSep "(" ")" ", "

-- | PP horizontally with "{", ",", and "}"
ppCurlysCommas :: PP a => [a] -> PP_Doc
ppCurlysCommas = ppListSep "{" "}" ","

ppCurlysCommasWith :: PP a => (a->PP_Doc) -> [a] -> PP_Doc
ppCurlysCommasWith ppa = ppListSepWith ppa "{" "}" ","

-- | PP horizontally with "{", ", ", and "}"
ppCurlysCommas' :: PP a => [a] -> PP_Doc
ppCurlysCommas' = ppListSep "{" "}" ", "

-- | PP horizontally with "{", ";", and "}"
ppCurlysSemis :: PP a => [a] -> PP_Doc
ppCurlysSemis = ppListSep "{" "}" ";"

-- | PP horizontally with "{", "; ", and "}"
ppCurlysSemis' :: PP a => [a] -> PP_Doc
ppCurlysSemis' = ppListSep "{" "}" "; "

{-
ppCommaListV :: PP a => [a] -> PP_Doc
ppCommaListV = ppListSepVV "[" "]" "; "
-}

{-# DEPRECATED ppListSepV', ppListSepV, ppListSepVV "Use pp...Block variants" #-}
ppListSepV' :: (PP s, PP c, PP o, PP a) => (forall x y . (PP x, PP y) => x -> y -> PP_Doc) -> o -> c -> s -> [a] -> PP_Doc
ppListSepV' aside o c s pps
  = l pps
  where l []      = o `aside` c
        l [p]     = o `aside` p `aside` c
        l (p:ps)  = vlist ([o `aside` p] ++ map (s `aside`) (init ps) ++ [s `aside` last ps `aside` c])

-- compact vertical list
{-
ppListSepV3 :: (PP s, PP c, PP o, PP a) => o -> c -> s -> [a] -> PP_Doc
ppListSepV3 o c s pps
  = l pps
  where l []      = o >|< c
        l [p]     = o >|< p >|< c
        l (p:ps)  = vlist ([o >|< p] ++ map (s >|<) (init ps) ++ [s >|< last ps >|< c])
-}

ppListSepV :: (PP s, PP c, PP o, PP a) => o -> c -> s -> [a] -> PP_Doc
ppListSepV = ppListSepV' (>|<)

ppListSepVV :: (PP s, PP c, PP o, PP a) => o -> c -> s -> [a] -> PP_Doc
ppListSepVV = ppListSepV' (>-<)

-- | Alias for 'vlist'
ppVertically :: [PP_Doc] -> PP_Doc
ppVertically = vlist

-- | Alias for 'hlist'
ppHorizontally :: [PP_Doc] -> PP_Doc
ppHorizontally = hlist

-------------------------------------------------------------------------
-- Printing open/close pairs
-------------------------------------------------------------------------

ppPacked :: (PP o, PP c, PP p) => o -> c -> p -> PP_Doc
ppPacked o c pp
  = o >|< pp >|< c

ppPackedWithStrings :: (PP p) => String -> String -> p -> PP_Doc
ppPackedWithStrings o c x = ppBlockWithStrings o c "" [x]

ppParens, ppBrackets, ppCurly, ppCurlys, ppVBar :: PP p => p -> PP_Doc
ppParens   = ppPackedWithStrings "(" ")"
ppBrackets = ppPackedWithStrings "[" "]"
ppCurly    = ppPackedWithStrings "{" "}"
ppCurlys   = ppCurly
ppVBar     = ppPackedWithStrings "|" "|"

-------------------------------------------------------------------------
-- Additional choice combinators, use with care...
-------------------------------------------------------------------------

infixr 2 >-|-<, >-#-<

aside :: (PP a, PP b) => String -> a -> b -> PP_Doc
aside sep l r | isSingleLine l' && isSingleLine r' = l' >|< sep >|< r'
              | otherwise                          = l' >-< sep >|< r'
  where l' = pp l
        r' = pp r

-- | As (>|<), but doing (>-<) when does not fit on single line
(>-|-<) :: (PP a, PP b) => a -> b -> PP_Doc
(>-|-<) = aside ""

-- | As (>#<), but doing (>-<) when does not fit on single line
(>-#-<) :: (PP a, PP b) => a -> b -> PP_Doc
(>-#-<) = aside " "

-------------------------------------------------------------------------
-- Conditional
-------------------------------------------------------------------------

maybeNull :: r -> ([a] -> r) -> [a] -> r
maybeNull n f l = if null l then n else f l
{-# INLINE maybeNull  #-}

-- | Only prefix with a 'Maybe' and extra space when 'Just'
ppMbPre :: (PP x, PP r) => (a -> x) -> Maybe a -> r -> PP_Doc
ppMbPre  p = maybe pp (\v rest -> p v >#< rest)

-- | Only suffix with a 'Maybe' and extra space when 'Just'
ppMbPost :: (PP x, PP r) => (a -> x) -> Maybe a -> r -> PP_Doc
ppMbPost p = maybe pp (\v rest -> rest >#< p v)

-- | Only prefix with a list and extra space when non-empty
ppListPre :: (PP x, PP r) => ([a] -> x) -> [a] -> r -> PP_Doc
ppListPre p = maybeNull pp (\l rest -> p l >#< rest)

-- | Only suffix with a list and extra space when non-empty
ppListPost :: (PP x, PP r) => ([a] -> x) -> [a] -> r -> PP_Doc
ppListPost p = maybeNull pp (\l rest -> p l >#< rest)

-- | Guard around PP: if False pass through
ppUnless :: PP x => Bool -> x -> PP_Doc
ppUnless b x = if b then empty else pp x

-- | Guard around PP: if True pass through
ppWhen :: PP x => Bool -> x -> PP_Doc
ppWhen b x = if b then pp x else empty

-------------------------------------------------------------------------
-- Misc
-------------------------------------------------------------------------

ppDots :: PP a => [a] -> PP_Doc
ppDots = ppListSep "" "" "."

ppMb :: PP a => Maybe a -> PP_Doc
ppMb = maybe empty pp

-------------------------------------------------------------------------
-- Instances
-------------------------------------------------------------------------

instance {-# OVERLAPPABLE #-} PP a => PP (Maybe a) where
  pp = maybe (pp "?") pp

instance {-# OVERLAPPABLE #-} PP a => PP (Set.Set a) where
  pp = ppCurlysCommasBlockH . Set.toList

instance PP Bool where
  pp = pp . show

instance PP Word32 where
  pp = pp . show

{-
instance PP ClockTime where
  pp = pp . show

instance PP FPath where
  pp = pp . fpathToStr
-}

instance PP () where
  pp _ = pp "()"

instance (PP a, PP b) => PP (a,b) where
  pp (a,b) = "(" >|< a >-|-< "," >|< b >-|-< ")"

instance (PP a, PP b, PP c) => PP (a,b,c) where
  pp (a,b,c) = "(" >|< a >-|-< "," >|< b >-|-< "," >|< c >-|-< ")"

{-
instance (PP a, PP b, PP c, PP d) => PP (a,b,c,d) where
  pp (a,b,c,d) = ppParensCommasBlock [a,b,c,d]

instance (PP a, PP b, PP c, PP d, PP e) => PP (a,b,c,d,e) where
  pp (a,b,c,d,e) = ppParensCommasBlock [a,b,c,d,e]

instance (PP a, PP b, PP c, PP d, PP e, PP f) => PP (a,b,c,d,e,f) where
  pp (a,b,c,d,e,f) = ppParensCommasBlock [a,b,c,d,e,f]

instance (PP a, PP b, PP c, PP d, PP e, PP f, PP g) => PP (a,b,c,d,e,f,g) where
  pp (a,b,c,d,e,f,g) = ppParensCommasBlock [a,b,c,d,e,f,g]

instance (PP a, PP b, PP c, PP d, PP e, PP f, PP g, PP h) => PP (a,b,c,d,e,f,g,h) where
  pp (a,b,c,d,e,f,g,h) = ppParensCommasBlock [a,b,c,d,e,f,g,h]

instance (PP a, PP b, PP c, PP d, PP e, PP f, PP g, PP h, PP i) => PP (a,b,c,d,e,f,g,h,i) where
  pp (a,b,c,d,e,f,g,h,i) = ppParensCommasBlock [a,b,c,d,e,f,g,h,i]

instance (PP a, PP b, PP c, PP d, PP e, PP f, PP g, PP h, PP i, PP j) => PP (a,b,c,d,e,f,g,h,i,j) where
  pp (a,b,c,d,e,f,g,h,i,j) = ppParensCommasBlock [a,b,c,d,e,f,g,h,i,j]
-}

-------------------------------------------------------------------------
-- Render
-------------------------------------------------------------------------

showPP :: PP a => a -> String
showPP x = disp (pp x) 1000 ""

-------------------------------------------------------------------------
-- PP printing to file
-------------------------------------------------------------------------

hPutLn :: Handle -> Int -> PP_Doc -> IO ()
{-
hPutLn h w pp
  = do hPut h pp w
       hPutStrLn h ""
-}
hPutLn h w pp
  = hPutStrLn h (disp pp w "")

hPutWidthPPLn :: Handle -> Int -> PP_Doc -> IO ()
hPutWidthPPLn h w pp = hPutLn h w pp

putWidthPPLn :: Int -> PP_Doc -> IO ()
putWidthPPLn = hPutWidthPPLn stdout

hPutPPLn :: Handle -> PP_Doc -> IO ()
hPutPPLn h = hPutWidthPPLn h 4000

putPPLn :: PP_Doc -> IO ()
putPPLn = hPutPPLn stdout

hPutPPFile :: Handle -> PP_Doc -> Int -> IO ()
hPutPPFile h pp wid
  = hPutLn h wid pp

{-
putPPFPath :: FPath -> PP_Doc -> Int -> IO ()
putPPFPath fp pp wid
  = do { fpathEnsureExists fp
       ; putPPFile (fpathToStr fp) pp wid
       }
-}

putPPFile :: String -> PP_Doc -> Int -> IO ()
putPPFile fn pp wid
  =  do  {  h <- openFile fn WriteMode
         ;  hPutPPFile h pp wid
         ;  hClose h
         }