// Copyright 2014 The Flutter Authors. 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:math';

import 'package:flutter/src/foundation/collections.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  test('listEquals', () {
    final List<int> listA = <int>[1, 2, 3];
    final List<int> listB = <int>[1, 2, 3];
    final List<int> listC = <int>[1, 2];
    final List<int> listD = <int>[3, 2, 1];

    expect(listEquals<void>(null, null), isTrue);
    expect(listEquals(listA, null), isFalse);
    expect(listEquals(null, listB), isFalse);
    expect(listEquals(listA, listA), isTrue);
    expect(listEquals(listA, listB), isTrue);
    expect(listEquals(listA, listC), isFalse);
    expect(listEquals(listA, listD), isFalse);
  });
  test('setEquals', () {
    final Set<int> setA = <int>{1, 2, 3};
    final Set<int> setB = <int>{1, 2, 3};
    final Set<int> setC = <int>{1, 2};
    final Set<int> setD = <int>{3, 2, 1};

    expect(setEquals<void>(null, null), isTrue);
    expect(setEquals(setA, null), isFalse);
    expect(setEquals(null, setB), isFalse);
    expect(setEquals(setA, setA), isTrue);
    expect(setEquals(setA, setB), isTrue);
    expect(setEquals(setA, setC), isFalse);
    expect(setEquals(setA, setD), isTrue);
  });
  test('mapEquals', () {
    final Map<int, int> mapA = <int, int>{1:1, 2:2, 3:3};
    final Map<int, int> mapB = <int, int>{1:1, 2:2, 3:3};
    final Map<int, int> mapC = <int, int>{1:1, 2:2};
    final Map<int, int> mapD = <int, int>{3:3, 2:2, 1:1};
    final Map<int, int> mapE = <int, int>{3:1, 2:2, 1:3};

    expect(mapEquals<void, void>(null, null), isTrue);
    expect(mapEquals(mapA, null), isFalse);
    expect(mapEquals(null, mapB), isFalse);
    expect(mapEquals(mapA, mapA), isTrue);
    expect(mapEquals(mapA, mapB), isTrue);
    expect(mapEquals(mapA, mapC), isFalse);
    expect(mapEquals(mapA, mapD), isTrue);
    expect(mapEquals(mapA, mapE), isFalse);
  });
  test('binarySearch', () {
    final List<int> items = <int>[1, 2, 3];

    expect(binarySearch(items, 1), 0);
    expect(binarySearch(items, 2), 1);
    expect(binarySearch(items, 3), 2);
    expect(binarySearch(items, 12), -1);
  });
  test('MergeSortRandom', () {
    final Random random = Random();
    for (int i = 0; i < 250; i += 1) {
      // Expect some equal elements.
      final List<int> list = List<int>.generate(i, (int j) => random.nextInt(i));
      mergeSort(list);
      for (int j = 1; j < i; j++) {
        expect(list[j - 1], lessThanOrEqualTo(list[j]));
      }
    }
  });
  test('MergeSortPreservesOrder', () {
    final Random random = Random();
    // Small case where only insertion call is called,
    // larger case where the internal moving insertion sort is used
    // larger cases with multiple splittings, numbers just around a power of 2.
    for (final int size in <int>[8, 50, 511, 512, 513]) {
      // Class OC compares using id.
      // With size elements with id's in the range 0..size/4, a number of
      // collisions are guaranteed. These should be sorted so that the 'order'
      // part of the objects are still in order.
      final List<OrderedComparable> list = List<OrderedComparable>.generate(
        size,
        (int i) => OrderedComparable(random.nextInt(size >> 2), i),
      );
      mergeSort(list);
      OrderedComparable prev = list[0];
      for (int i = 1; i < size; i++) {
        final OrderedComparable next = list[i];
        expect(prev.id, lessThanOrEqualTo(next.id));
        if (next.id == prev.id) {
          expect(prev.order, lessThanOrEqualTo(next.order));
        }
        prev = next;
      }
      // Reverse compare on part of list.
      final List<OrderedComparable> copy = list.toList();
      final int min = size >> 2;
      final int max = size - min;
      mergeSort<OrderedComparable>(
        list,
        start: min,
        end: max,
        compare: (OrderedComparable a, OrderedComparable b) => b.compareTo(a),
      );
      prev = list[min];
      for (int i = min + 1; i < max; i++) {
        final OrderedComparable next = list[i];
        expect(prev.id, greaterThanOrEqualTo(next.id));
        if (next.id == prev.id) {
          expect(prev.order, lessThanOrEqualTo(next.order));
        }
        prev = next;
      }
      // Equals on OC objects is identity, so this means the parts before min,
      // and the parts after max, didn't change at all.
      expect(list.sublist(0, min), equals(copy.sublist(0, min)));
      expect(list.sublist(max), equals(copy.sublist(max)));
    }
  });
  test('MergeSortSpecialCases', () {
    for (final int size in <int>[511, 512, 513]) {
      // All equal.
      final List<OrderedComparable> list = List<OrderedComparable>.generate(size, (int i) => OrderedComparable(0, i));
      mergeSort(list);
      for (int i = 0; i < size; i++) {
        expect(list[i].order, equals(i));
      }
      // All but one equal, first.
      list[0] = OrderedComparable(1, 0);
      for (int i = 1; i < size; i++) {
        list[i] = OrderedComparable(0, i);
      }
      mergeSort(list);
      for (int i = 0; i < size - 1; i++) {
        expect(list[i].order, equals(i + 1));
      }
      expect(list[size - 1].order, equals(0));

      // All but one equal, last.
      for (int i = 0; i < size - 1; i++) {
        list[i] = OrderedComparable(0, i);
      }
      list[size - 1] = OrderedComparable(-1, size - 1);
      mergeSort(list);
      expect(list[0].order, equals(size - 1));
      for (int i = 1; i < size; i++) {
        expect(list[i].order, equals(i - 1));
      }

      // Reversed.
      for (int i = 0; i < size; i++) {
        list[i] = OrderedComparable(size - 1 - i, i);
      }
      mergeSort(list);
      for (int i = 0; i < size; i++) {
        expect(list[i].id, equals(i));
        expect(list[i].order, equals(size - 1 - i));
      }
    }
  });
}

class OrderedComparable implements Comparable<OrderedComparable> {
  OrderedComparable(this.id, this.order);
  final int id;
  final int order;
  @override
  int compareTo(OrderedComparable other) => id - other.id;
  @override
  String toString() => 'OverrideComparable[$id,$order]';
}