// Copyright (c) 2018, 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 'package:test/test.dart'
    show expect, isA, predicate, same, test, throwsA, throwsArgumentError;

import 'gen/map_api.pb.dart' as pb;
import 'gen/map_api2.pb.dart' as pb2;
import 'gen/map_field.pb.dart';

void main() {
  test("message doesn't implement Map when turned off", () {
    expect(pb.NonMap(), predicate((x) => x is! Map));
    expect(pb2.NonMap2(), predicate((x) => x is! Map));
  });

  test('message implements Map when turned on', () {
    expect(pb.Rec(), predicate((x) => x is Map));
    expect(pb2.Rec2(), predicate((x) => x is Map));
  });

  test('operator [] returns null for unrecognized keys', () {
    final rec = pb.Rec();
    expect(rec['noSuchField'], null);
    expect(rec[1234], null);
    expect(rec[null], null);
  });

  test('operator [] returns default value when not set', () {
    final rec = pb.Rec();
    expect(rec['num'], 0);
    expect(rec['nums'], []);
    expect(rec['str'], '');
    expect(rec['msg'], predicate((x) => x is pb.NonMap));
  });

  test('operator [] returns new value when set', () {
    final rec = pb.Rec();
    rec.num = 42;
    expect(rec['num'], 42);
    rec.nums.add(123);
    expect(rec['nums'], [123]);
    rec.str = 'hello';
    expect(rec['str'], 'hello');
    final msg = pb.NonMap();
    rec.msg = msg;
    expect(rec['msg'], same(msg));
  });

  test('operator []= throws exception for invalid key', () {
    final rec = pb.Rec();
    expect(
      () {
        rec['unknown'] = 123;
      },
      throwsA(
        isA<ArgumentError>().having(
          (p0) => p0.message,
          'message',
          "field 'unknown' not found in protobuf_unittest.Rec",
        ),
      ),
    );
  });

  test('operator []= throws exception for repeated field', () {
    // Copying the values would be confusing.
    final rec = pb.Rec();
    expect(() {
      rec['nums'] = [1, 2];
    }, throwsArgumentError);
  });

  test('operator []= throws exception for invalid value type', () {
    final rec = pb.Rec();
    expect(() {
      rec['num'] = 'hello';
    }, throwsArgumentError);
    expect(() {
      rec['str'] = 123;
    }, throwsArgumentError);
  });

  test('operator []= sets the field', () {
    final rec = pb.Rec();
    rec['num'] = 123;
    expect(rec.num, 123);
    rec['str'] = 'hello';
    expect(rec.str, 'hello');
  });

  test('keys returns each field name (even when unset)', () {
    final rec = pb.Rec();
    expect(Set.from(rec.keys), {'msg', 'num', 'nums', 'str'});
  });

  test('containsKey returns true for fields that exist (even when unset)', () {
    final rec = pb.Rec();
    expect(rec.containsKey('unknown'), false);
    expect(rec.containsKey('str'), true);
    expect(rec.containsKey('num'), true);
    expect(rec.containsKey('nums'), true);
    expect(rec.containsKey('msg'), true);
  });

  test('length is constant', () {
    final rec = pb.Rec();
    expect(rec.length, 4);
    rec.str = 'hello';
    expect(rec.length, 4);
  });

  test("remove isn't supported", () {
    final rec = pb.Rec();
    rec.str = 'hello';
    expect(
      () {
        rec.remove('str');
      },
      throwsA(
        isA<UnsupportedError>().having(
          (p0) => p0.message,
          'message',
          'remove() not supported by protobuf_unittest.Rec',
        ),
      ),
    );
    expect(rec.str, 'hello');
  });

  test('clear sets each field to its default value (unlike a regular Map)', () {
    // We have little choice here since the clear() method already existed.
    final rec = pb.Rec();
    rec.str = 'hello';
    rec.num = 123;
    rec.nums.add(456);
    rec.clear();
    expect(rec.length, 4);
    expect(rec['str'], '');
    expect(rec['num'], 0);
    expect(rec['nums'], []);
  });

  test('addAll sets each field to a new value', () {
    final rec = pb.Rec();
    rec.addAll({'str': 'hello', 'num': 123});
    expect(rec['str'], 'hello');
    expect(rec['num'], 123);
  });

  test("addAll doesn't work for repeated fields", () {
    // It would be confusing to copy the values.
    final rec = pb.Rec();
    expect(() {
      rec.addAll({
        'nums': [1, 2, 3],
      });
    }, throwsArgumentError);
  });

  test(
    "Map equality check handles missing keys without type errors, bug #1075",
    () {
      final message1 = TestMap();
      final message2 = TestMap();
      expect(message1, message2);

      message1.int32ToInt32Field[1] = 2;
      expect(message1 == message2, false);

      message2.int32ToInt32Field[3] = 4;
      expect(message1 == message2, false);

      message1.int32ToInt32Field[3] = 4;
      message2.int32ToInt32Field[1] = 2;
      expect(message1, message2);
    },
  );
}
