// Copyright (c) 2016, 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.kernel_enum_builder;

import 'package:front_end/src/fasta/kernel/kernel_shadow_ast.dart'
    show ShadowClass;

import 'package:kernel/ast.dart'
    show
        Arguments,
        Class,
        Constructor,
        ConstructorInvocation,
        DirectPropertyGet,
        Expression,
        Field,
        FieldInitializer,
        IntLiteral,
        InterfaceType,
        ListLiteral,
        ProcedureKind,
        ReturnStatement,
        StaticGet,
        StringLiteral,
        SuperInitializer,
        ThisExpression,
        TreeNode,
        VariableGet;

import '../fasta_codes.dart'
    show
        messageNoUnnamedConstructorInObject,
        noLength,
        templateDuplicatedName,
        templateEnumConstantSameNameAsEnclosing;

import '../modifier.dart' show constMask, finalMask, staticMask;

import '../source/source_class_builder.dart' show SourceClassBuilder;

import 'kernel_builder.dart'
    show
        Declaration,
        EnumBuilder,
        FormalParameterBuilder,
        KernelClassBuilder,
        KernelConstructorBuilder,
        KernelFieldBuilder,
        KernelFormalParameterBuilder,
        KernelLibraryBuilder,
        KernelNamedTypeBuilder,
        KernelProcedureBuilder,
        KernelTypeBuilder,
        LibraryBuilder,
        MemberBuilder,
        MetadataBuilder,
        Scope;

import 'metadata_collector.dart';

