{-# LANGUAGE CPP #-}
module Development.IDE.GHC.Dump(showAstDataHtml) where
import qualified Data.ByteString                       as B
import           Data.Data                             hiding (Fixity)
import           Development.IDE.GHC.Compat            hiding (LocatedA,
                                                        NameAnn)
import           Development.IDE.GHC.Compat.ExactPrint (ExactPrint, exactPrint)
import           Development.IDE.GHC.Compat.Util
import           Generics.SYB                          (ext1Q, ext2Q, extQ)
import           GHC.Hs
import           GHC.Hs.Dump
import           GHC.Plugins                           hiding (AnnLet)
import           Prelude                               hiding ((<>))

-- | Show a GHC syntax tree in HTML.
showAstDataHtml :: (Data a, ExactPrint a) => a -> SDoc
showAstDataHtml a0 = html $
    header $$
    body (tag' [("id",text (show @String "myUL"))] "ul" $ vcat
        [
            li (pre $ text (exactPrint a0)),
            li (showAstDataHtml' a0),
            li (nested "Raw" $ pre $ showAstData NoBlankSrcSpan NoBlankEpAnnotations a0)
        ])
  where
    tag = tag' []
    tag' attrs t cont =
        angleBrackets (text t <+> hcat [text a<>char '=' <>v | (a,v) <- attrs])
        <> cont
        <> angleBrackets (char '/' <> text t)
    ul = tag' [("class", text (show @String "nested"))] "ul"
    li = tag "li"
    caret x = tag' [("class", text "caret")] "span" "" <+> x
    nested foo cts
      | otherwise = foo $$ (caret $ ul cts)
    body cts = tag "body" $ cts $$ tag "script" (text js)
    header = tag "head" $ tag "style" $ text css
    html = tag "html"
    pre = tag "pre"
    showAstDataHtml' :: Data a => a -> SDoc
    showAstDataHtml' =
      generic
              `ext1Q` list
              `extQ` string `extQ` fastString `extQ` srcSpan `extQ` realSrcSpan
#if !MIN_VERSION_ghc(9,11,0)
              `extQ` annotation
#endif
              `extQ` annotationModule
#if !MIN_VERSION_ghc(9,11,0)
              `extQ` annotationAddEpAnn
#endif
              `extQ` annotationGrhsAnn
              `extQ` annotationEpAnnHsCase
              `extQ` annotationEpAnnHsLet
              `extQ` annotationAnnList
              `extQ` annotationEpAnnImportDecl
              `extQ` annotationAnnParen
              `extQ` annotationTrailingAnn
              `extQ` annotationEpaLocation
#if !MIN_VERSION_ghc(9,11,0)
              `extQ` addEpAnn
#endif
              `extQ` lit `extQ` litr `extQ` litt
              `extQ` sourceText
              `extQ` deltaPos
              `extQ` epaAnchor
#if !MIN_VERSION_ghc(9,9,0)
              `extQ` anchorOp
#endif
              `extQ` bytestring
              `extQ` name `extQ` occName `extQ` moduleName `extQ` var
              `extQ` dataCon
              `extQ` bagName `extQ` bagRdrName `extQ` bagVar `extQ` nameSet
              `extQ` fixity
              `ext2Q` located
              `extQ` srcSpanAnnA
              `extQ` srcSpanAnnL
              `extQ` srcSpanAnnP
              `extQ` srcSpanAnnC
              `extQ` srcSpanAnnN

      where generic :: Data a => a -> SDoc
            generic t = nested (text $ showConstr (toConstr t))
                     (vcat (gmapQ (li . showAstDataHtml') t))

            string :: String -> SDoc
            string = text . normalize_newlines . show

            fastString :: FastString -> SDoc
            fastString s = braces $
                            text "FastString:"
                        <+> text (normalize_newlines . show $ s)

            bytestring :: B.ByteString -> SDoc
            bytestring = text . normalize_newlines . show

            list []  = brackets empty
            list [x] = "[]" $$ showAstDataHtml' x
            list xs  = nested "[]" (vcat $ map (li . showAstDataHtml') xs)

            -- Eliminate word-size dependence
            lit :: HsLit GhcPs -> SDoc
            lit (HsWordPrim   s x) = numericLit "HsWord{64}Prim" x s
            lit (HsWord64Prim s x) = numericLit "HsWord{64}Prim" x s
            lit (HsIntPrim    s x) = numericLit "HsInt{64}Prim"  x s
            lit (HsInt64Prim  s x) = numericLit "HsInt{64}Prim"  x s
            lit l                  = generic l

            litr :: HsLit GhcRn -> SDoc
            litr (HsWordPrim   s x) = numericLit "HsWord{64}Prim" x s
            litr (HsWord64Prim s x) = numericLit "HsWord{64}Prim" x s
            litr (HsIntPrim    s x) = numericLit "HsInt{64}Prim"  x s
            litr (HsInt64Prim  s x) = numericLit "HsInt{64}Prim"  x s
            litr l                  = generic l

            litt :: HsLit GhcTc -> SDoc
            litt (HsWordPrim   s x) = numericLit "HsWord{64}Prim" x s
            litt (HsWord64Prim s x) = numericLit "HsWord{64}Prim" x s
            litt (HsIntPrim    s x) = numericLit "HsInt{64}Prim"  x s
            litt (HsInt64Prim  s x) = numericLit "HsInt{64}Prim"  x s
            litt l                  = generic l

            numericLit :: String -> Integer -> SourceText -> SDoc
            numericLit tag x s = braces $ hsep [ text tag
                                               , generic x
                                               , generic s ]

            sourceText :: SourceText -> SDoc
            sourceText NoSourceText     = text "NoSourceText"

#if MIN_VERSION_ghc(9,7,0)
            sourceText (SourceText src) = text "SourceText" <+> ftext src
#else
            sourceText (SourceText src) = text "SourceText" <+> text src
#endif

            epaAnchor :: EpaLocation -> SDoc

#if MIN_VERSION_ghc(9,9,0)
            epaAnchor (EpaSpan s) = parens $ text "EpaSpan" <+> srcSpan s
#else
            epaAnchor (EpaSpan r _)  = text "EpaSpan" <+> realSrcSpan r
#endif

#if MIN_VERSION_ghc(9,11,0)
            epaAnchor (EpaDelta s d cs) = text "EpaDelta" <+> srcSpan s <+> deltaPos d <+> showAstDataHtml' cs
#else
            epaAnchor (EpaDelta d cs) = text "EpaDelta" <+> deltaPos d <+> showAstDataHtml' cs
#endif

#if !MIN_VERSION_ghc(9,9,0)
            anchorOp :: AnchorOperation -> SDoc
            anchorOp UnchangedAnchor  = "UnchangedAnchor"
            anchorOp (MovedAnchor dp) = "MovedAnchor " <> deltaPos dp
#endif

            deltaPos :: DeltaPos -> SDoc
            deltaPos (SameLine c) = text "SameLine" <+> ppr c
            deltaPos (DifferentLine l c) = text "DifferentLine" <+> ppr l <+> ppr c

            name :: Name -> SDoc
            name nm    = braces $ text "Name:" <+> ppr nm

            occName n  =  braces $
                          text "OccName:"
                      <+> text (occNameString n)

            moduleName :: ModuleName -> SDoc
            moduleName m = braces $ text "ModuleName:" <+> ppr m

            srcSpan :: SrcSpan -> SDoc
            srcSpan ss = char ' ' <>
                             hang (ppr ss) 1
                                   -- TODO: show annotations here
                                   (text "")

            realSrcSpan :: RealSrcSpan -> SDoc
            realSrcSpan ss = braces $ char ' ' <>
                             hang (ppr ss) 1
                                   -- TODO: show annotations here
                                   (text "")

#if !MIN_VERSION_ghc(9,11,0)
            addEpAnn :: AddEpAnn -> SDoc
            addEpAnn (AddEpAnn a s) = text "AddEpAnn" <+> ppr a <+> epaAnchor s
#endif

            var  :: Var -> SDoc
            var v      = braces $ text "Var:" <+> ppr v

            dataCon :: DataCon -> SDoc
            dataCon c  = braces $ text "DataCon:" <+> ppr c

            bagRdrName:: Bag (LocatedA (HsBind GhcPs)) -> SDoc
            bagRdrName bg =  braces $
                             text "Bag(LocatedA (HsBind GhcPs)):"
                          $$ (list . bagToList $ bg)

            bagName   :: Bag (LocatedA (HsBind GhcRn)) -> SDoc
            bagName bg  =  braces $
                           text "Bag(LocatedA (HsBind Name)):"
                        $$ (list . bagToList $ bg)

            bagVar    :: Bag (LocatedA (HsBind GhcTc)) -> SDoc
            bagVar bg  =  braces $
                          text "Bag(LocatedA (HsBind Var)):"
                       $$ (list . bagToList $ bg)

            nameSet ns =  braces $
                          text "NameSet:"
                       $$ (list . nameSetElemsStable $ ns)

            fixity :: Fixity -> SDoc
            fixity fx =  braces $
                         text "Fixity:"
                     <+> ppr fx

            located :: (Data a, Data b) => GenLocated a b -> SDoc
            located (L ss a)
              = nested "L" (li (showAstDataHtml' ss) $$ li (showAstDataHtml' a))

            -- -------------------------

#if !MIN_VERSION_ghc(9,11,0)
            annotation :: EpAnn [AddEpAnn] -> SDoc
            annotation = annotation' (text "EpAnn [AddEpAnn]")
#endif

            annotationModule :: EpAnn AnnsModule -> SDoc
            annotationModule = annotation' (text "EpAnn AnnsModule")

#if !MIN_VERSION_ghc(9,11,0)
            annotationAddEpAnn :: EpAnn AddEpAnn -> SDoc
            annotationAddEpAnn = annotation' (text "EpAnn AddEpAnn")
#endif

            annotationGrhsAnn :: EpAnn GrhsAnn -> SDoc
            annotationGrhsAnn = annotation' (text "EpAnn GrhsAnn")

            annotationEpAnnHsCase :: EpAnn EpAnnHsCase -> SDoc
            annotationEpAnnHsCase = annotation' (text "EpAnn EpAnnHsCase")

            annotationEpAnnHsLet :: EpAnn NoEpAnns -> SDoc
            annotationEpAnnHsLet = annotation' (text "EpAnn NoEpAnns")

#if MIN_VERSION_ghc(9,11,0)
            annotationAnnList :: EpAnn (AnnList ()) -> SDoc
#else
            annotationAnnList :: EpAnn AnnList -> SDoc
#endif
            annotationAnnList = annotation' (text "EpAnn AnnList")

            annotationEpAnnImportDecl :: EpAnn EpAnnImportDecl -> SDoc
            annotationEpAnnImportDecl = annotation' (text "EpAnn EpAnnImportDecl")

            annotationAnnParen :: EpAnn AnnParen -> SDoc
            annotationAnnParen = annotation' (text "EpAnn AnnParen")

            annotationTrailingAnn :: EpAnn TrailingAnn -> SDoc
            annotationTrailingAnn = annotation' (text "EpAnn TrailingAnn")

            annotationEpaLocation :: EpAnn EpaLocation -> SDoc
            annotationEpaLocation = annotation' (text "EpAnn EpaLocation")

            annotation' :: forall a. Data a => SDoc -> EpAnn a -> SDoc
            annotation' _tag anns = nested (text $ showConstr (toConstr anns))
              (vcat (map li $ gmapQ showAstDataHtml' anns))

            -- -------------------------

#if MIN_VERSION_ghc(9,9,0)
            srcSpanAnnA :: EpAnn AnnListItem -> SDoc
            srcSpanAnnA = locatedAnn'' (text "SrcSpanAnnA")

#if MIN_VERSION_ghc(9,11,0)
            srcSpanAnnL :: EpAnn (AnnList ()) -> SDoc
#else
            srcSpanAnnL :: EpAnn AnnList -> SDoc
#endif
            srcSpanAnnL = locatedAnn'' (text "SrcSpanAnnL")

            srcSpanAnnP :: EpAnn AnnPragma -> SDoc
            srcSpanAnnP = locatedAnn'' (text "SrcSpanAnnP")

            srcSpanAnnC :: EpAnn AnnContext -> SDoc
            srcSpanAnnC = locatedAnn'' (text "SrcSpanAnnC")

            srcSpanAnnN :: EpAnn NameAnn -> SDoc
            srcSpanAnnN = locatedAnn'' (text "SrcSpanAnnN")

            locatedAnn'' :: forall a. Data a => SDoc -> EpAnn a -> SDoc
            locatedAnn'' tag ss = parens $
              case cast ss of
                Just (ann :: EpAnn a) ->
                      text (showConstr (toConstr ann))
                                          $$ vcat (gmapQ showAstDataHtml' ann)
                Nothing -> text "locatedAnn:unmatched" <+> tag
                           <+> (parens $ text (showConstr (toConstr ss)))
#else
            srcSpanAnnA :: SrcSpanAnn' (EpAnn AnnListItem) -> SDoc
            srcSpanAnnA = locatedAnn'' (text "SrcSpanAnnA")

            srcSpanAnnL :: SrcSpanAnn' (EpAnn AnnList) -> SDoc
            srcSpanAnnL = locatedAnn'' (text "SrcSpanAnnL")

            srcSpanAnnP :: SrcSpanAnn' (EpAnn AnnPragma) -> SDoc
            srcSpanAnnP = locatedAnn'' (text "SrcSpanAnnP")

            srcSpanAnnC :: SrcSpanAnn' (EpAnn AnnContext) -> SDoc
            srcSpanAnnC = locatedAnn'' (text "SrcSpanAnnC")

            srcSpanAnnN :: SrcSpanAnn' (EpAnn NameAnn) -> SDoc
            srcSpanAnnN = locatedAnn'' (text "SrcSpanAnnN")

            locatedAnn'' :: forall a. Data a
              => SDoc -> SrcSpanAnn' a -> SDoc
            locatedAnn'' tag ss =
              case cast ss of
                Just ((SrcSpanAnn ann s) :: SrcSpanAnn' a) ->
                      nested "SrcSpanAnn" (
                                 li(showAstDataHtml' ann)
                              $$ li(srcSpan s))
                Nothing -> text "locatedAnn:unmatched" <+> tag
                           <+> text (showConstr (toConstr ss))
#endif


normalize_newlines :: String -> String
normalize_newlines ('\\':'r':'\\':'n':xs) = '\\':'n':normalize_newlines xs
normalize_newlines (x:xs)                 = x:normalize_newlines xs
normalize_newlines []                     = []

css :: String
css = unlines
  [ "body {background-color: black; color: white ;}"
  , "/* Remove default bullets */"
  , "ul, #myUL {"
  , "  list-style-type: none;"
  , "}"
  , "/* Remove margins and padding from the parent ul */"
  , "#myUL {"
  , "  margin: 0;                       "
  , "  padding: 0;                      "
  , "}                                  "
  , "/* Style the caret/arrow */        "
  , ".caret {                           "
  , "  cursor: pointer;                 "
  , "  user-select: none; /* Prevent text selection */"
  , "}                                  "
  , "/* Create the caret/arrow with a unicode, and style it */"
  , ".caret::before {                   "
  , "  content: \"\\25B6 \";                "
  , "  color: white;                    "
  , "  display: inline-block;           "
  , "  margin-right: 6px;               "
  , "}                                  "
  , "/* Rotate the caret/arrow icon when clicked on (using JavaScript) */"
  , ".caret-down::before {              "
  , "  transform: rotate(90deg);        "
  , "}                                  "
  , "/* Hide the nested list */         "
  , ".nested {                          "
  , "  display: none;                   "
  , "}                                  "
  , "/* Show the nested list when the user clicks on the caret/arrow (with JavaScript) */"
  , ".active {                          "
  , "  display: block;}"
  ]

js :: String
js = unlines
  [ "var toggler = document.getElementsByClassName(\"caret\");"
  , "var i;"
  , "for (i = 0; i < toggler.length; i++) {"
  , "  toggler[i].addEventListener(\"click\", function() {"
  , "    this.parentElement.querySelector(\".nested\").classList.toggle(\"active\");"
  , "    this.classList.toggle(\"caret-down\");"
  , "  }); }"
  ]
