// Copyright (c) 2014, 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 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/dart/ast/token.dart';
import 'package:analyzer/dart/element/element.dart';
import 'package:analyzer/dart/element/type.dart';
import 'package:analyzer/src/dart/element/element.dart';
import 'package:analyzer/src/dart/element/type.dart';
import 'package:analyzer/src/generated/constant.dart';
import 'package:analyzer/src/generated/engine.dart' show AnalysisContext;
import 'package:analyzer/src/generated/resolver.dart';
import 'package:analyzer/src/generated/sdk.dart' show DartSdk;
import 'package:analyzer/src/generated/source.dart' show Source;
import 'package:analyzer/src/generated/testing/ast_test_factory.dart';
import 'package:analyzer/src/generated/testing/element_factory.dart';
import 'package:analyzer/src/string_source.dart';

/**
 * A type provider that can be used by tests without creating the element model
 * for the core library.
 */
class TestTypeProvider extends TypeProviderBase {
  /**
   * The type representing the built-in type 'bool'.
   */
  InterfaceType _boolType;

  /**
   * The type representing the type 'bottom'.
   */
  DartType _bottomType;

  /**
   * The type representing the built-in type 'double'.
   */
  InterfaceType _doubleType;

  /**
   * The type representing the built-in type 'deprecated'.
   */
  InterfaceType _deprecatedType;

  /**
   * The type representing the built-in type 'dynamic'.
   */
  DartType _dynamicType;

  /**
   * The type representing the built-in type 'Function'.
   */
  InterfaceType _functionType;

  /**
   * The type representing 'Future<dynamic>'
   */
  InterfaceType _futureDynamicType;

  /**
   * The type representing 'Future<Null>'
   */
  InterfaceType _futureNullType;

  /**
   * The type representing the built-in type 'FutureOr'
   */
  InterfaceType _futureOrNullType;

  /**
   * The type representing the built-in type 'FutureOr'
   */
  InterfaceType _futureOrType;

  /**
   * The type representing the built-in type 'Future'
   */
  InterfaceType _futureType;

  /**
   * The type representing the built-in type 'int'.
   */
  InterfaceType _intType;

  /**
   * The type representing 'Iterable<dynamic>'
   */
  InterfaceType _iterableDynamicType;

  /**
   * The type representing the built-in type 'Iterable'.
   */
  InterfaceType _iterableType;

  /**
   * The type representing the built-in type 'Iterator'.
   */
  InterfaceType _iteratorType;

  /**
   * The type representing the built-in type 'List'.
   */
  InterfaceType _listType;

  /**
   * The type representing the built-in type 'Map'.
   */
  InterfaceType _mapType;

  /**
   * An shared object representing the value 'null'.
   */
  DartObjectImpl _nullObject;

  /**
   * The type representing the built-in type 'Null'.
   */
  InterfaceType _nullType;

  /**
   * The type representing the built-in type 'num'.
   */
  InterfaceType _numType;

  /**
   * The type representing the built-in type 'Object'.
   */
  InterfaceType _objectType;

  /**
   * The type representing the built-in type 'StackTrace'.
   */
  InterfaceType _stackTraceType;

  /**
   * The type representing 'Stream<dynamic>'.
   */
  InterfaceType _streamDynamicType;

  /**
   * The type representing the built-in type 'Stream'.
   */
  InterfaceType _streamType;

  /**
   * The type representing the built-in type 'String'.
   */
  InterfaceType _stringType;

  /**
   * The type representing the built-in type 'Symbol'.
   */
  InterfaceType _symbolType;

  /**
   * The type representing the built-in type 'Type'.
   */
  InterfaceType _typeType;

  /**
   * The type representing type names that can't be resolved.
   */
  DartType _undefinedType;

  /**
   * The analysis context, if any. Used to create an appropriate 'dart:async'
   * library to back `Future<T>`.
   */
  AnalysisContext _context;

  TestTypeProvider([this._context]);

