// Copyright (c) 2021, 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.

// VMOptions=--no-enable-fast-object-copy
// VMOptions=--enable-fast-object-copy
// VMOptions=--no-enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
// VMOptions=--enable-fast-object-copy --gc-on-foc-slow-path --force-evacuation
// VMOptions=--test_il_serialization

import 'dart:async';
import 'dart:ffi';
import 'dart:isolate';
import 'dart:typed_data';

import 'package:expect/expect.dart';
import 'package:ffi/ffi.dart';

import 'fast_object_copy_test.dart'
    show UserObject, SendReceiveTestBase, notAllocatableInTLAB;

typedef TypeLiteral<T> = T;

topLevelClosure(a, b) {}
topLevelClosureG<T>(T a, T b) {}
Type getType<T>() => T;
Type invokeWithType<T>(Type Function<T>() fun) => fun<T>();

class A<T> {
  dynamic m<H>(T a, H b) => this;
}

List<String> list = <String>["Hello", "world"];

// When running with isolate groups enabled, we can share all of the following
// objects.
final sharableObjects = [
  1,
  0xffffffffffffffff,
  'foobar',
  const UserObject(1, 1.2, ''),
  (() {
    final rp = ReceivePort();
    final sp = rp.sendPort;
    rp.close();
    return sp;
  })(),
  () {
    innerClosure(a, b) {}
    return innerClosure;
  }(),
  () {
    innerClosureG<T>(T a, T b) {}
    return innerClosureG;
  }(),
  () {
    innerClosureG<T>() {
      innerClosureG2<H>(T a, H b) {}
      return innerClosureG2;
    }

    return innerClosureG<int>();
  }(),
  () {
    innerClosureG<T>(T a, T b) {}
    final Function(int, int) partialInstantiatedInnerClosure = innerClosureG;
    return partialInstantiatedInnerClosure;
  }(),
  () {
    return topLevelClosureG;
  }(),
  () {
    final Function(int, int) partialInstantiatedInnerClosure = topLevelClosureG;
    return partialInstantiatedInnerClosure;
  }(),

  // Types: Type literal constants
  getType<int>(),
  getType<(int, double, Object)>(),
  getType<void Function(int, double, Object)>(),
  getType<T Function<T>(int, double, T)>(),

  // Types: Instantiated & canonicalized types.
  invokeWithType<int>(<T>() => getType<T>()),
  invokeWithType<int>(<T>() => getType<(T, T)>()),
  invokeWithType<int>(<T>() => getType<List<T>>()),
  invokeWithType<int>(<T>() => getType<T Function(T, T)>()),
  invokeWithType<int>(<T>() => getType<H Function<H>(T, T)>()),

  // Types: Instantiated but non-canonicalized types.
  invokeWithType<int>(<T>() => TypeLiteral<T>),
  invokeWithType<int>(<T>() => TypeLiteral<(T, T)>),
  invokeWithType<int>(<T>() => TypeLiteral<List<T>>),
  invokeWithType<int>(<T>() => TypeLiteral<T Function(T, T)>),
  invokeWithType<int>(<T>() => TypeLiteral<H Function<H>(T, T)>),

  const [1, 2, 3],
  const {1: 1, 2: 2, 3: 2},
  const {1, 2, 3},
  RegExp('a'),
  Isolate.current.pauseCapability,
  Int32x4(1, 2, 3, 4),
  Float32x4(1.0, 2.0, 3.0, 4.0),
  Float64x2(1.0, 2.0),
  StackTrace.current,
  Pointer<Int8>.fromAddress(0xdeadbeef),
  DeeplyImmutable(
    someString: 'someString',
    someNullableString: 'someString',
    someInt: 3,
    someDouble: 3.3,
    someBool: false,
    someNull: null,
    someInt32x4: Int32x4(0, 1, 2, 3),
    someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
    someFloat64x2: Float64x2(4.4, 5.5),
    someDeeplyImmutable: DeeplyImmutable(
      someString: 'someString',
      someInt: 3,
      someDouble: 3.3,
      someBool: false,
      someNull: null,
      someInt32x4: Int32x4(0, 1, 2, 3),
      someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
      someFloat64x2: Float64x2(4.4, 5.5),
      someDeeplyImmutable: null,
      somePointer: Pointer.fromAddress(0x8badf00d),
      someClosure: () => 31,
    ),
    somePointer: Pointer.fromAddress(0xdeadbeef),
    someClosure: () => 42,
  ),
  DeeplyImmutable(
    someString: 'someString',
    someNullableString: 'someString',
    someInt: 3,
    someDouble: 3.3,
    someBool: false,
    someNull: null,
    someInt32x4: Int32x4(0, 1, 2, 3),
    someFloat32x4: Float32x4(0.0, 1.1, 2.2, 3.3),
    someFloat64x2: Float64x2(4.4, 5.5),
    someDeeplyImmutable: null,
    somePointer: Pointer.fromAddress(0xdeadbeef),
    someClosure: () {
      list.add("kuka");
      return list;
    },
  ),
];

