//===--- WhitespaceManager.cpp - Format C++ code --------------------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
///
/// \file
/// \brief This file implements WhitespaceManager class.
///
//===----------------------------------------------------------------------===//

#include "WhitespaceManager.h"
#include "llvm/ADT/STLExtras.h"

namespace clang {
namespace format {

void WhitespaceManager::replaceWhitespace(const AnnotatedToken &Tok,
                                          unsigned NewLines, unsigned Spaces,
                                          unsigned WhitespaceStartColumn) {
  if (NewLines > 0)
    alignEscapedNewlines();

  // 2+ newlines mean an empty line separating logic scopes.
  if (NewLines >= 2)
    alignComments();

  // Align line comments if they are trailing or if they continue other
  // trailing comments.
  if (Tok.isTrailingComment()) {
    SourceLocation TokenEndLoc = Tok.FormatTok.getStartOfNonWhitespace()
        .getLocWithOffset(Tok.FormatTok.TokenLength);
    // Remove the comment's trailing whitespace.
    if (Tok.FormatTok.TrailingWhiteSpaceLength != 0)
      Replaces.insert(tooling::Replacement(
          SourceMgr, TokenEndLoc, Tok.FormatTok.TrailingWhiteSpaceLength, ""));

    bool LineExceedsColumnLimit =
        Spaces + WhitespaceStartColumn + Tok.FormatTok.TokenLength >
        Style.ColumnLimit;
    // Align comment with other comments.
    if ((Tok.Parent != NULL || !Comments.empty()) &&
        !LineExceedsColumnLimit) {
      unsigned MinColumn =
          NewLines > 0 ? Spaces : WhitespaceStartColumn + Spaces;
      unsigned MaxColumn = Style.ColumnLimit - Tok.FormatTok.TokenLength;
      Comments.push_back(StoredToken(
          Tok.FormatTok.WhiteSpaceStart, Tok.FormatTok.WhiteSpaceLength,
          MinColumn, MaxColumn, NewLines, Spaces));
      return;
    }
  }

  // If this line does not have a trailing comment, align the stored comments.
  if (Tok.Children.empty() && !Tok.isTrailingComment())
    alignComments();

  storeReplacement(Tok.FormatTok.WhiteSpaceStart,
                   Tok.FormatTok.WhiteSpaceLength,
                   getNewLineText(NewLines, Spaces));
}

void WhitespaceManager::replacePPWhitespace(const AnnotatedToken &Tok,
                                            unsigned NewLines, unsigned Spaces,
                                            unsigned WhitespaceStartColumn) {
  if (NewLines == 0) {
    replaceWhitespace(Tok, NewLines, Spaces, WhitespaceStartColumn);
  } else {
    // The earliest position for "\" is 2 after the last token.
    unsigned MinColumn = WhitespaceStartColumn + 2;
    unsigned MaxColumn = Style.ColumnLimit;
    EscapedNewlines.push_back(StoredToken(
        Tok.FormatTok.WhiteSpaceStart, Tok.FormatTok.WhiteSpaceLength,
        MinColumn, MaxColumn, NewLines, Spaces));
  }
}

void WhitespaceManager::breakToken(const FormatToken &Tok, unsigned Offset,
                                   unsigned ReplaceChars, StringRef Prefix,
                                   StringRef Postfix, bool InPPDirective,
                                   unsigned Spaces,
                                   unsigned WhitespaceStartColumn) {
  SourceLocation Location =
      Tok.getStartOfNonWhitespace().getLocWithOffset(Offset);
  if (InPPDirective) {
    // The earliest position for "\" is 2 after the last token.
    unsigned MinColumn = WhitespaceStartColumn + 2;
    unsigned MaxColumn = Style.ColumnLimit;
    StoredToken StoredTok = StoredToken(Location, ReplaceChars, MinColumn,
                                        MaxColumn, /*NewLines=*/ 1, Spaces);
    StoredTok.Prefix = Prefix;
    StoredTok.Postfix = Postfix;
    EscapedNewlines.push_back(StoredTok);
  } else {
    std::string ReplacementText =
        (Prefix + getNewLineText(1, Spaces) + Postfix).str();
    Replaces.insert(tooling::Replacement(SourceMgr, Location, ReplaceChars,
                                         ReplacementText));
  }
}

const tooling::Replacements &WhitespaceManager::generateReplacements() {
  alignComments();
  alignEscapedNewlines();
  return Replaces;
}

void WhitespaceManager::addReplacement(const SourceLocation &SourceLoc,
                                       unsigned ReplaceChars, StringRef Text) {
  Replaces.insert(
      tooling::Replacement(SourceMgr, SourceLoc, ReplaceChars, Text));
}

void WhitespaceManager::addUntouchableComment(unsigned Column) {
  StoredToken Tok = StoredToken(SourceLocation(), 0, Column, Column, 0, 0);
  Tok.Untouchable = true;
  Comments.push_back(Tok);
}

std::string WhitespaceManager::getNewLineText(unsigned NewLines,
                                              unsigned Spaces) {
  return std::string(NewLines, '\n') + std::string(Spaces, ' ');
}

std::string WhitespaceManager::getNewLineText(unsigned NewLines,
                                              unsigned Spaces,
                                              unsigned WhitespaceStartColumn,
                                              unsigned EscapedNewlineColumn) {
  std::string NewLineText;
  if (NewLines > 0) {
    unsigned Offset =
        std::min<int>(EscapedNewlineColumn - 1, WhitespaceStartColumn);
    for (unsigned i = 0; i < NewLines; ++i) {
      NewLineText += std::string(EscapedNewlineColumn - Offset - 1, ' ');
      NewLineText += "\\\n";
      Offset = 0;
    }
  }
  return NewLineText + std::string(Spaces, ' ');
}

void WhitespaceManager::alignComments() {
  unsigned MinColumn = 0;
  unsigned MaxColumn = UINT_MAX;
  token_iterator Start = Comments.begin();
  for (token_iterator I = Start, E = Comments.end(); I != E; ++I) {
    if (I->MinColumn > MaxColumn || I->MaxColumn < MinColumn) {
      alignComments(Start, I, MinColumn);
      MinColumn = I->MinColumn;
      MaxColumn = I->MaxColumn;
      Start = I;
    } else {
      MinColumn = std::max(MinColumn, I->MinColumn);
      MaxColumn = std::min(MaxColumn, I->MaxColumn);
    }
  }
  alignComments(Start, Comments.end(), MinColumn);
  Comments.clear();
}

void WhitespaceManager::alignComments(token_iterator I, token_iterator E,
                                      unsigned Column) {
  while (I != E) {
    if (!I->Untouchable) {
      unsigned Spaces = I->Spaces + Column - I->MinColumn;
      storeReplacement(I->ReplacementLoc, I->ReplacementLength,
                       getNewLineText(I->NewLines, Spaces));
    }
    ++I;
  }
}

void WhitespaceManager::alignEscapedNewlines() {
  unsigned MinColumn;
  if (Style.AlignEscapedNewlinesLeft) {
    MinColumn = 0;
    for (token_iterator I = EscapedNewlines.begin(), E = EscapedNewlines.end();
         I != E; ++I) {
      if (I->MinColumn > MinColumn)
        MinColumn = I->MinColumn;
    }
  } else {
    MinColumn = Style.ColumnLimit;
  }

  for (token_iterator I = EscapedNewlines.begin(), E = EscapedNewlines.end();
       I != E; ++I) {
    // I->MinColumn - 2 is the end of the previous token (i.e. the
    // WhitespaceStartColumn).
    storeReplacement(
        I->ReplacementLoc, I->ReplacementLength,
        I->Prefix + getNewLineText(I->NewLines, I->Spaces, I->MinColumn - 2,
                                   MinColumn) + I->Postfix);

  }
  EscapedNewlines.clear();
}

void WhitespaceManager::storeReplacement(SourceLocation Loc, unsigned Length,
                                         const std::string Text) {
  // Don't create a replacement, if it does not change anything.
  if (StringRef(SourceMgr.getCharacterData(Loc), Length) == Text)
    return;
  Replaces.insert(tooling::Replacement(SourceMgr, Loc, Length, Text));
}

} // namespace format
} // namespace clang