class KernelEnumBuilder extends SourceClassBuilder
    implements EnumBuilder<KernelTypeBuilder, InterfaceType> {
  final List<Object> constantNamesAndOffsetsAndDocs;

  final KernelNamedTypeBuilder intType;

  final KernelNamedTypeBuilder stringType;

  final KernelNamedTypeBuilder objectType;

  final KernelNamedTypeBuilder listType;

  KernelEnumBuilder.internal(
      List<MetadataBuilder> metadata,
      String name,
      Scope scope,
      Scope constructors,
      ShadowClass cls,
      this.constantNamesAndOffsetsAndDocs,
      this.intType,
      this.listType,
      this.objectType,
      this.stringType,
      LibraryBuilder parent,
      int startCharOffset,
      int charOffset)
      : super(metadata, 0, name, null, null, null, scope, constructors, parent,
            null, startCharOffset, charOffset, TreeNode.noOffset, cls);

  factory KernelEnumBuilder(
      MetadataCollector metadataCollector,
      List<MetadataBuilder> metadata,
      String name,
      List<Object> constantNamesAndOffsetsAndDocs,
      KernelLibraryBuilder parent,
      int charOffset,
      int charEndOffset) {
    constantNamesAndOffsetsAndDocs ??= const <Object>[];
    // TODO(ahe): These types shouldn't be looked up in scope, they come
    // directly from dart:core.
    KernelTypeBuilder intType = new KernelNamedTypeBuilder("int", null);
    KernelTypeBuilder stringType = new KernelNamedTypeBuilder("String", null);
    KernelNamedTypeBuilder objectType =
        new KernelNamedTypeBuilder("Object", null);
    ShadowClass cls = new ShadowClass(name: name);
    Map<String, MemberBuilder> members = <String, MemberBuilder>{};
    Map<String, MemberBuilder> constructors = <String, MemberBuilder>{};
    KernelNamedTypeBuilder selfType = new KernelNamedTypeBuilder(name, null);
    KernelTypeBuilder listType =
        new KernelNamedTypeBuilder("List", <KernelTypeBuilder>[selfType]);

    /// metadata class E {
    ///   final int index;
    ///   final String _name;
    ///   const E(this.index, this._name);
    ///   static const E id0 = const E(0, 'E.id0');
    ///   ...
    ///   static const E idn-1 = const E(n - 1, 'E.idn-1');
    ///   static const List<E> values = const <E>[id0, ..., idn-1];
    ///   String toString() => _name;
    /// }

    members["index"] = new KernelFieldBuilder(
        null, intType, "index", finalMask, parent, charOffset, null, true);
    members["_name"] = new KernelFieldBuilder(
        null, stringType, "_name", finalMask, parent, charOffset, null, true);
    KernelConstructorBuilder constructorBuilder = new KernelConstructorBuilder(
        null,
        constMask,
        null,
        "",
        null,
        <FormalParameterBuilder>[
          new KernelFormalParameterBuilder(
              null, 0, intType, "index", true, parent, charOffset),
          new KernelFormalParameterBuilder(
              null, 0, stringType, "_name", true, parent, charOffset)
        ],
        parent,
        charOffset,
        charOffset,
        charOffset,
        charEndOffset);
    constructors[""] = constructorBuilder;
    KernelFieldBuilder valuesBuilder = new KernelFieldBuilder(null, listType,
        "values", constMask | staticMask, parent, charOffset, null, true);
    members["values"] = valuesBuilder;
    KernelProcedureBuilder toStringBuilder = new KernelProcedureBuilder(
        null,
        0,
        stringType,
        "toString",
        null,
        null,
        ProcedureKind.Method,
        parent,
        charOffset,
        charOffset,
        charOffset,
        charEndOffset);
    members["toString"] = toStringBuilder;
    String className = name;
    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 4) {
      List<MetadataBuilder> metadata = constantNamesAndOffsetsAndDocs[i];
      String name = constantNamesAndOffsetsAndDocs[i + 1];
      int charOffset = constantNamesAndOffsetsAndDocs[i + 2];
      String documentationComment = constantNamesAndOffsetsAndDocs[i + 3];
      if (members.containsKey(name)) {
        parent.addCompileTimeError(templateDuplicatedName.withArguments(name),
            charOffset, noLength, parent.fileUri);
        constantNamesAndOffsetsAndDocs[i + 1] = null;
        continue;
      }
      if (name == className) {
        parent.addCompileTimeError(
            templateEnumConstantSameNameAsEnclosing.withArguments(name),
            charOffset,
            noLength,
            parent.fileUri);
        constantNamesAndOffsetsAndDocs[i + 1] = null;
        continue;
      }
      KernelFieldBuilder fieldBuilder = new KernelFieldBuilder(
          metadata,
          selfType,
          name,
          constMask | staticMask,
          parent,
          charOffset,
          null,
          true);
      metadataCollector?.setDocumentationComment(
          fieldBuilder.target, documentationComment);
      members[name] = fieldBuilder;
    }
    final int startCharOffset =
        metadata == null ? charOffset : metadata.first.charOffset;
    KernelEnumBuilder enumBuilder = new KernelEnumBuilder.internal(
        metadata,
        name,
        new Scope(members, null, parent.scope, "enum $name",
            isModifiable: false),
        new Scope(constructors, null, null, "constructors",
            isModifiable: false),
        cls,
        constantNamesAndOffsetsAndDocs,
        intType,
        listType,
        objectType,
        stringType,
        parent,
        startCharOffset,
        charOffset);
    // TODO(sigmund): dynamic should be `covariant MemberBuilder`.
    void setParent(String name, dynamic b) {
      MemberBuilder builder = b;
      builder.parent = enumBuilder;
    }

    members.forEach(setParent);
    constructors.forEach(setParent);
    selfType.bind(enumBuilder);
    ShadowClass.setBuilder(cls, enumBuilder);
    return enumBuilder;
  }

  KernelTypeBuilder get mixedInType => null;

  InterfaceType buildType(
      LibraryBuilder library, List<KernelTypeBuilder> arguments) {
    return cls.rawType;
  }

  @override
  Class build(KernelLibraryBuilder libraryBuilder, LibraryBuilder coreLibrary) {
    cls.isEnum = true;
    intType.resolveIn(coreLibrary.scope, charOffset, fileUri, libraryBuilder);
    stringType.resolveIn(
        coreLibrary.scope, charOffset, fileUri, libraryBuilder);
    objectType.resolveIn(
        coreLibrary.scope, charOffset, fileUri, libraryBuilder);
    listType.resolveIn(coreLibrary.scope, charOffset, fileUri, libraryBuilder);

    KernelFieldBuilder indexFieldBuilder = this["index"];
    Field indexField = indexFieldBuilder.build(libraryBuilder);
    KernelFieldBuilder nameFieldBuilder = this["_name"];
    Field nameField = nameFieldBuilder.build(libraryBuilder);
    KernelProcedureBuilder toStringBuilder = this["toString"];
    toStringBuilder.body = new ReturnStatement(
        new DirectPropertyGet(new ThisExpression(), nameField));
    List<Expression> values = <Expression>[];
    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 4) {
      String name = constantNamesAndOffsetsAndDocs[i + 1];
      if (name != null) {
        KernelFieldBuilder builder = this[name];
        values.add(new StaticGet(builder.build(libraryBuilder)));
      }
    }
    KernelFieldBuilder valuesBuilder = this["values"];
    valuesBuilder.build(libraryBuilder);
    valuesBuilder.initializer =
        new ListLiteral(values, typeArgument: cls.rawType, isConst: true);
    KernelConstructorBuilder constructorBuilder = constructorScopeBuilder[""];
    Constructor constructor = constructorBuilder.build(libraryBuilder);
    constructor.initializers.insert(
        0,
        new FieldInitializer(indexField,
            new VariableGet(constructor.function.positionalParameters[0]))
          ..parent = constructor);
    constructor.initializers.insert(
        1,
        new FieldInitializer(nameField,
            new VariableGet(constructor.function.positionalParameters[1]))
          ..parent = constructor);
    KernelClassBuilder objectClass = objectType.declaration;
    MemberBuilder superConstructor = objectClass.findConstructorOrFactory(
        "", charOffset, fileUri, libraryBuilder);
    if (superConstructor == null || !superConstructor.isConstructor) {
      // TODO(ahe): Ideally, we would also want to check that [Object]'s
      // unnamed constructor requires no arguments. But that information isn't
      // always available at this point, and it's not really a situation that
      // can happen unless you start modifying the SDK sources.
      addCompileTimeError(messageNoUnnamedConstructorInObject, -1, noLength);
    } else {
      constructor.initializers.add(
          new SuperInitializer(superConstructor.target, new Arguments.empty())
            ..parent = constructor);
    }
    int index = 0;
    for (int i = 0; i < constantNamesAndOffsetsAndDocs.length; i += 4) {
      String constant = constantNamesAndOffsetsAndDocs[i + 1];
      if (constant != null) {
        KernelFieldBuilder field = this[constant];
        field.build(libraryBuilder);
        Arguments arguments = new Arguments(<Expression>[
          new IntLiteral(index++),
          new StringLiteral("$name.$constant")
        ]);
        field.initializer =
            new ConstructorInvocation(constructor, arguments, isConst: true);
      }
    }
    return super.build(libraryBuilder, coreLibrary);
  }

  @override
  Declaration findConstructorOrFactory(
      String name, int charOffset, Uri uri, LibraryBuilder library) {
    return null;
  }
}