final copyableClosures = <dynamic>[
  () {
    final a = A<int>();
    final Function<T>(int, T) genericMethod = a.m;
    return genericMethod;
  }(),
  () {
    final a = A<int>();
    final Function(int, double) partialInstantiatedMethod = a.m;
    return partialInstantiatedMethod;
  }(),
  () {
    final a = Object();
    dynamic inner() => a;
    return inner;
  }(),
  () {
    foo(arg) {
      return () => arg;
    }

    return foo(1);
  }(),
];

class SendReceiveTest extends SendReceiveTestBase {
  Future runTests() async {
    await testSharable();
    await testSharable2();
    await testCopyableClosures();
    await testSharableTypedData();
  }

  Future testSharable() async {
    print('testSharable');
    final sharableObjectsCopy = await sendReceive([...sharableObjects]);
    Expect.notIdentical(sharableObjects, sharableObjectsCopy);
    for (int i = 0; i < sharableObjects.length; ++i) {
      Expect.identical(sharableObjects[i], sharableObjectsCopy[i]);
    }
  }

  Future testSharable2() async {
    print('testSharable2');
    final sharableObjectsCopy = await sendReceive([
      notAllocatableInTLAB,
      ...sharableObjects,
    ]);
    Expect.notIdentical(sharableObjects, sharableObjectsCopy);
    Expect.equals(
      notAllocatableInTLAB[0],
      (sharableObjectsCopy[0] as Uint8List)[0],
    );
    for (int i = 0; i < sharableObjects.length; ++i) {
      Expect.identical(sharableObjects[i], sharableObjectsCopy[i + 1]);
    }
  }

  Future testCopyableClosures() async {
    print('testCopyableClosures');
    final copy = await sendReceive([notAllocatableInTLAB, ...copyableClosures]);
    for (int i = 0; i < copyableClosures.length; ++i) {
      Expect.notIdentical(copyableClosures[i], copy[1 + i]);
      Expect.equals(copyableClosures[i].runtimeType, copy[1 + i].runtimeType);
    }

    final copy2 = await sendReceive([
      ...copyableClosures,
      notAllocatableInTLAB,
    ]);
    for (int i = 0; i < copyableClosures.length; ++i) {
      Expect.notIdentical(copyableClosures[i], copy2[i]);
      Expect.equals(copyableClosures[i].runtimeType, copy2[i].runtimeType);
    }
  }

  Future testSharableTypedData() async {
    print('testSharableTypedData');
    const int Dart_TypedData_kUint8 = 2;
    const int count = 10;
    final bytes = malloc.allocate<Uint8>(count);
    msanUnpoison(bytes, count);
    final td = createUnmodifiableTypedData(
      Dart_TypedData_kUint8,
      bytes,
      count,
      nullptr,
      0,
      nullptr,
    );
    Expect.equals(count, td.length);

    {
      final copiedTd = await sendReceive(td);
      Expect.identical(td, copiedTd);
    }

    malloc.free(bytes);
  }
}

main() async {
  await SendReceiveTest().run();
}

@Native<
  Handle Function(
    Int,
    Pointer<Uint8>,
    IntPtr,
    Pointer<Void>,
    IntPtr,
    Pointer<Void>,
  )
>(symbol: "Dart_NewUnmodifiableExternalTypedDataWithFinalizer")
external Uint8List createUnmodifiableTypedData(
  int type,
  Pointer<Uint8> data,
  int length,
  Pointer<Void> peer,
  int externalSize,
  Pointer<Void> callback,
);

final msanUnpoisonPointer =
    DynamicLibrary.process().providesSymbol("__msan_unpoison")
    ? DynamicLibrary.process()
          .lookup<NativeFunction<Void Function(Pointer<Void>, Size)>>(
            "__msan_unpoison",
          )
    : nullptr;

void msanUnpoison(Pointer<Uint8> pointer, int size) {
  if (msanUnpoisonPointer != nullptr) {
    msanUnpoisonPointer.asFunction<void Function(Pointer<Void>, int)>()(
      pointer.cast(),
      size,
    );
  }
}

@pragma('vm:deeply-immutable')
final class DeeplyImmutable {
  final String someString;
  final String? someNullableString;
  final int someInt;
  final double someDouble;
  final bool someBool;
  final Null someNull;
  final Int32x4 someInt32x4;
  final Float32x4 someFloat32x4;
  final Float64x2 someFloat64x2;
  final DeeplyImmutable? someDeeplyImmutable;
  final Pointer somePointer;
  final Function() someClosure;

  DeeplyImmutable({
    required this.someString,
    this.someNullableString,
    required this.someInt,
    required this.someDouble,
    required this.someBool,
    required this.someNull,
    required this.someInt32x4,
    required this.someFloat32x4,
    required this.someFloat64x2,
    this.someDeeplyImmutable,
    required this.somePointer,
    required this.someClosure,
  });
}
