// Copyright (c) 2017, 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:convert';

import 'package:front_end/src/fasta/builder/qualified_name.dart';
import 'package:front_end/src/fasta/kernel/metadata_collector.dart';
import 'package:front_end/src/incremental/kernel_driver.dart';
import 'package:kernel/kernel.dart' as kernel;

/// Additional information that Analyzer needs for nodes.
class AnalyzerMetadata {
  /// If the node is a named constructor, the offset of the name.
  /// Otherwise `-1`.
  int constructorNameOffset = -1;

  /// Optional documentation comment, may be `null`.
  String documentationComment;

  /// If the node is an import library dependency, the offset of the prefix.
  /// Otherwise `-1`.
  int importPrefixOffset = -1;

  /// Return the [AnalyzerMetadata] for the [node], or `null` absent.
  static AnalyzerMetadata forNode(kernel.TreeNode node) {
    var repository =
        node.enclosingComponent.metadata[AnalyzerMetadataRepository.TAG];
    if (repository != null) {
      return repository.mapping[node];
    }
    return null;
  }
}

/// Analyzer specific implementation of [MetadataCollector].
class AnalyzerMetadataCollector implements MetadataCollector {
  @override
  final AnalyzerMetadataRepository repository =
      new AnalyzerMetadataRepository();

  @override
  void setConstructorNameOffset(kernel.Member node, Object name) {
    if (name is QualifiedName) {
      var metadata = repository._forWriting(node);
      metadata.constructorNameOffset = name.charOffset;
    }
  }

  @override
  void setDocumentationComment(kernel.NamedNode node, String comment) {
    var metadata = repository._forWriting(node);
    metadata.documentationComment = comment;
  }

  @override
  void setImportPrefixOffset(kernel.LibraryDependency node, int offset) {
    var metadata = repository._forWriting(node);
    metadata.importPrefixOffset = offset;
  }
}

/// Factory for creating Analyzer specific sink and repository.
class AnalyzerMetadataFactory implements MetadataFactory {
  @override
  int get version => 1;

  @override
  MetadataCollector newCollector() {
    return new AnalyzerMetadataCollector();
  }

  @override
  kernel.MetadataRepository newRepositoryForReading() {
    return new AnalyzerMetadataRepository();
  }
}

/// Index of metadata.
class AnalyzerMetadataIndex {
  final Map<kernel.Library, List<kernel.TreeNode>> libraryNodes = {};
  AnalyzerMetadataRepository repository;

  /// The [library] was invalidated, flush its metadata.
  void invalidate(kernel.Library library) {
    var nodes = libraryNodes.remove(library);
    nodes?.forEach(repository.mapping.remove);
  }

  /// A [newComponent] has been compiled, with new, and only new, metadata.
  /// Merge the existing [repository] into the new one, and replace it.
  void replaceComponent(kernel.Component newComponent) {
    AnalyzerMetadataRepository newRepository =
        newComponent.metadata[AnalyzerMetadataRepository.TAG];
    if (newRepository != null) {
      _indexNewMetadata(newRepository);
      if (repository != null) {
        newRepository.mapping.addAll(repository.mapping);
      }
      repository = newRepository;
    } else {
      newComponent.metadata[AnalyzerMetadataRepository.TAG] = repository;
    }
  }

  void _indexNewMetadata(AnalyzerMetadataRepository newRepository) {
    for (var node in newRepository.mapping.keys) {
      var library = _enclosingLibrary(node);
      assert(library != null);

      var nodes = libraryNodes[library];
      if (nodes == null) {
        nodes = <kernel.TreeNode>[];
        libraryNodes[library] = nodes;
      }

      nodes.add(node);
    }
  }

  static kernel.Library _enclosingLibrary(kernel.TreeNode node) {
    for (; node != null; node = node.parent) {
      if (node is kernel.Library) {
        return node;
      }
    }
    return null;
  }
}

/// Analyzer specific implementation of [kernel.MetadataRepository].
class AnalyzerMetadataRepository
    implements kernel.MetadataRepository<AnalyzerMetadata> {
  static const TAG = 'kernel.metadata.analyzer';

  @override
  final String tag = TAG;

  @override
  final Map<kernel.TreeNode, AnalyzerMetadata> mapping =
      <kernel.TreeNode, AnalyzerMetadata>{};

  @override
  AnalyzerMetadata readFromBinary(
      kernel.Node node, kernel.BinarySource source) {
    return new AnalyzerMetadata()
      ..constructorNameOffset = _readOffset(source)
      ..documentationComment = _readOptionalString(source)
      ..importPrefixOffset = _readOffset(source);
  }

  @override
  void writeToBinary(
      AnalyzerMetadata metadata, kernel.Node node, kernel.BinarySink sink) {
    _writeOffset(sink, metadata.constructorNameOffset);
    _writeOptionalString(sink, metadata.documentationComment);
    _writeOffset(sink, metadata.importPrefixOffset);
  }

  /// Return the existing or new [AnalyzerMetadata] instance for the [node].
  AnalyzerMetadata _forWriting(kernel.TreeNode node) {
    return mapping[node] ??= new AnalyzerMetadata();
  }

  int _readOffset(kernel.BinarySource source) {
    return source.readUint32() - 1;
  }

  String _readOptionalString(kernel.BinarySource source) {
    int flag = source.readByte();
    if (flag == 1) {
      List<int> bytes = source.readByteList();
      return utf8.decode(bytes);
    } else {
      return null;
    }
  }

  /// The [offset] value must be `>= -1`.
  void _writeOffset(kernel.BinarySink sink, int offset) {
    assert(offset >= -1);
    sink.writeUInt32(1 + offset);
  }

  void _writeOptionalString(kernel.BinarySink sink, String str) {
    if (str != null) {
      sink.writeByte(1);
      List<int> bytes = utf8.encode(str);
      sink.writeByteList(bytes);
    } else {
      sink.writeByte(0);
    }
  }
}
