/*
 * Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
 * Use of this file is governed by the BSD 3-clause license that
 * can be found in the LICENSE.txt file in the project root.
 */

import 'input_stream.dart';
import 'interval_set.dart';
import 'misc/misc.dart';
import 'token_source.dart';

/// A token has properties: text, type, line, character position in the line
///  (so we can ignore tabs), token channel, index, and source from which
///  we obtained this token.
abstract class Token {
  static const int INVALID_TYPE = 0;

  /// During lookahead operations, this "token" signifies we hit rule end ATN state
  ///  and did not follow it despite needing to.
  static const int EPSILON = -2;

  static const int MIN_USER_TOKEN_TYPE = 1;

  static const int EOF = IntStream.EOF;

  /// All tokens go to the parser (unless skip() is called in that rule)
  ///  on a particular "channel".  The parser tunes to a particular channel
  ///  so that whitespace etc... can go to the parser on a "hidden" channel.
  static const int DEFAULT_CHANNEL = 0;

  /// Anything on different channel than DEFAULT_CHANNEL is not parsed
  ///  by parser.
  static const int HIDDEN_CHANNEL = 1;

  /// This is the minimum constant value which can be assigned to a
  /// user-defined token channel.
  ///
  /// <p>
  /// The non-negative numbers less than {@link #MIN_USER_CHANNEL_VALUE} are
  /// assigned to the predefined channels {@link #DEFAULT_CHANNEL} and
  /// {@link #HIDDEN_CHANNEL}.</p>
  ///
  /// @see Token#getChannel()
  static const int MIN_USER_CHANNEL_VALUE = 2;

  /// Get the text of the token.
  String? get text;

  /// Get the token type of the token */
  int get type;

  /// The line number on which the 1st character of this token was matched,
  ///  line=1..n
  int? get line;

  /// The index of the first character of this token relative to the
  ///  beginning of the line at which it occurs, 0..n-1
  int get charPositionInLine;

  /// Return the channel this token. Each token can arrive at the parser
  ///  on a different channel, but the parser only "tunes" to a single channel.
  ///  The parser ignores everything not on DEFAULT_CHANNEL.
  int get channel;

  /// An index from 0..n-1 of the token object in the input stream.
  ///  This must be valid in order to print token streams and
  ///  use TokenRewriteStream.
  ///
  ///  Return -1 to indicate that this token was conjured up since
  ///  it doesn't have a valid index.
  int get tokenIndex;

  /// The starting character index of the token
  ///  This method is optional; return -1 if not implemented.
  int get startIndex;

  /// The last character index of the token.
  ///  This method is optional; return -1 if not implemented.
  int get stopIndex;

  /// Gets the [TokenSource] which created this token.
  TokenSource? get tokenSource;

  /// Gets the [CharStream] from which this token was derived.
  CharStream? get inputStream;
}

abstract class WritableToken extends Token {
  set text(String? text);

  set type(int ttype);

  set line(int? line);

  set charPositionInLine(int pos);

  set channel(int channel);

  set tokenIndex(int index);
}

class CommonToken extends WritableToken {
  /// An empty [Pair] which is used as the default value of
  /// {@link #source} for tokens that do not have a source.
  static const EMPTY_SOURCE = Pair<TokenSource?, CharStream?>(null, null);

  @override
  int type;

  @override
  int? line;

  @override
  int charPositionInLine = -1; // set to invalid position

  @override
  int channel = Token.DEFAULT_CHANNEL;

  /// These properties share a field to reduce the memory footprint of
  /// [CommonToken]. Tokens created by a [CommonTokenFactory] from
  /// the same source and input stream share a reference to the same
  /// [Pair] containing these values.</p>
  late Pair<TokenSource?, CharStream?> source;

  /// This is the backing field for {@link #getText} when the token text is
  /// explicitly set in the constructor or via {@link #setText}.
  ///
  /// @see #getText()
  String? _text;

  @override
  int tokenIndex = -1;

  @override
  int startIndex;

  @override
  int stopIndex;

  /// Constructs a new [CommonToken] with the specified token type and
  /// text.
  ///
  /// @param type The token type.
  /// @param text The text of the token.
  CommonToken(
    this.type, {
    this.source = EMPTY_SOURCE,
    this.channel = Token.DEFAULT_CHANNEL,
    this.startIndex = -1,
    this.stopIndex = -1,
    text,
  }) {
    _text = text;
    if (source.a != null) {
      line = source.a!.line;
      charPositionInLine = source.a!.charPositionInLine;
    }
  }

  /// Constructs a new [CommonToken] as a copy of another [Token].
  ///
  /// <p>
  /// If [oldToken] is also a [CommonToken] instance, the newly
  /// constructed token will share a reference to the {@link #text} field and
  /// the [Pair] stored in {@link #source}. Otherwise, {@link #text} will
  /// be assigned the result of calling {@link #getText}, and {@link #source}
  /// will be constructed from the result of {@link Token#getTokenSource} and
  /// {@link Token#getInputStream}.</p>
  ///
  /// @param oldToken The token to copy.
  CommonToken.copy(Token oldToken)
      : type = oldToken.type,
        line = oldToken.line,
        tokenIndex = oldToken.tokenIndex,
        charPositionInLine = oldToken.charPositionInLine,
        channel = oldToken.channel,
        startIndex = oldToken.startIndex,
        stopIndex = oldToken.stopIndex {
    if (oldToken is CommonToken) {
      _text = oldToken.text;
      source = oldToken.source;
    } else {
      _text = oldToken.text;
      source = Pair<TokenSource?, CharStream?>(
        oldToken.tokenSource,
        oldToken.inputStream,
      );
    }
  }

