// Copyright (c) 2023, 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:analyzer/dart/element/type.dart';
import 'package:analyzer/src/wolf/ir/call_descriptor.dart';
import 'package:analyzer/src/wolf/ir/ir.dart';

/// Container for a sequence of IR instructions, along with auxiliary tables
/// representing types, literal values, etc. that those instructions refer to.
///
/// See [BaseIRContainer] for additional information.
///
/// To construct a sequence of IR instructions, see [CodedIRWriter].
class CodedIRContainer extends BaseIRContainer {
  final List<CallDescriptor> _callDescriptorTable;
  final List<Object?> _literalTable;
  final List<DartType> _typeTable;

  CodedIRContainer(CodedIRWriter super.writer)
    : _callDescriptorTable = writer._callDescriptorTable,
      _literalTable = writer._literalTable,
      _typeTable = writer._typeTable;

  @override
  String callDescriptorRefToString(CallDescriptorRef callDescriptor) =>
      decodeCallDescriptor(callDescriptor).toString();

  @override
  int countParameters(TypeRef type) =>
      (decodeType(type) as FunctionType).formalParameters.length;

  CallDescriptor decodeCallDescriptor(CallDescriptorRef callDescriptorRef) =>
      _callDescriptorTable[callDescriptorRef.index];

  Object? decodeLiteral(LiteralRef literal) => _literalTable[literal.index];

  DartType decodeType(TypeRef type) => _typeTable[type.index];

  @override
  String literalRefToString(LiteralRef value) =>
      json.encode(decodeLiteral(value));

  /// Applies [f] to each call descriptor in the call descriptor table, and
  /// gathers the results into a list.
  List<T> mapCallDescriptors<T>(T Function(CallDescriptor) f) =>
      _callDescriptorTable.map(f).toList();

  @override
  String typeRefToString(TypeRef type) => decodeType(type).toString();
}

/// Writer of an IR instruction stream, which can encode types, literal values,
/// etc. into auxiliary tables.
///
/// See [RawIRWriter] for more information.
class CodedIRWriter extends RawIRWriter {
  final _callDescriptorTable = <CallDescriptor>[];
  final _callDescriptorToRef = <CallDescriptor, CallDescriptorRef>{};
  final _literalTable = <Object?>[];
  final _literalToRef = <Object?, LiteralRef>{};
  final _typeTable = <DartType>[];
  final _typeToRef = <DartType, TypeRef>{};

  CallDescriptorRef encodeCallDescriptor(CallDescriptor callDescriptor) =>
      // TODO(paulberry): is `putIfAbsent` the best-performing way to do this?
      _callDescriptorToRef.putIfAbsent(callDescriptor, () {
        var encoding = CallDescriptorRef(_callDescriptorTable.length);
        _callDescriptorTable.add(callDescriptor);
        return encoding;
      });

  LiteralRef encodeLiteral(Object? value) =>
      // TODO(paulberry): is `putIfAbsent` the best-performing way to do this?
      _literalToRef.putIfAbsent(value, () {
        var encoding = LiteralRef(_literalTable.length);
        _literalTable.add(value);
        return encoding;
      });

  TypeRef encodeType(DartType type) =>
      // TODO(paulberry): is `putIfAbsent` the best-performing way to do this?
      _typeToRef.putIfAbsent(type, () {
        var encoding = TypeRef(_typeTable.length);
        _typeTable.add(type);
        return encoding;
      });
}