  @override
  InterfaceType get boolType {
    if (_boolType == null) {
      ClassElementImpl boolElement = ElementFactory.classElement2("bool");
      _boolType = boolElement.type;
      ConstructorElementImpl fromEnvironment =
          ElementFactory.constructorElement(
              boolElement, "fromEnvironment", true);
      fromEnvironment.parameters = <ParameterElement>[
        ElementFactory.requiredParameter2("name", stringType),
        ElementFactory.namedParameter3("defaultValue",
            type: _boolType,
            initializer: AstTestFactory.booleanLiteral(false),
            initializerCode: 'false')
      ];
      fromEnvironment.factory = true;
      fromEnvironment.isCycleFree = true;
      boolElement.constructors = <ConstructorElement>[fromEnvironment];
    }
    return _boolType;
  }

  @override
  DartType get bottomType {
    if (_bottomType == null) {
      _bottomType = BottomTypeImpl.instance;
    }
    return _bottomType;
  }

  @override
  InterfaceType get deprecatedType {
    if (_deprecatedType == null) {
      ClassElementImpl deprecatedElement =
          ElementFactory.classElement2("Deprecated");
      FieldElementImpl expiresField = ElementFactory.fieldElement(
          'expires', false, true, false, stringType);
      deprecatedElement.fields = <FieldElement>[expiresField];
      deprecatedElement.accessors = <PropertyAccessorElement>[
        expiresField.getter
      ];
      ConstructorElementImpl constructor = ElementFactory.constructorElement(
          deprecatedElement, '', true, [stringType]);
      (constructor.parameters[0] as ParameterElementImpl).name = 'expires';
      ConstructorFieldInitializer expiresInit =
          AstTestFactory.constructorFieldInitializer(
              true, 'expires', AstTestFactory.identifier3('expires'));
      expiresInit.fieldName.staticElement = expiresField;
      (expiresInit.expression as SimpleIdentifier).staticElement =
          constructor.parameters[0];
      constructor.constantInitializers = <ConstructorInitializer>[expiresInit];
      deprecatedElement.constructors = <ConstructorElement>[constructor];
      _deprecatedType = deprecatedElement.type;
    }
    return _deprecatedType;
  }

  @override
  InterfaceType get doubleType {
    if (_doubleType == null) {
      _initializeNumericTypes();
    }
    return _doubleType;
  }

  @override
  DartType get dynamicType {
    if (_dynamicType == null) {
      _dynamicType = DynamicTypeImpl.instance;
    }
    return _dynamicType;
  }

