//===--- BreakableToken.h - 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 Declares BreakableToken, BreakableStringLiteral, and
/// BreakableBlockComment classes, that contain token type-specific logic to
/// break long lines in tokens.
///
//===----------------------------------------------------------------------===//

#ifndef LLVM_CLANG_FORMAT_BREAKABLETOKEN_H
#define LLVM_CLANG_FORMAT_BREAKABLETOKEN_H

#include "TokenAnnotator.h"
#include "WhitespaceManager.h"
#include <utility>

namespace clang {
namespace format {

class BreakableToken {
public:
  BreakableToken(const SourceManager &SourceMgr, const FormatToken &Tok,
                 unsigned StartColumn)
      : Tok(Tok), StartColumn(StartColumn),
        TokenText(SourceMgr.getCharacterData(Tok.getStartOfNonWhitespace()),
                  Tok.TokenLength) {}
  virtual ~BreakableToken() {}
  virtual unsigned getLineCount() const = 0;
  virtual unsigned getLineSize(unsigned Index) const = 0;
  virtual unsigned getLineLengthAfterSplit(unsigned LineIndex,
                                           unsigned TailOffset) const = 0;

  // Contains starting character index and length of split.
  typedef std::pair<StringRef::size_type, unsigned> Split;
  virtual Split getSplit(unsigned LineIndex, unsigned TailOffset,
                         unsigned ColumnLimit) const = 0;
  virtual void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split,
                           bool InPPDirective,
                           WhitespaceManager &Whitespaces) = 0;
  virtual void trimLine(unsigned LineIndex, unsigned TailOffset,
                        unsigned InPPDirective,
                        WhitespaceManager &Whitespaces) {}
protected:
  const FormatToken &Tok;
  unsigned StartColumn;
  StringRef TokenText;
};

class BreakableStringLiteral : public BreakableToken {
public:
  BreakableStringLiteral(const SourceManager &SourceMgr, const FormatToken &Tok,
                         unsigned StartColumn)
      : BreakableToken(SourceMgr, Tok, StartColumn) {
    assert(TokenText.startswith("\"") && TokenText.endswith("\""));
  }

  virtual unsigned getLineCount() const { return 1; }

  virtual unsigned getLineSize(unsigned Index) const {
    return Tok.TokenLength - 2; // Should be in sync with getLine
  }

  virtual unsigned getLineLengthAfterSplit(unsigned LineIndex,
                                           unsigned TailOffset) const {
    return getDecorationLength() + getLine().size() - TailOffset;
  }

  virtual Split getSplit(unsigned LineIndex, unsigned TailOffset,
                         unsigned ColumnLimit) const {
    StringRef Text = getLine().substr(TailOffset);
    if (ColumnLimit <= getDecorationLength())
      return Split(StringRef::npos, 0);
    unsigned MaxSplit = ColumnLimit - getDecorationLength();
    assert(MaxSplit < Text.size());
    StringRef::size_type SpaceOffset = Text.rfind(' ', MaxSplit);
    if (SpaceOffset != StringRef::npos && SpaceOffset != 0)
      return Split(SpaceOffset + 1, 0);
    StringRef::size_type SlashOffset = Text.rfind('/', MaxSplit);
    if (SlashOffset != StringRef::npos && SlashOffset != 0)
      return Split(SlashOffset + 1, 0);
    StringRef::size_type SplitPoint = getStartOfCharacter(Text, MaxSplit);
    if (SplitPoint != StringRef::npos && SplitPoint > 1)
      // Do not split at 0.
      return Split(SplitPoint, 0);
    return Split(StringRef::npos, 0);
  }

  virtual void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split,
                           bool InPPDirective, WhitespaceManager &Whitespaces) {
    unsigned WhitespaceStartColumn = StartColumn + Split.first + 2;
    Whitespaces.breakToken(Tok, 1 + TailOffset + Split.first, Split.second,
                           "\"", "\"", InPPDirective, StartColumn,
                           WhitespaceStartColumn);
  }

private:
  StringRef getLine() const {
    // Get string without quotes.
    // FIXME: Handle string prefixes.
    return TokenText.substr(1, TokenText.size() - 2);
  }

  unsigned getDecorationLength() const { return StartColumn + 2; }

