{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}

module GhcDump.ToHtml (topBindingsToHtml, exprToHtml) where

import Data.List
import Lucid
import GhcDump.Ast
import GhcDump.Util
import qualified Data.ByteString.Char8 as BS
import qualified Data.Text as T
import Data.Monoid ((<>))
import Prelude

topBindingsToHtml :: [TopBinding] -> Html ()
topBindingsToHtml = foldMap topBindingToHtml

topBindingToHtml :: TopBinding -> Html ()
topBindingToHtml = mapM_ (\(bndr, _, rhs) -> bindingToHtml bndr rhs) . topBindings

divClass :: T.Text -> Html a -> Html a
divClass cls contents = div_ [class_ cls] contents

spanClass :: T.Text -> Html a -> Html a
spanClass cls contents = span_ [class_ cls] contents

keyword :: Html a -> Html a
keyword = divClass "kw"

lambda :: Html ()
lambda = "λ "

rarrow :: Html ()
rarrow = " → "

spaced :: Html () -> Html ()
spaced x = " " <> x <> " "

exprToHtml :: Expr -> Html ()
exprToHtml (EVar v) = bndrToHtml v
exprToHtml (EVarGlobal v) = externalNameToHtml v
exprToHtml (ELit lit) = litToHtml lit
exprToHtml e@(EApp _ _)
  | (x, ys) <- collectArgs e
  = divClass "app" $ do
    exprToHtml x
    " "
    mconcat $ intersperse " " $ map exprToHtml ys
exprToHtml e@(ETyLam _ _)
  | (bndrs, rhs) <- collectTyBinders e
  = divClass "lam" $ do
    lambda
    bndrsToHtml bndrs
    rarrow
    divClass "rhs" $ exprToHtml rhs
exprToHtml e@(ELam _ _)
  | (bndrs, rhs) <- collectBinders e
  = divClass "lam" $ do
    lambda
    bndrsToHtml bndrs
    rarrow
    divClass "rhs" $ exprToHtml rhs
exprToHtml (ELet bs e) = divClass "let" $ do
    keyword "let "
    divClass "binds" $ foldMap (uncurry bindingToHtml) bs
    keyword " in "
    divClass "body" $ exprToHtml e
exprToHtml (ECase scrut b alts) = divClass "case" $ do
    keyword "case "
    divClass "scrut" $ exprToHtml scrut
    keyword " of "
    divClass "alts" $ mapM_ altToHtml alts
exprToHtml (ETick tick e) = divClass "tick" $ do
    keyword "tick "
    exprToHtml e
exprToHtml (EType ty) = divClass "type" $ typeToHtml ty
exprToHtml (ECoercion) = "$co"

bndrToHtml :: Binder -> Html ()
bndrToHtml bndr =
    sigil <> divClass "bndr" (toHtml (binderUniqueName bndr))
  where
    sigil
      | isTyBinder bndr = "@"
      | otherwise = mempty

bndrsToHtml :: [Binder] -> Html ()
bndrsToHtml bndrs =
  divClass "bndrs" $ foldMap (spaced . bndrToHtml) bndrs

typeSigToHtml :: Binder -> Type -> Html ()
typeSigToHtml bndr ty = divClass "sig" $ do
  bndrToHtml bndr
  " :: "
  typeToHtml ty

bindingToHtml :: Binder -> Expr -> Html ()
bindingToHtml bndr rhs = divClass "bind" $ do
  bndrToHtml bndr
  " = "
  divClass "rhs" $ exprToHtml rhs

moduleNameToHtml :: ModuleName -> Html ()
moduleNameToHtml m =
  divClass "mod" $ toHtml $ getModuleName m

externalNameToHtml :: ExternalName -> Html ()
externalNameToHtml (ExternalName mod nam _) =
  divClass "ext-name" $ moduleNameToHtml mod <> "." <> toHtml nam
externalNameToHtml (ForeignCall) =
  "$foreign-call"

altToHtml :: Alt -> Html ()
altToHtml Alt{..} = div_ $ do
  case altCon of
    AltDataCon dc -> divClass "datacon" $ toHtml dc
    AltLit lit -> litToHtml lit
    AltDefault -> divClass "kw" "DEFAULT"
  bndrsToHtml altBinders
  rarrow
  divClass "rhs" $ exprToHtml altRHS

typeToHtml :: Type -> Html ()
typeToHtml (VarTy v) = bndrToHtml v
typeToHtml t@(FunTy _ _)
  | ts <- splitFunTys t
  = divClass "funty" $ mconcat $ intersperse rarrow $ map typeToHtml ts
typeToHtml (TyConApp tc tys)
  = divClass "tyconapp" $ tyConToHtml tc <> " " <> mconcat (intersperse " " (map typeToHtml tys))
typeToHtml (AppTy a b)
  = divClass "appty" $ typeToHtml a <> typeToHtml b
typeToHtml t@(ForAllTy _ _)
  | (bndrs, ty) <- splitForAlls t
  = divClass "forallty" $ do
    keyword "forall "
    bndrsToHtml bndrs
    ". "
    typeToHtml t
typeToHtml (LitTy) = "LIT"
typeToHtml (CoercionTy) = "COERCION"

tyConToHtml :: TyCon -> Html ()
tyConToHtml (TyCon name _) = divClass "tycon" $ toHtml name

litToHtml :: Lit -> Html ()
litToHtml (MachChar c) = "'" <> toHtml [c] <> "'"
litToHtml (MachStr s) = "'" <> toHtml (BS.unpack s) <> "'"
litToHtml (MachNullAddr) = "$nullAddr"
litToHtml (MachInt n) = showHtml n <> "#"
litToHtml (MachInt64 n) = showHtml n <> "#"
litToHtml (MachWord n) = showHtml n <> "##"
litToHtml (MachWord64 n) = showHtml n <> "##64"
litToHtml (MachFloat n) = showHtml (realToFrac n :: Double) <> "##"
litToHtml (MachDouble n) = showHtml (realToFrac n :: Double) <> "##"
litToHtml (MachLabel s) = "&" <> toHtml s
litToHtml (LitInteger n) = showHtml n
litToHtml (LitNatural n) = showHtml n

showHtml :: Show a => a -> Html ()
showHtml = toHtml . show