// 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.

library fasta.parser.type_info;

import '../../scanner/token.dart' show Token, TokenType;

import '../scanner/token_constants.dart' show IDENTIFIER_TOKEN, KEYWORD_TOKEN;

import 'parser.dart' show Parser;

import 'type_info_impl.dart';

import 'util.dart' show optional;

/// [TypeInfo] provides information collected by [computeType]
/// about a particular type reference.
abstract class TypeInfo {
  /// Return `true` if the tokens comprising the type represented by the
  /// receiver could be interpreted as a valid standalone expression.
  /// For example, `A` or `A.b` could be interpreted as a type references
  /// or as expressions, while `A<T>` only looks like a type reference.
  bool get couldBeExpression;

  /// Call this function when the token after [token] must be a type (not void).
  /// This function will call the appropriate event methods on the [Parser]'s
  /// listener to handle the type, inserting a synthetic type reference if
  /// necessary. This may modify the token stream when parsing `>>` in valid
  /// code or during recovery.
  Token ensureTypeNotVoid(Token token, Parser parser);

  /// Call this function when the token after [token] must be a type or void.
  /// This function will call the appropriate event methods on the [Parser]'s
  /// listener to handle the type, inserting a synthetic type reference if
  /// necessary. This may modify the token stream when parsing `>>` in valid
  /// code or during recovery.
  Token ensureTypeOrVoid(Token token, Parser parser);

  /// Call this function to parse an optional type (not void) after [token].
  /// This function will call the appropriate event methods on the [Parser]'s
  /// listener to handle the type. This may modify the token stream
  /// when parsing `>>` in valid code or during recovery.
  Token parseTypeNotVoid(Token token, Parser parser);

  /// Call this function to parse an optional type or void after [token].
  /// This function will call the appropriate event methods on the [Parser]'s
  /// listener to handle the type. This may modify the token stream
  /// when parsing `>>` in valid code or during recovery.
  Token parseType(Token token, Parser parser);

  /// Call this function with the [token] before the type to obtain
  /// the last token in the type. If there is no type, then this method
  /// will return [token]. This does not modify the token stream.
  Token skipType(Token token);
}

/// [TypeParamOrArgInfo] provides information collected by
/// [computeTypeParamOrArg] about a particular group of type arguments
/// or type parameters.
abstract class TypeParamOrArgInfo {
  /// Call this function to parse optional type arguments after [token].
  /// This function will call the appropriate event methods on the [Parser]'s
  /// listener to handle the arguments. This may modify the token stream
  /// when parsing `>>` in valid code or during recovery.
  Token parseArguments(Token token, Parser parser);

  /// Call this function to parse optional type parameters
  /// (also known as type variables) after [token].
  /// This function will call the appropriate event methods on the [Parser]'s
  /// listener to handle the parameters. This may modify the token stream
  /// when parsing `>>` in valid code or during recovery.
  Token parseVariables(Token token, Parser parser);

  /// Call this function with the [token] before the type var to obtain
  /// the last token in the type var. If there is no type var, then this method
  /// will return [token]. This does not modify the token stream.
  Token skip(Token token);
}

/// [NoType] is a specialized [TypeInfo] returned by [computeType] when
/// there is no type information in the source.
const TypeInfo noType = const NoType();

/// [VoidType] is a specialized [TypeInfo] returned by [computeType] when
/// there is a single identifier as the type reference.
const TypeInfo voidType = const VoidType();

/// [SimpleType] is a specialized [TypeInfo] returned by [computeType]
/// when there is a single identifier as the type reference.
const TypeInfo simpleType = const SimpleType();

/// [PrefixedType] is a specialized [TypeInfo] returned by [computeType]
/// when the type reference is of the form: identifier `.` identifier.
const TypeInfo prefixedType = const PrefixedType();

/// [SimpleTypeWith1Argument] is a specialized [TypeInfo] returned by
/// [computeType] when the type reference is of the form:
/// identifier `<` identifier `>`.
const TypeInfo simpleTypeWith1Argument = const SimpleTypeWith1Argument();

