{-|
Description : Rearranging the comments
-}
module Language.Haskell.Formatter.Process.FormatComments (formatComments) where
import qualified Data.Function as Function
import qualified Data.Monoid as Monoid
import qualified Language.Haskell.Formatter.ExactCode as ExactCode
import qualified Language.Haskell.Formatter.Location as Location
import qualified Language.Haskell.Formatter.Process.AttachComments
       as AttachComments
import qualified Language.Haskell.Formatter.Process.Code as Code
import qualified Language.Haskell.Formatter.Process.Note as Note
import qualified Language.Haskell.Formatter.Result as Result
import qualified Language.Haskell.Formatter.Style as Style
import qualified Language.Haskell.Formatter.Toolkit.ListTool as ListTool
import qualified Language.Haskell.Formatter.Toolkit.Visit as Visit

formatComments ::
               Style.Style ->
                 Code.LocatableCommentableCode ->
                   Result.Result Code.LocatableCommentableCode
formatComments style locatableCommentable
  = do locatableCommentable' <- mergeImpliedComments style locatableCommentable
       return . indentToLineStart $
         mergeSuccessiveEmptyLines style locatableCommentable'

mergeImpliedComments ::
                     Style.Style ->
                       Code.LocatableCommentableCode ->
                         Result.Result Code.LocatableCommentableCode
mergeImpliedComments style locatableCommentable
  = do impliedCommentable <- commentsImpliedByLocations style locatable
       commentable' <- commentsDifference style commentable impliedCommentable
       Code.tryZipLocationsComments locatable commentable'
  where locatable = Code.dropComments locatableCommentable
        commentable = Code.dropLocations locatableCommentable

commentsImpliedByLocations ::
                           Style.Style ->
                             Code.LocatableCode ->
                               Result.Result Code.CommentableCode
commentsImpliedByLocations style locatable
  = AttachComments.attachComments style exact
  where exact = ExactCode.create locatable comments
        comments = []

commentsDifference ::
                   Style.Style ->
                     Code.CommentableCode ->
                       Code.CommentableCode ->
                         Result.Result Code.CommentableCode
commentsDifference style = Code.tryZipCode minus
  where minus mixed implied
          = Note.createCommentNote commentsBefore commentsAfter
          where commentsBefore = difference Note.commentsBefore
                difference getComments
                  = Function.on (boxesDifference style) getComments mixed
                      implied
                commentsAfter
                  = reverse . difference $ reverse . Note.commentsAfter

boxesDifference ::
                Style.Style ->
                  [Note.CommentBox] -> [Note.CommentBox] -> [Note.CommentBox]
boxesDifference style mixed implied = Monoid.mappend difference mixedRest
  where difference = replicate differenceCount Note.EmptyLine
        differenceCount
          = if mixedCount <= impliedCount then 0 else
              clip mixedCount - impliedCount
        mixedCount = length mixedEmptyLines
        (mixedEmptyLines, mixedRest) = span isEmptyLine mixed
        impliedCount = length implied
        clip = min successiveEmptyLinesLimit
        successiveEmptyLinesLimit = Style.successiveEmptyLinesLimit style

isEmptyLine :: Note.CommentBox -> Bool
isEmptyLine (Note.ActualComment _) = False
isEmptyLine Note.EmptyLine = True

indentToLineStart ::
                  Code.LocatableCommentableCode -> Code.LocatableCommentableCode
indentToLineStart locatableCommentable = locatableCommentable'
  where (_, locatableCommentable')
          = Visit.mapAccumulateLeftWithCreation move startPosition
              locatableCommentable
        move lineStart note = (lineStart', Note.replaceCommentNote replace note)
          where lineStart'
                  = if
                      Function.on (==) Location.getStartLine noteStart lineStart
                      then lineStart else noteStart
                noteStart = startPosition note
                replace = Note.replaceCommentStartColumn indent
                indent = const $ Location.getStartColumn lineStart'
        startPosition = Location.getPointLoc . Location.getPortion

mergeSuccessiveEmptyLines ::
                          Style.Style ->
                            Code.LocatableCommentableCode ->
                              Code.LocatableCommentableCode
mergeSuccessiveEmptyLines style
  = fmap . Note.replaceCommentNote $ Note.replaceCommentBoxes merge
  where merge
          = ListTool.mergeLongerSuccessions isEmptyLine
              successiveEmptyLinesLimit
        successiveEmptyLinesLimit = Style.successiveEmptyLinesLimit style