module Michelson.Printer.Util
  ( RenderDoc(..)
  , printDoc
  , renderOps
  , renderOpsList
  , spaces
  , wrapInParens
  , buildRenderDoc
  ) where

import qualified Data.Text.Lazy as LT
import Data.Text.Lazy.Builder (Builder)
import Text.PrettyPrint.Leijen.Text
  (Doc, SimpleDoc, braces, displayB, displayT, hcat, isEmpty, parens, punctuate, renderOneLine,
  semi, space, vcat, (<+>))

-- | Generalize converting a type into a
-- Text.PrettyPrint.Leijen.Text.Doc. Used to pretty print Michelson code
-- and define Fmt.Buildable instances.
class RenderDoc a where
  renderDoc :: a -> Doc
  -- | Whether a value can be represented in Michelson code.
  -- Normally either all values of some type are renderable or not renderable.
  -- However, in case of instructions we have extra instructions which should
  -- not be rendered.
  -- Note: it's not suficcient to just return 'mempty' for such instructions,
  -- because sometimes we want to print lists of instructions and we need to
  -- ignore them complete (to avoid putting redundant separators).
  isRenderable :: a -> Bool
  isRenderable _ = True

-- | Convert 'Doc' to 'Text' with a line width of 80.
printDoc :: Doc -> LT.Text
printDoc = displayT . doRender

-- | Generic way to render the different op types that get passed
-- to a contract.
renderOps :: (RenderDoc op) => Bool -> NonEmpty op -> Doc
renderOps oneLine = renderOpsList oneLine . toList

spacecat :: NonEmpty Doc -> Doc
spacecat = foldr (<+>) mempty

renderOpsList :: (RenderDoc op) => Bool -> [op] -> Doc
renderOpsList oneLine ops =
  braces $ cat' $ punctuate semi (renderDoc <$> filter isRenderable ops)
  where
    cat' = if oneLine then maybe "" spacecat . nonEmpty else vcat

-- | Create a specific number of spaces.
spaces :: Int -> Doc
spaces x = hcat $ replicate x space

-- | Wrap documents in parentheses if there are two or more in the list.
wrapInParens :: NonEmpty Doc -> Doc
wrapInParens ds =
  if (length $ filter (not . isEmpty) (toList ds)) > 1
    then parens $ foldr (<+>) mempty ds
    else foldr (<+>) mempty ds

-- | Turn something that is instance of `RenderDoc` into a `Builder`.
-- It's formatted the same way as `printDoc` formats docs.
buildRenderDoc :: RenderDoc a => a -> Builder
buildRenderDoc = displayB . doRender . renderDoc

doRender :: Doc -> SimpleDoc
doRender = renderOneLine