// Copyright (c) 2018, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import "dart:math" as math;

/**
 * A source range defines a range of characters within source code.
 */
class SourceRange {
  /**
   * An empty source range (a range with offset `0` and length `0`).
   */
  static const SourceRange EMPTY = const SourceRange(0, 0);

  /**
   * The 0-based index of the first character of the source range.
   */
  final int offset;

  /**
   * The number of characters in the source range.
   */
  final int length;

  /**
   * Initialize a newly created source range using the given [offset] and
   * [length].
   */
  const SourceRange(this.offset, this.length);

  /**
   * Return the 0-based index of the character immediately after this source
   * range.
   */
  int get end => offset + length;

  @override
  int get hashCode => 31 * offset + length;

  @override
  bool operator ==(Object other) {
    return other is SourceRange &&
        other.offset == offset &&
        other.length == length;
  }

  /**
   * Return `true` if [x] is in the interval `[offset, offset + length)`.
   */
  bool contains(int x) => offset <= x && x < offset + length;

  /**
   * Return `true` if [x] is in the interval `(offset, offset + length)`.
   */
  bool containsExclusive(int x) => offset < x && x < offset + length;

  /**
   * Return `true` if the [otherRange] covers this source range.
   */
  bool coveredBy(SourceRange otherRange) => otherRange.covers(this);

  /**
   * Return `true` if this source range covers the [otherRange].
   */
  bool covers(SourceRange otherRange) =>
      offset <= otherRange.offset && otherRange.end <= end;

  /**
   * Return `true` if this source range ends inside the [otherRange].
   */
  bool endsIn(SourceRange otherRange) {
    int thisEnd = end;
    return otherRange.contains(thisEnd);
  }

  /**
   * Return a source range covering [delta] characters before the start of this
   * source range and [delta] characters after the end of this source range.
   */
  SourceRange getExpanded(int delta) =>
      new SourceRange(offset - delta, delta + length + delta);

  /**
   * Return a source range with the same offset as this source range but whose
   * length is [delta] characters longer than this source range.
   */
  SourceRange getMoveEnd(int delta) => new SourceRange(offset, length + delta);

  /**
   * Return a source range with the same length as this source range but whose
   * offset is [delta] characters after the offset of this source range.
   */
  SourceRange getTranslated(int delta) =>
      new SourceRange(offset + delta, length);

  /**
   * Return the minimal source range that covers both this and the [otherRange].
   */
  SourceRange getUnion(SourceRange otherRange) {
    int newOffset = math.min(offset, otherRange.offset);
    int newEnd =
        math.max(offset + length, otherRange.offset + otherRange.length);
    return new SourceRange(newOffset, newEnd - newOffset);
  }

  /**
   * Return `true` if this source range intersects the [otherRange].
   */
  bool intersects(SourceRange otherRange) {
    if (otherRange == null) {
      return false;
    }
    if (end <= otherRange.offset) {
      return false;
    }
    if (offset >= otherRange.end) {
      return false;
    }
    return true;
  }

  /**
   * Return `true` if this source range starts in the [otherRange].
   */
  bool startsIn(SourceRange otherRange) => otherRange.contains(offset);

  @override
  String toString() => '[offset=$offset, length=$length]';
}
