module Language.Dart.Pretty
  ( prettyPrint
  ) where

import Data.Char (toLower)
import Data.List (partition)
import Text.PrettyPrint
import Text.Printf (printf)

import Language.Dart.Syntax

-- FIXME: Don't omit comments

prettyPrint :: Pretty a => a -> String
prettyPrint = show . pretty

parenPrec :: Int -> Int -> Doc -> Doc
parenPrec inheritedPrec currentPrec t
  | inheritedPrec > currentPrec = parens t
  | otherwise                   = t

class Pretty a where
  pretty :: a -> Doc
  pretty = prettyPrec 0

  prettyPrec :: Int -> a -> Doc
  prettyPrec _ = pretty

instance Pretty MapLiteralEntry where
  prettyPrec p (MapLiteralEntry key value) =
    hsep [ prettyPrec p key
         , colon
         , prettyPrec p value
         ]

instance Pretty TypedLiteral where
  prettyPrec p (MapLiteral isConst mTypeArguments entries)  =
    hsep [ optKeyword isConst "const"
         , maybePP p mTypeArguments
         ] <> braces (hsep (ppIntersperse p comma entries))

  prettyPrec p (ListLiteral isConst mTypeArguments elements) =
    hsep [ optKeyword isConst "const"
         , maybePP p mTypeArguments
         ] <> brackets (hsep (ppIntersperse p comma elements))