/// [NoTypeParamOrArg] is a specialized [TypeParamOrArgInfo] returned by
/// [computeTypeParamOrArg] when no type parameters or arguments are found.
const TypeParamOrArgInfo noTypeParamOrArg = const NoTypeParamOrArg();

/// [SimpleTypeArgument1] is a specialized [TypeParamOrArgInfo] returned by
/// [computeTypeParamOrArg] when the type reference is of the form:
/// `<` identifier `>`.
const TypeParamOrArgInfo simpleTypeArgument1 = const SimpleTypeArgument1();

bool isGeneralizedFunctionType(Token token) {
  return optional('Function', token) &&
      (optional('<', token.next) || optional('(', token.next));
}

bool isValidTypeReference(Token token) {
  int kind = token.kind;
  if (IDENTIFIER_TOKEN == kind) return true;
  if (KEYWORD_TOKEN == kind) {
    TokenType type = token.type;
    String value = type.lexeme;
    return type.isPseudo ||
        (type.isBuiltIn && optional('.', token.next)) ||
        (identical(value, 'dynamic')) ||
        (identical(value, 'void'));
  }
  return false;
}

/// Called by the parser to obtain information about a possible type reference
/// that follows [token]. This does not modify the token stream.
///
/// If [inDeclaration] is `true`, then this will more aggressively recover
/// given unbalanced `<` `>` and invalid parameters or arguments.
///
/// If this method is called by [computeTypeParamOrArg] and the outer group ends
/// with `>>`, then then [innerEndGroup] is set to either `>>` if the token
/// has not been split or the first `>` if the `>>` token has been split.
TypeInfo computeType(final Token token, bool required,
    [bool inDeclaration = false, Token innerEndGroup]) {
  Token next = token.next;
  if (!isValidTypeReference(next)) {
    if (next.type.isBuiltIn) {
      TypeParamOrArgInfo typeParamOrArg =
          computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
      if (typeParamOrArg != noTypeParamOrArg) {
        // Recovery: built-in `<` ... `>`
        if (required || looksLikeName(typeParamOrArg.skip(next).next)) {
          return new ComplexTypeInfo(token, typeParamOrArg)
              .computeBuiltinAsType(required);
        }
      } else if (required || isGeneralizedFunctionType(next.next)) {
        String value = next.stringValue;
        if ((!identical('get', value) &&
            !identical('set', value) &&
            !identical('factory', value) &&
            !identical('operator', value) &&
            !(identical('typedef', value) && next.next.isIdentifier))) {
          return new ComplexTypeInfo(token, typeParamOrArg)
              .computeBuiltinAsType(required);
        }
      }
    } else if (required && optional('.', next)) {
      // Recovery: looks like prefixed type missing the prefix
      return new ComplexTypeInfo(
              token, computeTypeParamOrArg(next, inDeclaration, innerEndGroup))
          .computePrefixedType(required);
    }
    return noType;
  }

  if (optional('void', next)) {
    next = next.next;
    if (isGeneralizedFunctionType(next)) {
      // `void` `Function` ...
      return new ComplexTypeInfo(token, noTypeParamOrArg)
          .computeVoidGFT(required);
    }
    // `void`
    return voidType;
  }

  if (isGeneralizedFunctionType(next)) {
    // `Function` ...
    return new ComplexTypeInfo(token, noTypeParamOrArg)
        .computeNoTypeGFT(required);
  }

  // We've seen an identifier.

  TypeParamOrArgInfo typeParamOrArg =
      computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
  if (typeParamOrArg != noTypeParamOrArg) {
    if (typeParamOrArg == simpleTypeArgument1) {
      // We've seen identifier `<` identifier `>`
      next = typeParamOrArg.skip(next).next;
      if (!isGeneralizedFunctionType(next)) {
        if (required || looksLikeName(next)) {
          // identifier `<` identifier `>` identifier
          return simpleTypeWith1Argument;
        } else {
          // identifier `<` identifier `>` non-identifier
          return noType;
        }
      }
    }
    // TODO(danrubel): Consider adding a const for
    // identifier `<` identifier `,` identifier `>`
    // if that proves to be a common case.

    // identifier `<` ... `>`
    return new ComplexTypeInfo(token, typeParamOrArg)
        .computeSimpleWithTypeArguments(required);
  }

  assert(typeParamOrArg == noTypeParamOrArg);
  next = next.next;
  if (optional('.', next)) {
    next = next.next;
    if (isValidTypeReference(next)) {
      // We've seen identifier `.` identifier
      typeParamOrArg =
          computeTypeParamOrArg(next, inDeclaration, innerEndGroup);
      next = next.next;
      if (typeParamOrArg == noTypeParamOrArg &&
          !isGeneralizedFunctionType(next)) {
        if (required || looksLikeName(next)) {
          // identifier `.` identifier identifier
          return prefixedType;
        } else {
          // identifier `.` identifier non-identifier
          return noType;
        }
      }
      // identifier `.` identifier
      return new ComplexTypeInfo(token, typeParamOrArg)
          .computePrefixedType(required);
    }
    // identifier `.` non-identifier
    if (required) {
      typeParamOrArg =
          computeTypeParamOrArg(token.next.next, inDeclaration, innerEndGroup);
      return new ComplexTypeInfo(token, typeParamOrArg)
          .computePrefixedType(required);
    }
    return noType;
  }

  assert(typeParamOrArg == noTypeParamOrArg);
  if (isGeneralizedFunctionType(next)) {
    // identifier `Function`
    return new ComplexTypeInfo(token, noTypeParamOrArg)
        .computeIdentifierGFT(required);
  }

  if (required || looksLikeName(next)) {
    // identifier identifier
    return simpleType;
  }
  return noType;
}

