{-|
 A Pretty printer for 'CPSScheme'-files and control flow.
 -}
{-# LANGUAGE TypeOperators #-}
module CPSPrint  where

import Text.PrettyPrint
import Data.Char
import Control.Arrow ((***))
import Data.Map (unions, singleton)
import Data.Monoid hiding ((<>))

import CPSScheme
import Common

-- * Pretty printer for 'CPSScheme' programs, omitting any labels

-- | Pretty-Prints a whole document. The first flag, if set to true, embedds the
-- label information by abusing high range unicode characters.
ppProg :: Bool -> Prog -> Doc
ppProg el = ppLambda el

-- | Renders to a String
renderProg :: Bool -> Prog -> String
renderProg el = render . ppProg el

ppLambda :: Bool -> Lambda -> Doc
ppLambda el (Lambda l vs c) = parens $ 
    embeddLabel el l <> text "λ" <+> sep
       [ hsep (map (\(Var _ n) -> text n) vs) <> text "." 
       , ppCall el c
       ]

ppCall :: Bool -> Call -> Doc
ppCall el (App l (P (If lt lf)) [b,c1,c2]) = sep
    [ embeddLabel el l  <> text "if" <+> ppVal el b
    , embeddLabel el lt <> text "then" <+> ppVal el c1
    , embeddLabel el lf <> text "else" <+> ppVal el c2
    ]
ppCall el (App l f as) =
    embeddLabel el l <> ppVal el f <+> sep (map (ppVal el) as)
ppCall el (Let l binds c) =
    embeddLabel el l <> text "let" <+> vcat (map ppBind binds) $$
    text "in" <+> ppCall el c
    where ppBind (Var _ n,l) = text n <+> text "=" $$ nest 6 (ppLambda el l)

ppVal :: Bool -> Val -> Doc
ppVal el (L l)             = ppLambda el l
ppVal el (R _ (Var _ v))   = text v
ppVal el (C _ c)           = integer c
ppVal el (P (Plus l))      = embeddLabel el l <> text "(+)"
ppVal el (P (If l _))      = embeddLabel el l <> text "if"

-- * Label embedding trick

-- | First unicode point to embed labels with (Private Use Area)
startAt :: Integer
startAt = 0x100000

labelToChar :: Label -> Char
labelToChar (Label i) = chr (fromIntegral (startAt + i))

charToLabel :: Char -> Maybe Label
charToLabel c = if i >= startAt then Just $ Label (i - startAt)
                                else Nothing
  where i = fromIntegral (ord c)                            

embeddLabel :: Bool -> Label -> Doc
embeddLabel False _  = empty
embeddLabel True  l  = char (labelToChar l)

-- | Given a replacement function and a string containing embedded labels, this
-- function replaces the labels by the given replacement character and
-- calculates a map of labels to positions in the text (1-based row and column
-- indexing)
labelPositions :: Char -> String -> (Label : (Integer, Integer), String)
labelPositions rep = (unions *** unlines) . unzip . zipWith labelLines [1..] . lines
  where labelLines :: Integer -> String -> (Label : (Integer, Integer), String)
        labelLines row = (unions *** id) . unzip . zipWith labelChar [1..]
          where labelChar :: Integer -> Char -> (Label : (Integer, Integer), Char)
                labelChar col c = case charToLabel c of
                                      Just l -> (l `singleton` (row,col), rep)
                                      Nothing -> (mempty, c)

-- | HPDF can not print lambdas. Therefore, replace them by backslashes.
removeLambdas :: String -> String
removeLambdas = map (\c -> if c == 'λ' then '\\' else c)

-- * Printing to Isablle-Expression

-- | Converts the whole program into an expression that can be copy'n'pasted
-- into an Isabelle source file
ipProg :: Prog -> Doc
ipProg = ipLambda

-- | Renders to a String
renderProgToIsa :: Prog -> String
renderProgToIsa = renderStyle myStyle . ipProg
  where myStyle = style { mode = OneLineMode }


ipLambda :: Lambda -> Doc
ipLambda (Lambda (Label i) vs c) = parens $ 
    text "Lambda" <+> integer i <+> sep
       [ brackets $ hsep (punctuate (char ',') (map ipVar vs))
       , ipCall c
       ]
ipVar :: Var -> Doc
ipVar (Var (Label i) n) = parens $ integer i <> char ',' <>
                                   text "''" <> text (quote n) <> text "''"
  where quote = map (\c -> if c == '\'' then '_' else c)

ipCall :: Call -> Doc
ipCall (App (Label l) f as) = parens $
    text "App" <+> integer l <+> ipVal f <+> brackets (sep (punctuate (char ',') (map ipVal as)))
ipCall (Let (Label l) binds c) = parens $
    text "Let" <+> integer l <+> brackets (sep (punctuate (char ',') (map ipBind binds))) $$
                   ipCall c
    where ipBind (v,l) = parens $ ipVar v <> char ',' <> ipLambda l

ipVal :: Val -> Doc
ipVal (L l)             = parens $ text "L" <+> ipLambda l
ipVal (R (Label l) v)   = parens $ text "R" <+> integer l <+> ipVar v
ipVal (C (Label l) c)   = parens $ text "C" <+> integer l <+>  integer c
ipVal (P prim)          = parens $ text "P" <+> ipPrim prim

ipPrim :: Prim -> Doc
ipPrim (Plus (Label l))            = parens $ text "Plus" <+> integer l
ipPrim (If (Label lt) (Label lf))  = parens $ text "If" <+> integer lt <+> integer lf