instance Pretty Literal where
  prettyPrec p (NullLiteral)      = text "null"
  prettyPrec p (BooleanLiteral b) = text . map toLower $ show b
  prettyPrec p (DoubleLiteral d)  = text (show d)
  prettyPrec p (IntegerLiteral i) = text (show i)
  prettyPrec p (TypedLiteral t)   = prettyPrec p t
  prettyPrec p (StringLiteral' s) = prettyPrec p s
  prettyPrec p (SymbolLiteral ts) = pound <> hsep (punctuate period (map text ts))

instance Pretty InterpolationElement where
  prettyPrec p (InterpolationString s) = text s

  prettyPrec p (InterpolationExpression expression) =
    dollar <> braces (prettyPrec p expression)

instance Pretty SingleStringLiteral where
  prettyPrec p (SimpleStringLiteral value) =
    doubleQuotes (text (concatMap escapeString value))

  prettyPrec p (StringInterpolation elements) =
    doubleQuotes (hcat (map (prettyPrec p) elements))

instance Pretty StringLiteral where
  prettyPrec p (SingleStringLiteral' l) = prettyPrec p l

  prettyPrec p (AdjacentStrings strings) =
    hsep (map (prettyPrec p) strings)

instance Pretty Combinator where
  prettyPrec p (ShowCombinator shownNames)  = text "show" <+>
    hsep (ppIntersperse p comma shownNames)

  prettyPrec p (HideCombinator hiddenNames) = text "hide" <+>
    hsep (ppIntersperse p comma hiddenNames)

-- FIXME: Handle configurations
instance Pretty NamespaceDirective where
  prettyPrec p (ExportDirective _ metadata libraryUri _ combinators) =
    ppMetadata p metadata $$
      hsep [ text "export"
           , prettyPrec p libraryUri
           , hsep (ppIntersperse p comma combinators)
           ] <> semi

  prettyPrec p (ImportDirective _ metadata libraryUri _ isDeferred mPrefix combinators) =
    ppMetadata p metadata $$
      hsep [ text "import"
           , prettyPrec  p libraryUri
           , optKeyword isDeferred "deferred"
           , maybe empty ((text "as" <+>) . prettyPrec p) mPrefix
           , hsep (ppIntersperse p comma combinators)
           ] <> semi

instance Pretty UriBasedDirective where
  prettyPrec p (NamespaceDirective nsDir) = prettyPrec p nsDir

  prettyPrec p (PartDirective _ metadata partUri) =
    ppMetadata p metadata $$ prettyPrec p partUri <> semi

instance Pretty Directive where
  prettyPrec p (UriBasedDirective dir) = prettyPrec p dir

  prettyPrec p (PartOfDirective _ metadata libraryName) =
    ppMetadata p metadata $$
      text "part of" <> prettyPrec p libraryName <> semi

  prettyPrec p (LibraryDirective _ metadata libraryName) =
    ppMetadata p metadata $$
      text "library" <> prettyPrec p libraryName <> semi

instance Pretty AsyncModifier where
  prettyPrec p AsyncStar  = text "async*"
  prettyPrec p Async      = text "async"
  prettyPrec p SyncStar   = text "sync*"
  prettyPrec p Sync       = empty

instance Pretty NormalFormalParameter where
  prettyPrec p (FunctionTypedFormalParameter _ metadata mReturnType identifier mTypeParameters parameters) =
    ppMetadata p metadata $$
      hsep [ maybePP p mReturnType
           , hcat [ prettyPrec p identifier
                  , maybePP p mTypeParameters
                  , prettyPrec p parameters
                  ]
           ]

  prettyPrec p (FieldFormalParameter _ metadata kind explicitThis identifier) =
    ppMetadata p metadata $$
      hsep [ maybePP p kind
           , opt explicitThis (text "this.") <> prettyPrec p identifier
           ]

  prettyPrec p (SimpleFormalParameter _ metadata kind identifier) =
    ppMetadata p metadata $$
      hsep [ prettyPrec p kind
           , prettyPrec p identifier
           ]

instance Pretty FormalParameter where
  prettyPrec p (NormalFormalParameter' parameter) = prettyPrec p parameter

  prettyPrec p (DefaultFormalParameter parameter kind mDefaultValue) =
    hsep [ prettyPrec p parameter
         , maybe empty ((separator kind <+>) . prettyPrec p) mDefaultValue
         ]
    where separator Positional = equals
          separator Named = colon

instance Pretty FormalParameterList where
  prettyPrec p (FormalParameterList parameters) =
    let (normals, defaults) = partition isNormal parameters
     in parens $ hcat [ hsep (ppIntersperse p comma normals)
                      , opt (not $ null defaults) (comma <+> (matchingBracket (head defaults)) (hsep (ppIntersperse p comma defaults)))
                      ]
    where isNormal (NormalFormalParameter' _) = True
          isNormal _ = False
          matchingBracket (DefaultFormalParameter _ Positional _) = brackets
          matchingBracket _ = braces

instance Pretty ConstructorName where
  prettyPrec p (ConstructorName type' mName) =
    hsep [ prettyPrec p type'
         , maybe empty ((period <>) . prettyPrec p) mName
         ]

instance Pretty SimpleIdentifier where
  prettyPrec _ (SimpleIdentifier token) = text token

instance Pretty LibraryIdentifier where
  prettyPrec p (LibraryIdentifier components) =
    hcat (ppIntersperse p period components)

instance Pretty Identifier where
  prettyPrec p (SimpleIdentifier' simpleId) = prettyPrec p simpleId

  prettyPrec p (PrefixedIdentifier prefix identifier) =
    hcat [ prettyPrec p prefix
         , period
         , prettyPrec p identifier
         ]

  prettyPrec p (LibraryIdentifier' libraryName) = prettyPrec p libraryName

instance Pretty TypeArgumentList where
  prettyPrec p (TypeArgumentList args) =
    angleBrackets (hsep (ppIntersperse p comma args))

instance Pretty TypeName where
  prettyPrec p (TypeName name typeArgList) = prettyPrec p name <> maybePP p typeArgList

instance Pretty PropertyKeyword where
  prettyPrec p Get   = text "get"
  prettyPrec p Set   = text "set"
  prettyPrec p Empty = empty

instance Pretty ArgumentList where
  prettyPrec p (ArgumentList args) =
    parens (hsep (ppIntersperse p comma args))

instance Pretty Annotation where
  prettyPrec p (Annotation name mConstructorName mArguments) =
    hcat [ at
         , prettyPrec p name
         , maybe empty ((period <>) . prettyPrec p) mConstructorName
         , maybePP p mArguments
         ]

instance Pretty ExtendsClause where
  prettyPrec p (ExtendsClause superclass) = text "extends" <+>
    prettyPrec p superclass

instance Pretty WithClause where
  prettyPrec p (WithClause mixinTypes) = text "with" <+>
    hsep (ppIntersperse p comma mixinTypes)

instance Pretty ImplementsClause where
  prettyPrec p (ImplementsClause interfaces) = text "implements" <+>
    hsep (ppIntersperse p comma interfaces)

instance Pretty TypeAlias where
  prettyPrec p (ClassTypeAlias _ metadata name mTypeParameters isAbstract superclass withClause mImplementsClause) =
    ppMetadata p metadata $$
      hsep [ optKeyword isAbstract "abstract"
           , text "class"
           , hcat [ prettyPrec p name
                  , maybePP p mTypeParameters
                  ]
           , equals
           , prettyPrec p superclass
           , prettyPrec p withClause
           , maybePP p mImplementsClause
           ] <> semi

  prettyPrec p (FunctionTypeAlias _ metadata mReturnType name mTypeParameters parameters) =
    ppMetadata p metadata $$
      hsep [ text "typedef"
           , maybePP p mReturnType
           , hcat [ prettyPrec p name
                  , maybePP p mTypeParameters
                  , prettyPrec p parameters
                  ]
           ] <> semi

instance Pretty EnumConstantDeclaration where
  prettyPrec p (EnumConstantDeclaration _ metadata name) =
    ppMetadata p metadata $$ prettyPrec p name

instance Pretty TypeParameter where
  prettyPrec p (TypeParameter _ metadata name mBound) =
    ppMetadata p metadata $$
      hsep [ prettyPrec p name
           , maybe empty ((text "extends" <+>) . prettyPrec p) mBound
           ]

instance Pretty TypeParameterList where
  prettyPrec p (TypeParameterList typeParameters) =
    angleBrackets (hsep (ppIntersperse p comma typeParameters))

instance Pretty Label where
  prettyPrec p (Label label) = prettyPrec p label <> char ':'

instance Pretty FinalConstVarOrType where
  prettyPrec p (FCVTFinal type')  = text "final" <+> prettyPrec p type'
  prettyPrec p (FCVTConst type')  = text "const" <+> prettyPrec p type'
  prettyPrec p (FCVTType type')   = prettyPrec p type'
  prettyPrec p FCVTVar            = text "var"

instance Pretty FinalVarOrType where
  prettyPrec p (FVTFinal type') = text "final" <+> prettyPrec p type'
  prettyPrec p (FVTType type')  = prettyPrec p type'
  prettyPrec p FVTVar           = text "var"

instance Pretty VariableDeclaration where
  prettyPrec p (VariableDeclaration name mInitializer) =
    hsep [ prettyPrec p name
         , maybe empty ((equals <+>) . prettyPrec p) mInitializer
         ]

instance Pretty VariableDeclarationList where
  prettyPrec p (VariableDeclarationList _ metadata kind variables) =
    ppMetadata p metadata $$
      prettyPrec p kind <+> hsep (ppIntersperse p comma variables)

instance Pretty CatchClause where
  prettyPrec p (CatchClause mExceptionType exceptionParameter mStackTraceParameter body) =
    case mExceptionType of
      Nothing -> text "catch" <+> parens (ppExceptionParameter p exceptionParameter mStackTraceParameter) $$ prettyPrec p body
      Just exceptionType -> text "on" <+> prettyPrec p exceptionType $$ prettyPrec p body
    where ppExceptionParameter p ep Nothing = prettyPrec p ep
          ppExceptionParameter p ep (Just stp) = hsep (ppIntersperse p comma [ep, stp])

instance Pretty SwitchMember where
  prettyPrec p (SwitchCase labels expression statements) =
    vcat (map (prettyPrec p) labels) $$
      vcat (ppSwitchCase p expression : map (nest 2 . prettyPrec p) statements)
    where ppSwitchCase p expression = text "case" <+> prettyPrec p expression <> colon

  prettyPrec p (SwitchDefault labels statements) =
    vcat (map (prettyPrec p) labels) $$
      vcat (ppSwitchDefault : map (nest 2 . prettyPrec p) statements)
    where ppSwitchDefault = text "default" <> colon

instance Pretty DeclaredIdentifier where
  prettyPrec p (DeclaredIdentifier _ metadata kind identifier) =
    ppMetadata p metadata $$
      hsep [ prettyPrec p kind
           , prettyPrec p identifier
           ]

instance Pretty Statement where
  prettyPrec p (Block' block) = prettyPrec p block

  prettyPrec p (VariableDeclarationStatement variableList) =
    prettyPrec p variableList <> semi

  prettyPrec p (ForStatement mVariableList mInitialization mCondition updaters body) =
    text "for" <+> (parens $ hsep [ maybe empty ((<> semi) . prettyPrec p) mVariableList
                                  , maybe empty ((<> semi) . prettyPrec p) mInitialization
                                  , maybePP p mCondition <> semi
                                  , hsep (ppIntersperse p comma updaters)
                                  ]) $+$ prettyNestedStmt p body

  prettyPrec p (ForEachStatementWithDeclaration isAwait loopVariable iterator body) = undefined
    hsep [ optKeyword isAwait "await"
         , text "for"
         , parens $ hsep [ prettyPrec p loopVariable
                         , text "in"
                         , prettyPrec p iterator
                         ]
         , prettyPrec p body
         ]

  prettyPrec p (ForEachStatementWithReference isAwait identifier iterator body) =
    hsep [ optKeyword isAwait "await"
         , text "for"
         , parens $ hsep [ prettyPrec p identifier
                         , text "in"
                         , prettyPrec p iterator
                         ]
         , prettyPrec p body
         ]

  prettyPrec p (WhileStatement condition body) =
    text "while" <+> parens (prettyPrec p condition) $+$ prettyNestedStmt 0 body

  prettyPrec p (DoStatement body condition) =
    text "do" $+$ prettyPrec p body <+> text "while" <+> parens (prettyPrec p condition) <> semi

  prettyPrec p (SwitchStatement expression members) =
    text "switch" <+> parens (prettyPrec p expression)
      $$ braceBlock (map (prettyPrec p) members)

  prettyPrec p (IfStatement condition thenStatement mElseStatement) =
    text "if" <+> parens (prettyPrec p condition) $+$
      prettyNestedStmt 0 thenStatement $+$ ppElseStatement mElseStatement
    where ppElseStatement Nothing = empty
          ppElseStatement (Just elseStatement) = text "else" $+$ prettyNestedStmt 0 elseStatement

  prettyPrec p (TryStatement body catchCaluses mFinallyBlock) =
    text "try" $$ prettyPrec p body $$
      vcat (map (prettyPrec p) catchCaluses ++ [ppFinally mFinallyBlock])
    where ppFinally Nothing = empty
          ppFinally (Just finally) = text "finally" $$ prettyPrec p finally

  prettyPrec p (BreakStatement mLabel) =
    text "break" <+> maybePP p mLabel <> semi

  prettyPrec p (ContinueStatement mLabel) =
    text "continue" <+> maybePP p mLabel <> semi

  prettyPrec p (ReturnStatement mExpression) =
    text "return" <+> maybePP p mExpression <> semi

  prettyPrec p (ExpressionStatement expression) =
    prettyPrec p expression <> semi

  prettyPrec p (FunctionDeclarationStatement functionDeclaration) =
    prettyPrec p functionDeclaration <> semi

  -- FIXME: Show message
  prettyPrec p (AssertStatement condition _) =
    hcat [ text "assert"
         , parens (prettyPrec p condition)
         , semi
         ]

  prettyPrec p (YieldStatement isStar expression) =
    hsep [ text "yield"
         , opt isStar star
         , prettyPrec p expression
         ] <> semi

  prettyPrec p (EmptyStatement) = semi

  prettyPrec p (LabeledStatement labels statement) =
    vcat (map (prettyPrec p) labels) $$
      prettyPrec p statement

instance Pretty Block where
  prettyPrec p (Block statements) = braceBlock (map (prettyPrec p) statements)

instance Pretty NewOrConst where
  prettyPrec p NCNew    = text "new"
  prettyPrec p NCConst  = text "const"

instance Pretty InvocationExpression where
  prettyPrec p (FunctionExpressionInvocation function mTypeArguments argumentList) =
    hcat [ prettyPrec p function
         , maybePP p mTypeArguments
         , prettyPrec p argumentList
         ]

  prettyPrec p (MethodInvocation mTarget methodName mTypeArguments argumentList) =
    hcat [ maybe empty ((<> period) . prettyPrec p) mTarget
         , prettyPrec p methodName
         , maybePP p mTypeArguments
         , prettyPrec p argumentList
         ]

instance Pretty Expression where
  prettyPrec p (Literal' literal) = prettyPrec p literal

  prettyPrec p (Identifier' identifier) = prettyPrec p identifier

  prettyPrec p (PrefixExpression  operator operand) =
    parenPrec p 15 $ text operator <> prettyPrec 15 operand

  prettyPrec p (PostfixExpression operand operator) =
    parenPrec p 16 $ prettyPrec 15 operand <> text operator

  prettyPrec p (BinaryExpression leftOperand operator rightOperand) =
    let prec = opPrec operator
     in parenPrec p prec $ hsep [ prettyPrec prec leftOperand
                                , text operator
                                , prettyPrec prec rightOperand
                                ]

  prettyPrec p (AssignmentExpression leftHandSide operator rightHandSide) =
    hsep [ prettyPrec p leftHandSide
         , text operator
         , prettyPrec p rightHandSide
         ]

  prettyPrec p (FunctionExpression' functionExpression) = prettyPrec p functionExpression

  prettyPrec p (InstanceCreationExpression newOrConst constructorName argumentList) =
    hsep [ prettyPrec p newOrConst
         , hcat [ prettyPrec p constructorName
                , prettyPrec p argumentList
                ]
         ]

  prettyPrec p (AsExpression expression type') =
    parenPrec p 8 $ hsep [ prettyPrec 8 expression
                         , text "as"
                         , prettyPrec 8 type'
                         ]

  prettyPrec p (IsExpression expression isNot type') =
    parenPrec p 8 $ hsep [ prettyPrec 8 expression
                         , text "is"
                         , opt isNot exclamation
                         , prettyPrec 8 type'
                         ]

  prettyPrec p (ThrowExpression expression) = text "throw" <+> prettyPrec p expression
  prettyPrec p (RethrowExpression)          = text "rethrow"

  prettyPrec p (ThisExpression)  = text "this"
  prettyPrec p (SuperExpression) = text "super"

  prettyPrec p (ParenthesizedExpression expression) = parens (prettyPrec p expression)

  prettyPrec p (PropertyAccess target propertyName) =
    hcat [ prettyPrec p target
         , period
         , prettyPrec p propertyName
         ]

  prettyPrec p (NamedExpression name expression) =
    hsep [ prettyPrec p name <> colon
         , prettyPrec p expression
         ]

  prettyPrec p (InvocationExpression invocationExpression) =
    prettyPrec p invocationExpression

  prettyPrec p (ConditionalExpression condition thenExpression elseExpression) =
    parenPrec p 3 $ hsep [ prettyPrec 3 condition
                         , questionMark
                         , prettyPrec p thenExpression
                         , colon
                         , prettyPrec 3 elseExpression
                         ]

  prettyPrec p (CascadeExpression target cascadeSections) =
    let sections = target:cascadeSections
     in parenPrec p 2 $ hsep (ppIntersperse 2 (text "..") sections)

  prettyPrec p (IndexExpressionForCasecade index) = brackets (prettyPrec p index)

  prettyPrec p (IndexExpressionForTarget target index) =
    prettyPrec p target <> brackets (prettyPrec p index)

  prettyPrec p (AwaitExpression expression) =
    hsep [ text "await"
         , prettyPrec p expression
         ]

instance Pretty FunctionBody where
  prettyPrec p (BlockFunctionBody asyncModifier block) =
    prettyPrec p asyncModifier <+> prettyPrec p block

  prettyPrec p (EmptyFunctionBody) = semi

  prettyPrec p (ExpressionFunctionBody isAsync expression) =
    hsep [ optKeyword isAsync "async"
         , text "=>"
         , prettyPrec p expression
         ] <> semi

  prettyPrec p (NativeFunctionBody stringLiteral) =
    text "native" <+> prettyPrec p stringLiteral <> semi

instance Pretty FunctionExpression where
  prettyPrec p (FunctionExpression mTypeParameters parameters body) =
    case body of
      BlockFunctionBody _ _      -> ppParams $$  prettyPrec p body
      ExpressionFunctionBody _ _ -> ppParams <+> prettyPrec p body
      _                          -> ppParams <> semi
    where ppParams = maybePP p mTypeParameters <> prettyPrec p parameters

instance Pretty FunctionDeclaration where
  prettyPrec p (FunctionDeclaration _ metadata isExternal mReturnType propertyKeyword name (FunctionExpression mTypeParameters parameters body)) =
    ppMetadata p metadata $$
      hsep [ optKeyword isExternal "external"
           , maybePP p mReturnType
           , prettyPrec p propertyKeyword
           , hcat [ prettyPrec p name
                  , maybePP p mTypeParameters
                  , prettyPrec p parameters
                  ]
           ] $$$ prettyPrec p body
   where ($$$) = case body of
                   (ExpressionFunctionBody _ _) -> (<+>)
                   (BlockFunctionBody _ _) -> ($$)
                   _ -> (<>)

instance Pretty ConstructorInitializer where
  prettyPrec p (RedirectingConstructorInvocation mConstructorName argumentList) =
    hcat [ text "this"
         , maybe empty ((period <>) . prettyPrec p) mConstructorName
         , prettyPrec p argumentList
         ]

  prettyPrec p (ConstructorFieldInitializer explicitThis fieldName expression) =
    hsep [ hcat [ opt explicitThis (text "this.")
                , prettyPrec p fieldName
                ]
         , equals
         , prettyPrec p expression
         ]

  prettyPrec p (SuperConstructorInvocation mConstructorName argumentList) =
    hcat [ text "super"
         , maybe empty ((period <>) . prettyPrec p) mConstructorName
         , prettyPrec p argumentList
         ]

instance Pretty MethodModifier where
  prettyPrec _ Abstract = text "abstract"
  prettyPrec _ Static   = text "static"

instance Pretty NamedCompilationUnitMember where
  prettyPrec p (FunctionDeclaration' funDecl) = prettyPrec p funDecl

  prettyPrec p (TypeAlias typeAlias) = prettyPrec p typeAlias

  prettyPrec p (EnumDeclaration _ metadata name constants) =
    ppMetadata p metadata $$
      hsep [ text "enum"
           , prettyPrec p name
           ] $$ braceBlock (ppIntersperse p comma constants)

  prettyPrec p (ClassDeclaration _ metadata isAbstract name mTypeParameters mExtendsClause mWithClause mImplementsClause members) =
    ppMetadata p metadata $$
      hsep [ optKeyword isAbstract "abstract"
           , text "class"
           , prettyPrec p name <> maybePP p mTypeParameters
           , maybePP p mExtendsClause
           , maybePP p mWithClause
           , maybePP p mImplementsClause
           ] $$ braceBlock (map (prettyPrec p) members)

instance Pretty CompilationUnitMember where
  prettyPrec p (TopLevelVariableDeclaration _ metadata variableList) =
    ppMetadata p metadata $$ prettyPrec p variableList

  prettyPrec p (NamedCompilationUnitMember member) = prettyPrec p member

instance Pretty ScriptTag where
  prettyPrec _ (ScriptTag token) = text token

instance Pretty CompilationUnit where
  prettyPrec p (CompilationUnit mScriptTag dirs members) =
    vcat $ [maybePP p mScriptTag] ++ map (prettyPrec p) dirs ++ map (prettyPrec p) members

ppMetadata :: Int -> [Annotation] -> Doc
ppMetadata p metadata = vcat (map (prettyPrec p) metadata)

instance Pretty ClassMember where
  prettyPrec p (ConstructorDeclaration _ metadata isExternal isConst isFactory className mName parameters initializers mRedirectedConstructor mBody) =
    ppMetadata p metadata $$
      case mRedirectedConstructor of
        Nothing -> hsep [ optKeyword isExternal "external"
                        , optKeyword isConst "const"
                        , optKeyword isFactory "factory"
                        , hcat [ prettyPrec p className
                               , maybe empty ((period <>) . prettyPrec p) mName
                               , prettyPrec p parameters
                               ]
                        , opt (not $ null initializers) (colon <+> vcat (ppIntersperse p comma initializers))
                        ] <> maybe semi (const empty) mBody $$ maybePP p mBody
        Just redirectedConstructor -> hsep [ optKeyword isConst "const"
                                           , text "factory"
                                           , hcat [ prettyPrec p className
                                                  , maybe empty ((period <>) . prettyPrec p) mName
                                                  , prettyPrec p parameters
                                                  ]
                                           , equals
                                           , prettyPrec p redirectedConstructor <> semi
                                           ]

  prettyPrec p (MethodDeclaration _ metadata isExternal methodModifier mReturnType propertyKeyword isOperator name mTypeParameters mParameters body) =
    ppMetadata p metadata $$
      hsep [ optKeyword isExternal "external"
           , maybePP p methodModifier
           , maybePP p mReturnType
           , prettyPrec p propertyKeyword
           , optKeyword isOperator "operator"
           , hcat [ prettyPrec p name, maybePP p mTypeParameters, maybePP p mParameters]
           ] $$ prettyPrec p body
  prettyPrec p (FieldDeclaration _ metadata isStatic fieldList) =
    ppMetadata p metadata $$
      optKeyword isStatic "static" <+> prettyPrec p fieldList <> semi

ppIntersperse :: (Pretty a) => Int -> Doc -> [a] -> [Doc]
ppIntersperse p s as = punctuate s (map (prettyPrec p) as)

-----------------------------------------------------------------------
---- Help functionality
prettyNestedStmt :: Int -> Statement -> Doc
prettyNestedStmt prio b@(Block' _) = prettyPrec prio b
prettyNestedStmt prio s = nest 2 (prettyPrec prio s)

maybePP :: Pretty a => Int -> Maybe a -> Doc
maybePP p = maybe empty (prettyPrec p)

pound :: Doc
pound = char '#'

period :: Doc
period = char '.'

star :: Doc
star = char '*'

at :: Doc
at = char '@'

dollar :: Doc
dollar = char '$'

questionMark :: Doc
questionMark = char '?'

exclamation :: Doc
exclamation = char '!'

angleBrackets :: Doc -> Doc -- ^ Wrap document in @<...>@
angleBrackets p = char '<' <> p <> char '>'

opt :: Bool -> Doc -> Doc
opt x a = if x then a else empty

optKeyword :: Bool -> String -> Doc
optKeyword x s = opt x (text s)

block :: Char -> Char -> [Doc] -> Doc
block open close xs = char open
    $+$ nest 2 (vcat xs)
    $+$ char close

braceBlock :: [Doc] -> Doc
braceBlock = block '{' '}'

bracketBlock :: [Doc] -> Doc
bracketBlock = block '[' ']'

escapeGeneral :: Char -> String
escapeGeneral '\b' = "\\b"
escapeGeneral '\t' = "\\t"
escapeGeneral '\n' = "\\n"
escapeGeneral '\f' = "\\f"
escapeGeneral '\r' = "\\r"
escapeGeneral '\\' = "\\\\"
escapeGeneral c | c >= ' ' && c < '\DEL' = [c]
                | c <= '\xFFFF' = printf "\\u%04x" (fromEnum c)
                | otherwise = error $ "Language.Dart.Pretty.escapeGeneral: Char " ++ show c ++ " too large for Dart char"

escapeChar :: Char -> String
escapeChar '\'' = "\\'"
escapeChar c = escapeGeneral c

escapeString :: Char -> String
escapeString '"' = "\\\""
escapeString c | c <= '\xFFFF' = escapeGeneral c
               | otherwise = escapeGeneral lead ++ escapeGeneral trail
                   where c' = fromEnum c - 0x010000
                         lead = toEnum $ 0xD800 + c' `div` 0x0400
                         trail = toEnum $ 0xDC00 + c' `mod` 0x0400

-- Operator precedence of binary operators
-- 20.2 of Dart Programming Language Specification
-- http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-408.pdf
opPrec :: String -> Int
opPrec "*"  = 14
opPrec "/"  = 14
opPrec "~/" = 14
opPrec "%"  = 14
opPrec "+"  = 13
opPrec "-"  = 13
opPrec "<<" = 12
opPrec ">>" = 12
opPrec "&"  = 11
opPrec "^"  = 10
opPrec "|"  = 9
opPrec "<"  = 8
opPrec ">"  = 8
opPrec "<=" = 8
opPrec ">=" = 8
opPrec "==" = 7
opPrec "!=" = 7
opPrec "&&" = 6
opPrec "||" = 5
opPrec "??" = 4