//===--- SourceLoc.h - ReST source location and source manager classes ----===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//


#ifndef SWIFT_MARKUP_SOURCELOC_H
#define SWIFT_MARKUP_SOURCELOC_H

#include "llvm/ADT/StringRef.h"
#include <algorithm>
#include <cassert>
#include <utility>
#include <vector>

namespace swift {
namespace markup {

class SourceLoc {
  friend class SourceManagerBase;

  template<typename ExternalSourceLocTy>
  friend class SourceManager;

  unsigned Value;

  static const unsigned InvalidValue = 0;

public:
  SourceLoc() : Value(InvalidValue) {}
  SourceLoc(const SourceLoc &) = default;

  bool isValid() const { return !isInvalid(); }
  bool isInvalid() const { return Value == InvalidValue; }

  bool operator==(SourceLoc RHS) const { return Value == RHS.Value; }
  bool operator!=(SourceLoc RHS) const { return !(*this == RHS); }

  /// Return a source location advanced a specified number of bytes.
  SourceLoc getAdvancedLoc(int ByteOffset) const {
    assert(isValid() && "Can't advance an invalid location");
    SourceLoc Result = *this;
    Result.Value += ByteOffset;
    return Result;
  }
};

class SourceRange {
public:
  /// The source range is a half-open byte range [Start; End).
  SourceLoc Start, End;

  SourceRange() {}
  SourceRange(SourceLoc Loc) : Start(Loc), End(Loc) { }
  SourceRange(SourceLoc Start, SourceLoc End) : Start(Start), End(End) {
    assert(Start.isValid() == End.isValid() &&
           "Start and end should either both be valid or both be invalid!");
  }

  bool isValid() const { return Start.isValid(); }
  bool isInvalid() const { return Start.isInvalid(); }
};

class SourceManagerBase {
protected:
  SourceLoc NextUnassignedLoc;

  /// All source pieces, in order of increasing source location.
  std::vector<SourceRange> RegisteredRanges;

public:
  SourceManagerBase() : NextUnassignedLoc() {
    NextUnassignedLoc.Value = 1;
  }
  SourceManagerBase(const SourceManagerBase &) = delete;

  void operator=(const SourceManagerBase &) = delete;

  bool isBeforeInBuffer(SourceLoc LHS, SourceLoc RHS) const {
    // When we support multiple buffers, assert that locations come from the
    // same buffer.
    return LHS.Value < RHS.Value;
  }

  /// Returns true if range \c R contains the location \c Loc.
  bool containsLoc(SourceRange R, SourceLoc Loc) const {
    return Loc == R.Start ||
           (isBeforeInBuffer(R.Start, Loc) && isBeforeInBuffer(Loc, R.End));
  }
};

template <typename ExternalSourceLocTy>
class SourceManager : public SourceManagerBase {
  std::vector<ExternalSourceLocTy> ExternalLocs;

public:
  SourceManager() = default;
  SourceManager(const SourceManager &) = delete;

  void operator=(const SourceManager &) = delete;

  SourceRange registerLine(StringRef Line, ExternalSourceLocTy ExternalLoc);

  /// Returns the external source range and a byte offset inside it.
  std::pair<ExternalSourceLocTy, unsigned>
  toExternalSourceLoc(SourceLoc Loc) const;
};

template <typename ExternalSourceLocTy>
SourceRange SourceManager<ExternalSourceLocTy>::registerLine(
    StringRef Line, ExternalSourceLocTy ExternalLoc) {
  if (Line.size() > 4095)
    return SourceRange();

  SourceLoc Start = NextUnassignedLoc;
  SourceLoc End = Start.getAdvancedLoc(Line.size());
  RegisteredRanges.push_back(SourceRange(Start, End));
  ExternalLocs.push_back(ExternalLoc);
  NextUnassignedLoc = End.getAdvancedLoc(2);
#ifndef NDEBUG
  // To make debugging easier, make each line start at offset that is equal to
  // 1 mod 1000.
  NextUnassignedLoc.Value = ((NextUnassignedLoc.Value + 999) / 1000) * 1000 + 1;
#endif
  return SourceRange(Start, End);
}

template <typename ExternalSourceLocTy>
std::pair<ExternalSourceLocTy, unsigned>
SourceManager<ExternalSourceLocTy>::toExternalSourceLoc(SourceLoc Loc) const {
  auto I = std::lower_bound(RegisteredRanges.begin(), RegisteredRanges.end(),
                            Loc, [this](const SourceRange &LHS, SourceLoc Loc) {
    return this->isBeforeInBuffer(LHS.Start, Loc);
  });
  assert(I != RegisteredRanges.end() && "unknown source location");
  const auto &InternalRange = *I;
  assert(containsLoc(InternalRange, Loc) && "unknown source location");
  const auto &ExternalLoc = ExternalLocs[I - RegisteredRanges.begin()];
  return { ExternalLoc, Loc.Value - InternalRange.Start.Value };
}

} // namespace markup
} // namespace swift

#endif // SWIFT_MARKUP_SOURCELOC_H