/// Called by the parser to obtain information about a possible group of type
/// parameters or type arguments that follow [token].
/// This does not modify the token stream.
///
/// If [inDeclaration] is `true`, then this will more aggressively recover
/// given unbalanced `<` `>` and invalid parameters or arguments.
///
/// If this method is called by [computeType] and the outer group ends
/// with `>>`, then then [innerEndGroup] is set to either `>>` if the token
/// has not been split or the first `>` if the `>>` token has been split.
TypeParamOrArgInfo computeTypeParamOrArg(Token token,
    [bool inDeclaration = false, Token innerEndGroup]) {
  Token next = token.next;
  if (!optional('<', next)) {
    return noTypeParamOrArg;
  }
  Token endGroup = next.endGroup ?? innerEndGroup;
  if (endGroup == null) {
    if (inDeclaration) {
      // Recovery
      // Since the leading `<` cannot be part of an expression,
      // try to more aggressively recover given an unbalanced '<'.
      return new ComplexTypeParamOrArgInfo(token, inDeclaration)
          .compute(innerEndGroup);
    }
    return noTypeParamOrArg;
  }
  Token identifier = next.next;
  // identifier `<` `void` `>` and `<` `dynamic` `>`
  // are handled by ComplexTypeInfo.
  if ((identifier.kind == IDENTIFIER_TOKEN || identifier.type.isPseudo) &&
      identifier.next == endGroup) {
    return simpleTypeArgument1;
  }
  // TODO(danrubel): Consider adding additional const for common situations.
  return new ComplexTypeParamOrArgInfo(token, inDeclaration)
      .compute(innerEndGroup);
}

/// Called by the parser to obtain information about a possible group of type
/// type arguments that follow [token] and that are followed by '('.
/// Returns the type arguments if [token] matches '<' type (',' type)* '>' '(',
/// and otherwise returns [noTypeParamOrArg]. The final '(' is not part of the
/// grammar construct `typeArguments`, but it is required here such that type
/// arguments in generic method invocations can be recognized, and as few as
/// possible other constructs will pass (e.g., 'a < C, D > 3').
TypeParamOrArgInfo computeMethodTypeArguments(Token token) {
  TypeParamOrArgInfo typeArg = computeTypeParamOrArg(token);
  return optional('(', typeArg.skip(token).next) ? typeArg : noTypeParamOrArg;
}