  @override
  InterfaceType get functionType {
    if (_functionType == null) {
      ClassElementImpl functionClass = ElementFactory.classElement2("Function");
      functionClass.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(functionClass, null, false)
      ];
      _functionType = functionClass.type;
    }
    return _functionType;
  }

  @override
  InterfaceType get futureDynamicType {
    if (_futureDynamicType == null) {
      _futureDynamicType = futureType.instantiate(<DartType>[dynamicType]);
    }
    return _futureDynamicType;
  }

  @override
  InterfaceType get futureNullType {
    if (_futureNullType == null) {
      _futureNullType = futureType.instantiate(<DartType>[nullType]);
    }
    return _futureNullType;
  }

  @override
  InterfaceType get futureOrNullType {
    if (_futureOrNullType == null) {
      _futureOrNullType = futureOrType.instantiate(<DartType>[nullType]);
    }
    return _futureOrNullType;
  }

  @override
  InterfaceType get futureOrType {
    if (_futureOrType == null) {
      _initDartAsync();
    }
    return _futureOrType;
  }

  @override
  InterfaceType get futureType {
    if (_futureType == null) {
      _initDartAsync();
    }
    return _futureType;
  }

  @override
  InterfaceType get intType {
    if (_intType == null) {
      _initializeNumericTypes();
    }
    return _intType;
  }

  @override
  InterfaceType get iterableDynamicType {
    if (_iterableDynamicType == null) {
      _iterableDynamicType = iterableType.instantiate(<DartType>[dynamicType]);
    }
    return _iterableDynamicType;
  }

  @override
  InterfaceType get iterableType {
    if (_iterableType == null) {
      ClassElementImpl iterableElement =
          ElementFactory.classElement2("Iterable", ["E"]);
      _iterableType = iterableElement.type;
      DartType eType = iterableElement.typeParameters[0].type;
      _setAccessors(iterableElement, <PropertyAccessorElement>[
        ElementFactory.getterElement(
            "iterator", false, iteratorType.instantiate(<DartType>[eType])),
        ElementFactory.getterElement("last", false, eType)
      ]);
      iterableElement.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(iterableElement, '', true)
          ..isCycleFree = true
      ];
      _propagateTypeArguments(iterableElement);
    }
    return _iterableType;
  }

  InterfaceType get iteratorType {
    if (_iteratorType == null) {
      ClassElementImpl iteratorElement =
          ElementFactory.classElement2("Iterator", ["E"]);
      _iteratorType = iteratorElement.type;
      DartType eType = iteratorElement.typeParameters[0].type;
      _setAccessors(iteratorElement, <PropertyAccessorElement>[
        ElementFactory.getterElement("current", false, eType)
      ]);
      iteratorElement.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(iteratorElement, null, false)
      ];
      _propagateTypeArguments(iteratorElement);
    }
    return _iteratorType;
  }

  @override
  InterfaceType get listType {
    if (_listType == null) {
      ClassElementImpl listElement =
          ElementFactory.classElement2("List", ["E"]);
      listElement.constructors = <ConstructorElement>[
        ElementFactory.constructorElement2(listElement, null)
      ];
      _listType = listElement.type;
      DartType eType = listElement.typeParameters[0].type;
      InterfaceType iterableType =
          this.iterableType.instantiate(<DartType>[eType]);
      listElement.interfaces = <InterfaceType>[iterableType];
      _setAccessors(listElement, <PropertyAccessorElement>[
        ElementFactory.getterElement("length", false, intType)
      ]);
      listElement.methods = <MethodElement>[
        ElementFactory.methodElement("[]", eType, [intType]),
        ElementFactory.methodElement(
            "[]=", VoidTypeImpl.instance, [intType, eType]),
        ElementFactory.methodElement("add", VoidTypeImpl.instance, [eType])
      ];
      _propagateTypeArguments(listElement);
    }
    return _listType;
  }

  @override
  InterfaceType get mapType {
    if (_mapType == null) {
      ClassElementImpl mapElement =
          ElementFactory.classElement2("Map", ["K", "V"]);
      _mapType = mapElement.type;
      DartType kType = mapElement.typeParameters[0].type;
      DartType vType = mapElement.typeParameters[1].type;
      _setAccessors(mapElement, <PropertyAccessorElement>[
        ElementFactory.getterElement("length", false, intType)
      ]);
      mapElement.methods = <MethodElement>[
        ElementFactory.methodElement("[]", vType, [objectType]),
        ElementFactory.methodElement(
            "[]=", VoidTypeImpl.instance, [kType, vType])
      ];
      mapElement.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(mapElement, '', false)
          ..external = true
          ..factory = true
      ];
      _propagateTypeArguments(mapElement);
    }
    return _mapType;
  }

  @override
  DartObjectImpl get nullObject {
    if (_nullObject == null) {
      _nullObject = new DartObjectImpl(nullType, NullState.NULL_STATE);
    }
    return _nullObject;
  }

  @override
  InterfaceType get nullType {
    if (_nullType == null) {
      var nullElement = ElementFactory.classElement2("Null");
      nullElement.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(
            nullElement, '_uninstantiatable', false)
          ..factory = true
      ];
      // Create a library element for "dart:core"
      // This enables the "isDartCoreNull" getter.
      var library = new LibraryElementImpl.forNode(
          _context, AstTestFactory.libraryIdentifier2(["dart.core"]));
      var unit = new CompilationUnitElementImpl();
      library.definingCompilationUnit = unit;
      unit.librarySource = unit.source = new StringSource('', null);

      nullElement.enclosingElement = library;
      _nullType = nullElement.type;
    }
    return _nullType;
  }

  @override
  InterfaceType get numType {
    if (_numType == null) {
      _initializeNumericTypes();
    }
    return _numType;
  }

  @override
  InterfaceType get objectType {
    if (_objectType == null) {
      ClassElementImpl objectElement = ElementFactory.object;
      _objectType = objectElement.type;
      ConstructorElementImpl constructor =
          ElementFactory.constructorElement(objectElement, '', true);
      constructor.constantInitializers = <ConstructorInitializer>[];
      objectElement.constructors = <ConstructorElement>[constructor];
      objectElement.methods = <MethodElement>[
        ElementFactory.methodElement("toString", stringType),
        ElementFactory.methodElement("==", boolType, [_objectType]),
        ElementFactory.methodElement("noSuchMethod", dynamicType, [dynamicType])
      ];
      _setAccessors(objectElement, <PropertyAccessorElement>[
        ElementFactory.getterElement("hashCode", false, intType),
        ElementFactory.getterElement("runtimeType", false, typeType)
      ]);
    }
    return _objectType;
  }

  @override
  InterfaceType get stackTraceType {
    if (_stackTraceType == null) {
      ClassElementImpl stackTraceElement =
          ElementFactory.classElement2("StackTrace");
      stackTraceElement.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(stackTraceElement, null, false)
      ];
      _stackTraceType = stackTraceElement.type;
    }
    return _stackTraceType;
  }

  @override
  InterfaceType get streamDynamicType {
    if (_streamDynamicType == null) {
      _streamDynamicType = streamType.instantiate(<DartType>[dynamicType]);
    }
    return _streamDynamicType;
  }

  @override
  InterfaceType get streamType {
    if (_streamType == null) {
      _streamType = ElementFactory.classElement2("Stream", ["T"]).type;
    }
    return _streamType;
  }

  @override
  InterfaceType get stringType {
    if (_stringType == null) {
      ClassElementImpl stringElement = ElementFactory.classElement2("String");
      _stringType = stringElement.type;
      _setAccessors(stringElement, <PropertyAccessorElement>[
        ElementFactory.getterElement("isEmpty", false, boolType),
        ElementFactory.getterElement("length", false, intType),
        ElementFactory.getterElement(
            "codeUnits", false, listType.instantiate(<DartType>[intType]))
      ]);
      stringElement.methods = <MethodElement>[
        ElementFactory.methodElement("+", _stringType, [_stringType]),
        ElementFactory.methodElement("toLowerCase", _stringType),
        ElementFactory.methodElement("toUpperCase", _stringType)
      ];
      ConstructorElementImpl fromEnvironment =
          ElementFactory.constructorElement(
              stringElement, "fromEnvironment", true);
      fromEnvironment.parameters = <ParameterElement>[
        ElementFactory.requiredParameter2("name", stringType),
        ElementFactory.namedParameter3("defaultValue", type: _stringType)
      ];
      fromEnvironment.factory = true;
      fromEnvironment.isCycleFree = true;
      stringElement.constructors = <ConstructorElement>[fromEnvironment];
    }
    return _stringType;
  }

  @override
  InterfaceType get symbolType {
    if (_symbolType == null) {
      ClassElementImpl symbolClass = ElementFactory.classElement2("Symbol");
      ConstructorElementImpl constructor = ElementFactory.constructorElement(
          symbolClass, '', true, [stringType]);
      constructor.factory = true;
      constructor.isCycleFree = true;
      symbolClass.constructors = <ConstructorElement>[constructor];
      _symbolType = symbolClass.type;
    }
    return _symbolType;
  }

  @override
  InterfaceType get typeType {
    if (_typeType == null) {
      ClassElementImpl typeClass = ElementFactory.classElement2("Type");
      typeClass.constructors = <ConstructorElement>[
        ElementFactory.constructorElement(typeClass, null, false)
          ..isSynthetic = true
      ];
      _typeType = typeClass.type;
    }
    return _typeType;
  }

  @override
  DartType get undefinedType {
    if (_undefinedType == null) {
      _undefinedType = UndefinedTypeImpl.instance;
    }
    return _undefinedType;
  }

  void _initDartAsync() {
    Source asyncSource = _context.sourceFactory.forUri(DartSdk.DART_ASYNC);
    _context.setContents(asyncSource, "");
    CompilationUnitElementImpl asyncUnit = new CompilationUnitElementImpl();
    LibraryElementImpl asyncLibrary = new LibraryElementImpl.forNode(
        _context, AstTestFactory.libraryIdentifier2(["dart.async"]));
    asyncLibrary.definingCompilationUnit = asyncUnit;
    asyncUnit.librarySource = asyncUnit.source = asyncSource;

    ClassElementImpl future = ElementFactory.classElement2("Future", ["T"]);
    _futureType = future.type;
    asyncUnit.types = <ClassElement>[future];
    ClassElementImpl futureOr = ElementFactory.classElement2("FutureOr", ["T"]);
    _futureOrType = futureOr.type;
    asyncUnit.types = <ClassElement>[future, futureOr];
  }

  /**
   * Initialize the numeric types. They are created as a group so that we can
   * (a) create the right hierarchy and (b) add members to them.
   */
  void _initializeNumericTypes() {
    //
    // Create the type hierarchy.
    //
    ClassElementImpl numElement = ElementFactory.classElement2("num");
    _numType = numElement.type;
    ClassElementImpl intElement = ElementFactory.classElement("int", _numType);
    _intType = intElement.type;
    ClassElementImpl doubleElement =
        ElementFactory.classElement("double", _numType);
    _doubleType = doubleElement.type;
    //
    // Force the referenced types to be cached.
    //
    objectType;
    boolType;
    nullType;
    stringType;
    //
    // Add the methods.
    //
    numElement.methods = <MethodElement>[
      ElementFactory.methodElement("+", _numType, [_numType]),
      ElementFactory.methodElement("-", _numType, [_numType]),
      ElementFactory.methodElement("*", _numType, [_numType]),
      ElementFactory.methodElement("%", _numType, [_numType]),
      ElementFactory.methodElement("/", _doubleType, [_numType]),
      ElementFactory.methodElement("~/", _numType, [_numType]),
      ElementFactory.methodElement("-", _numType),
      ElementFactory.methodElement("remainder", _numType, [_numType]),
      ElementFactory.methodElement("<", _boolType, [_numType]),
      ElementFactory.methodElement("<=", _boolType, [_numType]),
      ElementFactory.methodElement(">", _boolType, [_numType]),
      ElementFactory.methodElement(">=", _boolType, [_numType]),
      ElementFactory.methodElement("==", _boolType, [_objectType]),
      ElementFactory.methodElement("abs", _numType),
      ElementFactory.methodElement("floor", _numType),
      ElementFactory.methodElement("ceil", _numType),
      ElementFactory.methodElement("round", _numType),
      ElementFactory.methodElement("truncate", _numType),
      ElementFactory.methodElement("toInt", _intType),
      ElementFactory.methodElement("toDouble", _doubleType),
      ElementFactory.methodElement("toStringAsFixed", _stringType, [_intType]),
      ElementFactory.methodElement(
          "toStringAsExponential", _stringType, [_intType]),
      ElementFactory.methodElement(
          "toStringAsPrecision", _stringType, [_intType]),
      ElementFactory.methodElement("toRadixString", _stringType, [_intType])
    ];
    numElement.accessors = [
      ElementFactory.getterElement('isInfinite', false, _boolType),
      ElementFactory.getterElement('isNaN', false, _boolType),
      ElementFactory.getterElement('isNegative', false, _boolType),
    ];
    intElement.methods = <MethodElement>[
      ElementFactory.methodElement("&", _intType, [_intType]),
      ElementFactory.methodElement("|", _intType, [_intType]),
      ElementFactory.methodElement("^", _intType, [_intType]),
      ElementFactory.methodElement("~", _intType),
      ElementFactory.methodElement("<<", _intType, [_intType]),
      ElementFactory.methodElement(">>", _intType, [_intType]),
      ElementFactory.methodElement("-", _intType),
      ElementFactory.methodElement("abs", _intType),
      ElementFactory.methodElement("round", _intType),
      ElementFactory.methodElement("floor", _intType),
      ElementFactory.methodElement("ceil", _intType),
      ElementFactory.methodElement("truncate", _intType),
      ElementFactory.methodElement("toString", _stringType)
    ];
    ConstructorElementImpl fromEnvironment =
        ElementFactory.constructorElement(intElement, "fromEnvironment", true);
    fromEnvironment.parameters = <ParameterElement>[
      ElementFactory.requiredParameter2("name", stringType),
      ElementFactory.namedParameter3("defaultValue", type: _intType)
    ];
    fromEnvironment.factory = true;
    fromEnvironment.isCycleFree = true;
    numElement.constructors = <ConstructorElement>[
      ElementFactory.constructorElement(numElement, null, false)
        ..isSynthetic = true
    ];
    intElement.constructors = <ConstructorElement>[fromEnvironment];
    doubleElement.constructors = <ConstructorElement>[
      ElementFactory.constructorElement(doubleElement, null, false)
        ..isSynthetic = true
    ];
    ConstFieldElementImpl varINFINITY = ElementFactory.fieldElement(
        "INFINITY", true, false, true, _doubleType,
        initializer: AstTestFactory.doubleLiteral(double.infinity));
    varINFINITY.constantInitializer = AstTestFactory.binaryExpression(
        AstTestFactory.integer(1), TokenType.SLASH, AstTestFactory.integer(0));
    List<FieldElement> fields = <FieldElement>[
      ElementFactory.fieldElement("NAN", true, false, true, _doubleType,
          initializer: AstTestFactory.doubleLiteral(double.nan)),
      varINFINITY,
      ElementFactory.fieldElement(
          "NEGATIVE_INFINITY", true, false, true, _doubleType,
          initializer: AstTestFactory.doubleLiteral(double.negativeInfinity)),
      ElementFactory.fieldElement(
          "MIN_POSITIVE", true, false, true, _doubleType,
          initializer: AstTestFactory.doubleLiteral(double.minPositive)),
      ElementFactory.fieldElement("MAX_FINITE", true, false, true, _doubleType,
          initializer: AstTestFactory.doubleLiteral(double.maxFinite))
    ];
    doubleElement.fields = fields;
    int fieldCount = fields.length;
    List<PropertyAccessorElement> accessors =
        new List<PropertyAccessorElement>(fieldCount);
    for (int i = 0; i < fieldCount; i++) {
      accessors[i] = fields[i].getter;
    }
    doubleElement.accessors = accessors;
    doubleElement.methods = <MethodElement>[
      ElementFactory.methodElement("remainder", _doubleType, [_numType]),
      ElementFactory.methodElement("+", _doubleType, [_numType]),
      ElementFactory.methodElement("-", _doubleType, [_numType]),
      ElementFactory.methodElement("*", _doubleType, [_numType]),
      ElementFactory.methodElement("%", _doubleType, [_numType]),
      ElementFactory.methodElement("/", _doubleType, [_numType]),
      ElementFactory.methodElement("~/", _doubleType, [_numType]),
      ElementFactory.methodElement("-", _doubleType),
      ElementFactory.methodElement("abs", _doubleType),
      ElementFactory.methodElement("round", _doubleType),
      ElementFactory.methodElement("floor", _doubleType),
      ElementFactory.methodElement("ceil", _doubleType),
      ElementFactory.methodElement("truncate", _doubleType),
      ElementFactory.methodElement("toString", _stringType)
    ];
  }

  /**
   * Given a [classElement] representing a class with type parameters, propagate
   * those type parameters to all of the accessors, methods and constructors
   * defined for the class.
   */
  void _propagateTypeArguments(ClassElementImpl classElement) {
    for (PropertyAccessorElement accessor in classElement.accessors) {
      (accessor as ExecutableElementImpl).type = new FunctionTypeImpl(accessor);
    }
    for (MethodElement method in classElement.methods) {
      (method as ExecutableElementImpl).type = new FunctionTypeImpl(method);
    }
  }

  /**
   * Set the accessors for the given class [element] to the given [accessors]
   * and also set the fields to those that correspond to the accessors.
   */
  void _setAccessors(
      ClassElementImpl element, List<PropertyAccessorElement> accessors) {
    element.accessors = accessors;
    element.fields = accessors
        .map((PropertyAccessorElement accessor) => accessor.variable)
        .cast<FieldElement>()
        .toList();
  }
}
