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

    {-# OPTIONS_GHC -fno-warn-deprecations #-}

    #include "version-compatibility-macros.h"

    -- | This module shows how to write a custom prettyprinter backend, based on
    -- directly converting a 'SimpleDocStream' to an output format using a stack
    -- machine. For a tree serialization approach, which may be more suitable for
    -- certain output formats, see
    -- "Data.Text.Prettyprint.Doc.Render.Tutorials.TreeRenderingTutorial".
    --
    -- Rendering to ANSI terminal with colors is an important use case for stack
    -- machine based rendering.
    --
    -- The module is written to be readable top-to-bottom in both Haddock and raw
    -- source form.
    module Data.Text.Prettyprint.Doc.Render.Tutorials.StackMachineTutorial
        {-# DEPRECATED "Writing your own stack machine is probably more efficient and customizable; also consider using »renderSimplyDecorated(A)« instead" #-}
        (
        module Data.Text.Prettyprint.Doc.Render.Tutorials.StackMachineTutorial
    ) where

    import qualified Data.Text              as T
    import qualified Data.Text.Lazy         as TL
    import qualified Data.Text.Lazy.Builder as TLB

    import Data.Text.Prettyprint.Doc
    import Data.Text.Prettyprint.Doc.Render.Util.Panic
    import Data.Text.Prettyprint.Doc.Render.Util.StackMachine

    #if !(APPLICATIVE_MONAD)
    import Control.Applicative
    #endif

    -- $standalone-text
    --
    -- = The type of available markup
    --
    -- First, we define a set of valid annotations must be defined, with the goal of
    -- defining a @'Doc' 'SimpleHtml'@. We will later define how to convert this to
    -- the output format ('TL.Text').

    data SimpleHtml = Bold | Italics | Color Color | Paragraph | Headline
    data Color = Red | Green | Blue

    -- $standalone-text
    --
    -- == Conveinence definitions

    bold, italics, paragraph, headline :: Doc SimpleHtml -> Doc SimpleHtml
    bold = annotate Bold
    italics = annotate Italics
    paragraph = annotate Paragraph
    headline = annotate Headline

    color :: Color -> Doc SimpleHtml -> Doc SimpleHtml
    color c = annotate (Color c)

    -- $standalone-text
    --
    -- = The rendering algorithm
    --
    -- With the annotation definitions out of the way, we can now define a
    -- conversion function from 'SimpleDocStream' annotated with our 'SimpleHtml' to the
    -- final 'TL.Text' representation.
    --
    -- There are two ways to render this; the simpler one is just using
    -- 'renderSimplyDecorated'. However, some output formats require more
    -- complicated functionality, so we explore this explicitly with a simple
    -- example below. An example for something more complicated is ANSI terminal
    -- rendering, where on popping we need to regenerate the previous style,
    -- requiring a pop (discard current style) followed by a peek (regenerate
    -- previous style).

    -- | The 'StackMachine' type defines a stack machine suitable for many rendering
    -- needs. It has two auxiliary parameters: the type of the end result, and the
    -- type of the document’s annotations.
    --
    -- Most 'StackMachine' creations will look like this definition: a recursive
    -- walk through the 'SimpleDocStream', pushing styles on the stack and popping
    -- them off again, and writing raw output.
    --
    -- The equivalent to this in the tree based rendering approach is
    -- 'Data.Text.Prettyprint.Doc.Render.Tutorials.TreeRenderingTutorial.renderTree'.
    renderStackMachine :: SimpleDocStream SimpleHtml -> StackMachine TLB.Builder SimpleHtml ()
    renderStackMachine = \case
        SFail -> panicUncaughtFail
        SEmpty -> pure ()
        SChar c x -> do
            writeOutput (TLB.singleton c)
            renderStackMachine x
        SText _l t x -> do
            writeOutput (TLB.fromText t)
            renderStackMachine x
        SLine i x -> do
            writeOutput (TLB.singleton '\n')
            writeOutput (TLB.fromText (T.replicate i " "))
            renderStackMachine x
        SAnnPush s x -> do
            pushStyle s
            writeOutput (fst (htmlTag s))
            renderStackMachine x
        SAnnPop x -> do
            s <- unsafePopStyle
            writeOutput (snd (htmlTag s))
            renderStackMachine x

    -- | Convert a 'SimpleHtml' annotation to a pair of opening and closing tags.
    -- This is where the translation of style to raw output happens.
    htmlTag :: SimpleHtml -> (TLB.Builder, TLB.Builder)
    htmlTag = \case
        Bold      -> ("<strong>", "</strong>")
        Italics   -> ("<em>", "</em>")
        Color c   -> ("<span style=\"color: " <> hexCode c <> "\">", "</span>")
        Paragraph -> ("<p>", "</p>")
        Headline  -> ("<h1>", "</h1>")
      where
        hexCode :: Color -> TLB.Builder
        hexCode = \case
            Red   -> "#f00"
            Green -> "#0f0"
            Blue  -> "#00f"

    -- | We can now wrap our stack machine definition from 'renderStackMachine' in a
    -- nicer interface; on successful conversion, we run the builder to give us the
    -- final 'TL.Text', and before we do that we check that the style stack is empty
    -- (i.e. there are no unmatched style applications) after the machine is run.
    --
    -- This function does only a bit of plumbing around 'renderStackMachine', and is
    -- the main API function of a stack machine renderer. The tree renderer
    -- equivalent to this is
    -- 'Data.Text.Prettyprint.Doc.Render.Tutorials.TreeRenderingTutorial.render'.
    render :: SimpleDocStream SimpleHtml -> TL.Text
    render doc
      = let (resultBuilder, remainingStyles) = execStackMachine [] (renderStackMachine doc)
        in if null remainingStyles
            then TLB.toLazyText resultBuilder
            else error ("There are "
                        <> show (length remainingStyles)
                        <> " unpaired styles! Please report this as a bug.")

    -- $standalone-text
    --
    -- = Example invocation
    --
    -- We can now render an example document using our definitions:
    --
    -- >>> :set -XOverloadedStrings
    -- >>> import qualified Data.Text.Lazy.IO as TL
    -- >>> :{
    -- >>> let go = TL.putStrLn . render . layoutPretty defaultLayoutOptions
    -- >>> in go (vsep
    -- >>>     [ headline "Example document"
    -- >>>     , paragraph ("This is a" <+> color Red "paragraph" <> comma)
    -- >>>     , paragraph ("and" <+> bold "this text is bold.")
    -- >>>     ])
    -- >>> :}
    -- <h1>Example document</h1>
    -- <p>This is a <span style="color: #f00">paragraph</span>,</p>
    -- <p>and <strong>this text is bold.</strong></p>