// 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 'dart:convert';

import 'package:protobuf/protobuf.dart';
import 'package:test/test.dart';

import 'gen/map_field.pb.dart';

void main() {
  int int32ToEnumFieldTag =
      TestMap().info_.byName['int32ToEnumField']!.tagNumber;
  int int32ToMessageFieldTag =
      TestMap().info_.byName['int32ToMessageField']!.tagNumber;

  void setValues(TestMap testMap) {
    testMap
      ..int32ToInt32Field[1] = 11
      ..int32ToInt32Field[2] = 22
      ..int32ToInt32Field[3] = 33
      ..int32ToStringField[1] = '11'
      ..int32ToStringField[2] = '22'
      ..int32ToStringField[3] = '33'
      ..int32ToBytesField[1] = utf8.encode('11')
      ..int32ToBytesField[2] = utf8.encode('22')
      ..int32ToBytesField[3] = utf8.encode('33')
      ..int32ToEnumField[1] = TestMap_EnumValue.DEFAULT
      ..int32ToEnumField[2] = TestMap_EnumValue.BAR
      ..int32ToEnumField[3] = TestMap_EnumValue.BAZ
      ..int32ToMessageField[1] = (TestMap_MessageValue()..value = 11)
      ..int32ToMessageField[2] = (TestMap_MessageValue()..value = 22)
      ..int32ToMessageField[3] = (TestMap_MessageValue()..value = 33)
      ..stringToInt32Field['1'] = 11
      ..stringToInt32Field['2'] = 22
      ..stringToInt32Field['3'] = 33;
  }

  void updateValues(TestMap testMap) {
    testMap
      ..int32ToInt32Field[1] = 111
      ..int32ToInt32Field.remove(2)
      ..int32ToInt32Field[4] = 44
      ..int32ToStringField[1] = '111'
      ..int32ToStringField.remove(2)
      ..int32ToStringField[4] = '44'
      ..int32ToBytesField[1] = utf8.encode('111')
      ..int32ToBytesField.remove(2)
      ..int32ToBytesField[4] = utf8.encode('44')
      ..int32ToEnumField[1] = TestMap_EnumValue.BAR
      ..int32ToEnumField.remove(2)
      ..int32ToEnumField[4] = TestMap_EnumValue.ZOP
      ..int32ToMessageField[1] = (TestMap_MessageValue()..value = 111)
      ..int32ToMessageField.remove(2)
      ..int32ToMessageField[4] = (TestMap_MessageValue()..value = 44)
      ..stringToInt32Field['1'] = 111
      ..stringToInt32Field.remove('2')
      ..stringToInt32Field['4'] = 44;
  }

  void expectEmpty(TestMap testMap) {
    expect(testMap.int32ToInt32Field, isEmpty);
    expect(testMap.int32ToStringField, isEmpty);
    expect(testMap.int32ToBytesField, isEmpty);
    expect(testMap.int32ToEnumField, isEmpty);
    expect(testMap.int32ToMessageField, isEmpty);
    expect(testMap.stringToInt32Field, isEmpty);
  }

  void expectMapValuesSet(TestMap testMap) {
    expect(testMap.int32ToInt32Field[1], 11);
    expect(testMap.int32ToInt32Field[2], 22);
    expect(testMap.int32ToInt32Field[3], 33);

    expect(testMap.int32ToStringField[1], '11');
    expect(testMap.int32ToStringField[2], '22');
    expect(testMap.int32ToStringField[3], '33');

    expect(testMap.int32ToBytesField[1], utf8.encode('11'));
    expect(testMap.int32ToBytesField[2], utf8.encode('22'));
    expect(testMap.int32ToBytesField[3], utf8.encode('33'));

    expect(testMap.int32ToEnumField[1], TestMap_EnumValue.DEFAULT);
    expect(testMap.int32ToEnumField[2], TestMap_EnumValue.BAR);
    expect(testMap.int32ToEnumField[3], TestMap_EnumValue.BAZ);

    expect(testMap.int32ToMessageField[1]!.value, 11);
    expect(testMap.int32ToMessageField[2]!.value, 22);
    expect(testMap.int32ToMessageField[3]!.value, 33);

    expect(testMap.stringToInt32Field['1'], 11);
    expect(testMap.stringToInt32Field['2'], 22);
    expect(testMap.stringToInt32Field['3'], 33);
  }

  void expectMapValuesUpdated(TestMap testMap) {
    expect(testMap.int32ToInt32Field.length, 3);
    expect(testMap.int32ToInt32Field[1], 111);
    expect(testMap.int32ToInt32Field[3], 33);
    expect(testMap.int32ToInt32Field[4], 44);

    expect(testMap.int32ToStringField.length, 3);
    expect(testMap.int32ToStringField[1], '111');
    expect(testMap.int32ToStringField[3], '33');
    expect(testMap.int32ToStringField[4], '44');

    expect(testMap.int32ToBytesField.length, 3);
    expect(testMap.int32ToBytesField[1], utf8.encode('111'));
    expect(testMap.int32ToBytesField[3], utf8.encode('33'));
    expect(testMap.int32ToBytesField[4], utf8.encode('44'));

    expect(testMap.int32ToEnumField.length, 3);
    expect(testMap.int32ToEnumField[1], TestMap_EnumValue.BAR);
    expect(testMap.int32ToEnumField[3], TestMap_EnumValue.BAZ);
    expect(testMap.int32ToEnumField[4], TestMap_EnumValue.ZOP);

    expect(testMap.int32ToMessageField.length, 3);
    expect(testMap.int32ToMessageField[1]!.value, 111);
    expect(testMap.int32ToMessageField[3]!.value, 33);
    expect(testMap.int32ToMessageField[4]!.value, 44);

    expect(testMap.stringToInt32Field.length, 3);
    expect(testMap.stringToInt32Field['1'], 111);
    expect(testMap.stringToInt32Field['3'], 33);
    expect(testMap.stringToInt32Field['4'], 44);
  }

  test('set and clear values', () {
    final testMap = TestMap();
    expectEmpty(testMap);

    setValues(testMap);
    expectMapValuesSet(testMap);

    testMap.clear();
    expectEmpty(testMap);
  });

  test('update map values', () {
    final testMap = TestMap();
    setValues(testMap);
    updateValues(testMap);
    expectMapValuesUpdated(testMap);
  });

  test('Serialize and parse map', () {
    var testMap = TestMap();
    setValues(testMap);

    testMap = TestMap.fromBuffer(testMap.writeToBuffer());
    expectMapValuesSet(testMap);

    updateValues(testMap);
    testMap = TestMap.fromBuffer(testMap.writeToBuffer());
    expectMapValuesUpdated(testMap);

    testMap.clear();
    testMap = TestMap.fromBuffer(testMap.writeToBuffer());
    expectEmpty(testMap);
  });

  test('json serialize map', () {
    var testMap = TestMap();
    setValues(testMap);

    testMap = TestMap.fromJson(testMap.writeToJson());
    expectMapValuesSet(testMap);

    updateValues(testMap);
    testMap = TestMap.fromJson(testMap.writeToJson());
    expectMapValuesUpdated(testMap);

    testMap.clear();
    testMap = TestMap.fromJson(testMap.writeToJson());
    expectEmpty(testMap);
  });

  test(
    'PbMap` is equal to another PbMap with equal key/value pairs in any order',
    () {
      final t =
          TestMap()
            ..int32ToStringField[2] = 'test2'
            ..int32ToStringField[1] = 'test';
      final t2 =
          TestMap()
            ..int32ToStringField[1] = 'test'
            ..int32ToStringField[2] = 'test2';
      final t3 = TestMap()..int32ToStringField[1] = 'test';

      final m = t.int32ToStringField;
      final m2 = t2.int32ToStringField;
      final m3 = t3.int32ToStringField;

      expect(t, t2);
      expect(t.hashCode, t2.hashCode);

      expect(m, m2);
      expect(m == m2, isTrue);
      expect(m.hashCode, m2.hashCode);

      expect(m, isNot(m3));
      expect(m == m3, isFalse);
      expect(m.hashCode, isNot(m3.hashCode));
    },
  );

  test('Unitialized map field is equal to initialized', () {
    final testMap1 = TestMap();
    final testMap2 = TestMap();

    // Do a trivial operation to initialize the map field of testMap2.
    testMap2.int32ToStringField.clear();

    expect(testMap1, equals(testMap2));
    expect(testMap2, equals(testMap1));

    expect(testMap1.hashCode, equals(testMap2.hashCode));
  });

  test('merge from other message', () {
    var testMap = TestMap();
    setValues(testMap);

    var other = TestMap();
    other.mergeFromMessage(testMap);
    expectMapValuesSet(other);

    testMap =
        TestMap()
          ..int32ToMessageField[1] = (TestMap_MessageValue()..value = 42)
          ..int32ToMessageField[2] = (TestMap_MessageValue()..value = 44);
    other =
        TestMap()
          ..int32ToMessageField[1] = (TestMap_MessageValue()..secondValue = 43);
    testMap.mergeFromMessage(other);

    expect(testMap.int32ToMessageField[1]!.value, 0);
    expect(testMap.int32ToMessageField[1]!.secondValue, 43);
    expect(testMap.int32ToMessageField[2]!.value, 44);
  });

  test('parse duplicate keys', () {
    final testMap = TestMap()..int32ToStringField[1] = 'foo';
    final testMap2 = TestMap()..int32ToStringField[1] = 'bar';

    final merge = TestMap.fromBuffer([
      ...testMap.writeToBuffer(),
      ...testMap2.writeToBuffer(),
    ]);

    // When parsing from the wire, if there are duplicate map keys the last key
    // seen should be used.
    expect(merge.int32ToStringField.length, 1);
    expect(merge.int32ToStringField[1], 'bar');
  });

  test('Deep merge from other message', () {
    final i1 = Inner()..innerMap['a'] = 'a';
    final i2 = Inner()..innerMap['b'] = 'b';

    final o1 = Outer()..i = i1;
    final o2 = Outer()..i = i2;

    o1.mergeFromMessage(o2);
    expect(o1.i.innerMap.length, 2);
  });

  test('retain explicit default values of sub-messages', () {
    final testMap = TestMap()..int32ToMessageField[1] = TestMap_MessageValue();
    expect(testMap.int32ToMessageField[1]!.secondValue, 42);

    final testMap2 = TestMap()..int32ToMessageField[2] = TestMap_MessageValue();

    testMap.mergeFromBuffer(testMap2.writeToBuffer());
    expect(testMap.int32ToMessageField[2]!.secondValue, 42);
  });

  test('Freeze message with map field', () {
    final testMap = TestMap();
    setValues(testMap);
    testMap.freeze();

    expect(
      () => updateValues(testMap),
      throwsA(const TypeMatcher<UnsupportedError>()),
    );
    expect(
      () => testMap.int32ToMessageField[1]!.value = 42,
      throwsA(const TypeMatcher<UnsupportedError>()),
    );
    expect(
      () => testMap.int32ToStringField.remove(1),
      throwsA(const TypeMatcher<UnsupportedError>()),
    );
    expect(
      () => testMap.int32ToStringField.clear(),
      throwsA(const TypeMatcher<UnsupportedError>()),
    );
  });

  test('Values for different keys are not merged together when decoding', () {
    final testMap = TestMap();
    testMap.int32ToMessageField[1] = (TestMap_MessageValue()..value = 11);
    testMap.int32ToMessageField[2] = (TestMap_MessageValue()..secondValue = 12);

    void testValues(TestMap candidate) {
      final message1 = candidate.int32ToMessageField[1]!;
      final message2 = candidate.int32ToMessageField[2]!;

      expect(message1.hasValue(), true);
      expect(message1.value, 11);
      expect(message1.hasSecondValue(), false);
      expect(message1.secondValue, 42);
      expect(message2.hasValue(), false);
      expect(message2.value, 0);
      expect(message2.hasSecondValue(), true);
      expect(message2.secondValue, 12);
    }

    testValues(TestMap.fromBuffer(testMap.writeToBuffer()));
    testValues(TestMap.fromJson(testMap.writeToJson()));
  });

  test('Calling getField on map fields using reflective API works.', () {
    final testMap = TestMap();
    final mapFieldInfo =
        testMap.info_.fieldInfo.values
            .where(
              (fieldInfo) =>
                  fieldInfo is MapFieldInfo &&
                  fieldInfo.name == 'int32ToBytesField',
            )
            .single;
    final value = testMap.getField(mapFieldInfo.tagNumber);
    expect(value is Map<int, List<int>>, true);
  });

  test('Parses null keys and values', () {
    // Use a desugared version of the message to create missing
    // values in the serialized form.
    final d =
        Desugared()
          ..int32ToStringField.add(
            Desugared_Int32ToString()
              ..clearKey()
              ..value = 'abc',
          )
          ..int32ToStringField.add(
            Desugared_Int32ToString()
              ..key = 42
              ..clearValue(),
          )
          ..int32ToStringField.add(
            Desugared_Int32ToString()
              ..key = 11
              ..value = 'def',
          )
          ..stringToInt32Field.add(
            Desugared_StringToInt32()
              ..clearKey()
              ..value = 11,
          )
          ..stringToInt32Field.add(
            Desugared_StringToInt32()
              ..key = 'abc'
              ..clearValue(),
          )
          ..stringToInt32Field.add(
            Desugared_StringToInt32()
              ..key = 'def'
              ..value = 42,
          );

    final m = TestMap.fromBuffer(d.writeToBuffer());
    expect(m.int32ToStringField[0], 'abc');
    expect(m.int32ToStringField[42], '');
    expect(m.int32ToStringField[11], 'def');
    expect(m.stringToInt32Field['abc'], 0);
    expect(m.stringToInt32Field[''], 11);
    expect(m.stringToInt32Field['def'], 42);
  });

  test('Map field reads should not affect equality or hash of message', () {
    final m1 = TestMap.create();
    final m2 = TestMap.create();
    expect(m1, equals(m2));
    expect(m1.hashCode, equals(m2.hashCode));
    m1.int32ToStringField; // read a map field
    expect(m1, equals(m2));
    expect(m1.hashCode, equals(m2.hashCode));
  });

  test('getField and \$_getMap are in sync', () {
    final msg1 = TestMap();
    expect(msg1.hasField(1), false);
    final map1 = msg1.getField(1) as Map<int, int>;
    expect(msg1.hasField(1), true);
    map1[1] = 2;
    expect(msg1.int32ToInt32Field[1], 2);

    final msg2 = TestMap();
    expect(msg2.hasField(1), false);
    final map2 = msg2.$_getMap(0) as Map<int, int>;
    expect(msg2.hasField(1), true);
    map2[1] = 2;
    expect(msg2.int32ToInt32Field[1], 2);
  });

  test('Parses empty map fields', () {
    // Map fields are encoded as messages (as length-delimited fields). Check
    // that we handle 0 length fields. (#719)
    {
      final messageBytes = <int>[
        ...varint32Bytes(
          (int32ToMessageFieldTag << 3) | 2, // wire type = 2 (length delimited)
        ),
        0, // length = 0
      ];
      final message = TestMap.fromBuffer(messageBytes);
      expect(
        message,
        TestMap()..int32ToMessageField[0] = TestMap_MessageValue(),
      );
    }

    {
      final messageBytes = <int>[
        ...varint32Bytes(
          (int32ToEnumFieldTag << 3) | 2, // wire type = 2 (length delimited)
        ),
        0, // length = 0
      ];
      final message = TestMap.fromBuffer(messageBytes);
      expect(
        message,
        TestMap()..int32ToEnumField[0] = TestMap_EnumValue.DEFAULT,
      );
    }
  });

  test('Parses map field with just key', () {
    // Similar to the case above, but the field just has key (no value)
    {
      final messageBytes = <int>[
        ...varint32Bytes(
          (int32ToMessageFieldTag << 3) | 2, // wire type = 2 (length delimited)
        ),
        2, // length = 2
        (1 << 3) | 0, // tag = 1 (map key), wire type = 0 (varint)
        1, // key = 1
      ];
      final message = TestMap.fromBuffer(messageBytes);
      expect(
        message,
        TestMap()..int32ToMessageField[1] = TestMap_MessageValue(),
      );
    }

    {
      final messageBytes = <int>[
        ...varint32Bytes(
          (int32ToEnumFieldTag << 3) | 2, // wire type = 2 (length delimited)
        ),
        2, // length = 2
        (1 << 3) | 0, // tag = 1 (map key), wire type = 0 (varint)
        1, // key = 1
      ];
      final message = TestMap.fromBuffer(messageBytes);
      expect(
        message,
        TestMap()..int32ToEnumField[1] = TestMap_EnumValue.DEFAULT,
      );
    }
  });

  test('Parses map field with just key', () {
    // Similar to the case above, but the field just has value (no key)
    {
      final messageBytes = <int>[
        ...varint32Bytes(
          (int32ToMessageFieldTag << 3) | 2, // wire type = 2 (length delimited)
        ),
        2, // length = 2
        (2 << 3) | 2, // tag = 2 (map value), wire type = 2 (length delimited)
        0, // length = 0 (empty message)
      ];
      final message = TestMap.fromBuffer(messageBytes);
      expect(
        message,
        TestMap()..int32ToMessageField[0] = TestMap_MessageValue(),
      );
    }

    {
      final messageBytes = <int>[
        ...varint32Bytes(
          (int32ToEnumFieldTag << 3) | 2, // wire type = 2 (length delimited)
        ),
        2, // length = 2
        (2 << 3) | 2, // tag = 2 (map value), wire type = 2 (length delimited)
        1, // enum value = 1
      ];
      final message = TestMap.fromBuffer(messageBytes);
      expect(message, TestMap()..int32ToEnumField[0] = TestMap_EnumValue.BAR);
    }
  });

  test('Read-only message uninitialized map field value is read-only', () {
    final msg = TestMap()..freeze();
    expect(() {
      msg.int32ToInt32Field[0] = 1;
    }, throwsA(const TypeMatcher<UnsupportedError>()));
  });
}

List<int> varint32Bytes(int value) {
  List<int> output = [];
  while (value >= 0x80) {
    output.add(0x80 | (value & 0x7f));
    value >>= 7;
  }
  output.add(value);
  return output;
}