  @override
  String? get text {
    if (_text != null) {
      return _text;
    }

    final input = inputStream;
    if (input == null) return null;
    final n = input.size;

    if (startIndex < n && stopIndex < n) {
      return input.getText(Interval.of(startIndex, stopIndex));
    } else {
      return '<EOF>';
    }
  }

  /// Explicitly set the text for this token. If {code text} is not
  /// null, then {@link #getText} will return this value rather than
  /// extracting the text from the input.
  ///
  /// @param text The explicit text of the token, or null if the text
  /// should be obtained from the input along with the start and stop indexes
  /// of the token.
  @override
  set text(String? text) {
    _text = text;
  }

  @override
  TokenSource? get tokenSource {
    return source.a;
  }

  @override
  CharStream? get inputStream {
    return source.b;
  }

  @override
  String toString([void _]) {
    var txt = text;
    if (txt != null) {
      txt = txt
          .replaceAll('\n', r'\n')
          .replaceAll('\r', r'\r')
          .replaceAll('\t', r'\t');
    } else {
      txt = '<no text>';
    }
    return "[@$tokenIndex,$startIndex:$stopIndex='$txt',<$type>" +
        (channel > 0 ? ',channel=$channel' : '') +
        ',$line:$charPositionInLine]';
  }
}

/// A [Token] object representing an entire subtree matched by a parser
/// rule; e.g., {@code <expr>}. These tokens are created for [TagChunk]
/// chunks where the tag corresponds to a parser rule.
class RuleTagToken implements Token {
  /// Gets the name of the rule associated with this rule tag.
  ///
  /// @return The name of the parser rule associated with this rule tag.
  final String ruleName;

  /// The token type for the current token. This is the token type assigned to
  /// the bypass alternative for the rule during ATN deserialization.
  final int bypassTokenType;

  /// Gets the label associated with the rule tag.
  ///
  /// @return The name of the label associated with the rule tag, or
  /// null if this is an unlabeled rule tag.
  final String? label;

  /// Constructs a new instance of [RuleTagToken] with the specified rule
  /// name, bypass token type, and label.
  ///
  /// @param ruleName The name of the parser rule this rule tag matches.
  /// @param bypassTokenType The bypass token type assigned to the parser rule.
  /// @param label The label associated with the rule tag, or null if
  /// the rule tag is unlabeled.
  ///
  /// @exception ArgumentError.value(value) if [ruleName] is null
  /// or empty.
  RuleTagToken(this.ruleName, this.bypassTokenType, [this.label]) {
    if (ruleName.isEmpty) {
      throw ArgumentError.value(
        ruleName,
        'ruleName',
        'cannot be empty.',
      );
    }
  }

  /// {@inheritDoc}
  ///
  /// <p>Rule tag tokens are always placed on the {@link #DEFAULT_CHANNEL}.</p>

  @override
  int get channel {
    return Token.DEFAULT_CHANNEL;
  }

  /// {@inheritDoc}
  ///
  /// <p>This method returns the rule tag formatted with {@code <} and {@code >}
  /// delimiters.</p>

  @override
  String get text {
    if (label != null) {
      return '<' + label! + ':' + ruleName + '>';
    }

    return '<' + ruleName + '>';
  }

  /// {@inheritDoc}
  ///
  /// <p>Rule tag tokens have types assigned according to the rule bypass
  /// transitions created during ATN deserialization.</p>

  @override
  int get type {
    return bypassTokenType;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns 0.</p>

  @override
  int get line {
    return 0;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns -1.</p>
  @override
  int get charPositionInLine {
    return -1;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns -1.</p>
  @override
  int get tokenIndex {
    return -1;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns -1.</p>
  @override
  int get startIndex {
    return -1;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns -1.</p>

  @override
  int get stopIndex {
    return -1;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns null.</p>

  @override
  TokenSource? get tokenSource {
    return null;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] always returns null.</p>

  @override
  CharStream? get inputStream {
    return null;
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [RuleTagToken] returns a string of the form
  /// {@code ruleName:bypassTokenType}.</p>

  @override
  String toString() {
    return ruleName + ':$bypassTokenType';
  }
}

/// A [Token] object representing a token of a particular type; e.g.,
/// {@code <ID>}. These tokens are created for [TagChunk] chunks where the
/// tag corresponds to a lexer rule or token type.
class TokenTagToken extends CommonToken {
  /// Gets the token name.
  /// @return The token name.
  final String tokenName;

  /// Gets the label associated with the rule tag.
  ///
  /// @return The name of the label associated with the rule tag, or
  /// null if this is an unlabeled rule tag.
  final String? label;

  /// Constructs a new instance of [TokenTagToken] with the specified
  /// token name, type, and label.
  ///
  /// @param tokenName The token name.
  /// @param type The token type.
  /// @param label The label associated with the token tag, or null if
  /// the token tag is unlabeled.
  TokenTagToken(this.tokenName, type, [this.label]) : super(type);

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [TokenTagToken] returns the token tag
  /// formatted with {@code <} and {@code >} delimiters.</p>

  @override
  String get text {
    if (label != null) {
      return '<' + label! + ':' + tokenName + '>';
    }

    return '<' + tokenName + '>';
  }

  /// {@inheritDoc}
  ///
  /// <p>The implementation for [TokenTagToken] returns a string of the form
  /// {@code tokenName:type}.</p>

  @override
  String toString([void _]) {
    return tokenName + ':$type';
  }
}