  static StringRef::size_type getStartOfCharacter(StringRef Text,
                                                  StringRef::size_type Offset) {
    StringRef::size_type NextEscape = Text.find('\\');
    while (NextEscape != StringRef::npos && NextEscape < Offset) {
      StringRef::size_type SequenceLength =
          getEscapeSequenceLength(Text.substr(NextEscape));
      if (Offset < NextEscape + SequenceLength)
        return NextEscape;
      NextEscape = Text.find('\\', NextEscape + SequenceLength);
    }
    return Offset;
  }

  static unsigned getEscapeSequenceLength(StringRef Text) {
    assert(Text[0] == '\\');
    if (Text.size() < 2)
      return 1;

    switch (Text[1]) {
    case 'u':
      return 6;
    case 'U':
      return 10;
    case 'x':
      return getHexLength(Text);
    default:
      if (Text[1] >= '0' && Text[1] <= '7')
        return getOctalLength(Text);
      return 2;
    }
  }

  static unsigned getHexLength(StringRef Text) {
    unsigned I = 2; // Point after '\x'.
    while (I < Text.size() && ((Text[I] >= '0' && Text[I] <= '9') ||
                               (Text[I] >= 'a' && Text[I] <= 'f') ||
                               (Text[I] >= 'A' && Text[I] <= 'F'))) {
      ++I;
    }
    return I;
  }

  static unsigned getOctalLength(StringRef Text) {
    unsigned I = 1;
    while (I < Text.size() && I < 4 && (Text[I] >= '0' && Text[I] <= '7')) {
      ++I;
    }
    return I;
  }

};

class BreakableComment : public BreakableToken {
public:
  virtual unsigned getLineSize(unsigned Index) const {
    return getLine(Index).size();
  }

  virtual unsigned getLineCount() const { return Lines.size(); }

  virtual unsigned getLineLengthAfterSplit(unsigned LineIndex,
                                           unsigned TailOffset) const {
    return getContentStartColumn(LineIndex, TailOffset) +
           getLine(LineIndex).size() - TailOffset;
  }

  virtual Split getSplit(unsigned LineIndex, unsigned TailOffset,
                         unsigned ColumnLimit) const;
  virtual void insertBreak(unsigned LineIndex, unsigned TailOffset, Split Split,
                           bool InPPDirective, WhitespaceManager &Whitespaces);

protected:
  BreakableComment(const SourceManager &SourceMgr, const FormatToken &Tok,
                   unsigned StartColumn)
      : BreakableToken(SourceMgr, Tok, StartColumn) {}

  // Get comment lines without /* */, common prefix and trailing whitespace.
  // Last line is not trimmed, as it is terminated by */, so its trailing
  // whitespace is not really trailing.
  StringRef getLine(unsigned Index) const {
    return Index < Lines.size() - 1 ? Lines[Index].rtrim() : Lines[Index];
  }

  unsigned getContentStartColumn(unsigned LineIndex,
                                 unsigned TailOffset) const {
    return (TailOffset == 0 && LineIndex == 0)
               ? StartColumn
               : IndentAtLineBreak + Decoration.size();
  }

  unsigned IndentAtLineBreak;
  StringRef Decoration;
  SmallVector<StringRef, 16> Lines;
};

class BreakableBlockComment : public BreakableComment {
public:
  BreakableBlockComment(const SourceManager &SourceMgr,
                        const AnnotatedToken &Token, unsigned StartColumn);

  void alignLines(WhitespaceManager &Whitespaces);

  virtual unsigned getLineLengthAfterSplit(unsigned LineIndex,
                                           unsigned TailOffset) const {
    return BreakableComment::getLineLengthAfterSplit(LineIndex, TailOffset) +
           (LineIndex + 1 < Lines.size() ? 0 : 2);
  }

  virtual void trimLine(unsigned LineIndex, unsigned TailOffset,
                        unsigned InPPDirective, WhitespaceManager &Whitespaces);

private:
  unsigned OriginalStartColumn;
  unsigned CommonPrefixLength;
};

class BreakableLineComment : public BreakableComment {
public:
  BreakableLineComment(const SourceManager &SourceMgr,
                       const AnnotatedToken &Token, unsigned StartColumn);

private:
  static StringRef getLineCommentPrefix(StringRef Comment);
};

} // namespace format
} // namespace clang

#endif // LLVM_CLANG_FORMAT_BREAKABLETOKEN_H