// Copyright (c) 2013, 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:typed_data';

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

import 'gen/google/protobuf/unittest.pb.dart';
import 'src/test_util.dart';

void main() {
  final testAllTypes = getAllSet();
  final List<int> allFieldsData = testAllTypes.writeToBuffer();
  final emptyMessage = TestEmptyMessage.fromBuffer(allFieldsData);
  final unknownFields = emptyMessage.unknownFields;

  UnknownFieldSetField getField(String name) {
    final tagNumber = testAllTypes.getTagNumber(name)!;
    assert(unknownFields.hasField(tagNumber));
    return unknownFields.getField(tagNumber)!;
  }

  // Asserts that the given field sets are not equal and have different
  // hash codes.
  //
  // N.B.: It is valid for non-equal objects to have the same hash code, so
  // this test is more strict than necessary. However, in the test cases
  // identifies, the hash codes should differ, and as a matter of principle
  // hash collisions should be relatively rare.
  void checkNotEqual(UnknownFieldSet s1, UnknownFieldSet s2) {
    expect(s1 == s2, isFalse);
    expect(s2 == s1, isFalse);

    expect(
      s1.hashCode == s2.hashCode,
      isFalse,
      reason:
          '${s1.toString()} should have a different hash code '
          'from ${s2.toString()}',
    );
  }

  // Asserts that the given field sets are equal and have identical hash codes.
  void checkEqualsIsConsistent(UnknownFieldSet set) {
    // Object should be equal to itself.
    expect(set, set);

    // Object should be equal to a copy of itself.
    final copy = set.clone();
    expect(copy, set);
    expect(set, copy);
  }

  test('testVarint', () {
    final optionalInt32 = getField('optionalInt32');
    expect(optionalInt32.varints[0], expect64(testAllTypes.optionalInt32));
  });

  test('testFixed32', () {
    final optionalFixed32 = getField('optionalFixed32');
    expect(optionalFixed32.fixed32s[0], testAllTypes.optionalFixed32);
  });

  test('testFixed64', () {
    final optionalFixed64 = getField('optionalFixed64');
    expect(optionalFixed64.fixed64s[0], testAllTypes.optionalFixed64);
  });

  test('testLengthDelimited', () {
    final optionalBytes = getField('optionalBytes');
    expect(optionalBytes.lengthDelimited[0], testAllTypes.optionalBytes);
  });

  test('testGroup', () {
    final tagNumberA = TestAllTypes_OptionalGroup().getTagNumber('a')!;

    final optionalGroupField = getField('optionalgroup');
    expect(optionalGroupField.groups.length, 1);
    final group = optionalGroupField.groups[0];
    expect(group.hasField(tagNumberA), isTrue);
    expect(
      group.getField(tagNumberA)!.varints[0],
      expect64(testAllTypes.optionalgroup.a),
    );
  });

  test('testSerialize', () {
    expect(emptyMessage.writeToBuffer(), allFieldsData);
  });

  test('testCopyFrom', () {
    final message = emptyMessage.deepCopy();
    expect(message.toString(), emptyMessage.toString());
    expect(emptyMessage.toString().isEmpty, isFalse);
  });

  test('testMergeFrom', () {
    // Source.
    final sourceFieldSet =
        UnknownFieldSet()
          ..addField(2, UnknownFieldSetField()..addVarint(make64(2)))
          ..addField(3, UnknownFieldSetField()..addVarint(make64(3)));

    final source = TestEmptyMessage()..mergeUnknownFields(sourceFieldSet);

    // Destination.
    final destinationFieldSet =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addVarint(make64(1)))
          ..addField(3, UnknownFieldSetField()..addVarint(make64(4)));

    final destination =
        TestEmptyMessage()
          ..mergeUnknownFields(destinationFieldSet)
          ..mergeFromMessage(source);

    expect(
      destination.toString(),
      '1: 1\n'
      '2: 2\n'
      '3: 4\n'
      '3: 3\n',
    );
  });

  test('testClear', () {
    final fsb = unknownFields.clone()..clear();
    expect(fsb.asMap(), isEmpty);
  });

  test('testEmpty', () {
    expect(UnknownFieldSet().asMap(), isEmpty);
  });

  test('testClearMessage', () {
    final message = emptyMessage.deepCopy();
    message.clear();
    expect(message.writeToBuffer(), isEmpty);
  });

  test('testParseKnownAndUnknown', () {
    // Test mixing known and unknown fields when parsing.
    final fields =
        unknownFields.clone()
          ..addField(123456, UnknownFieldSetField()..addVarint(make64(654321)));

    final writer = CodedBufferWriter();
    fields.writeToCodedBufferWriter(writer);

    final destination = TestAllTypes.fromBuffer(writer.toBuffer());

    assertAllFieldsSet(destination);
    expect(destination.unknownFields.asMap().length, 1);

    final field = destination.unknownFields.getField(123456)!;
    expect(field.varints.length, 1);
    expect(field.varints[0], expect64(654321));
  });

  // Constructs a protocol buffer which contains fields with all the same
  // numbers as allFieldsData except that each field is some other wire
  // type.
  List<int> getBizarroData() {
    final bizarroFields = UnknownFieldSet();

    final varintField = UnknownFieldSetField()..addVarint(make64(1));

    final fixed32Field = UnknownFieldSetField()..addFixed32(1);

    unknownFields.asMap().forEach((int tag, UnknownFieldSetField value) {
      if (value.varints.isEmpty) {
        // Original field is not a varint, so use a varint.
        bizarroFields.addField(tag, varintField);
      } else {
        // Original field *is* a varint, so use something else.
        bizarroFields.addField(tag, fixed32Field);
      }
    });
    final writer = CodedBufferWriter();
    bizarroFields.writeToCodedBufferWriter(writer);
    return writer.toBuffer();
  }

  test('testWrongTypeTreatedAsUnknown', () {
    // Test that fields of the wrong wire type are treated like unknown fields
    // when parsing.
    final bizarroData = getBizarroData();
    final allTypesMessage = TestAllTypes.fromBuffer(bizarroData);
    final emptyMessage_ = TestEmptyMessage.fromBuffer(bizarroData);
    // All fields should have been interpreted as unknown, so the debug strings
    // should be the same.
    expect(allTypesMessage.toString(), emptyMessage_.toString());
  });

  test('testUnknownExtensions', () {
    // Make sure fields are properly parsed to the UnknownFieldSet even when
    // they are declared as extension numbers.
    final message = TestEmptyMessageWithExtensions.fromBuffer(allFieldsData);

    expect(message.unknownFields.asMap().length, unknownFields.asMap().length);
    expect(message.writeToBuffer(), allFieldsData);
  });

  test('testWrongExtensionTypeTreatedAsUnknown', () {
    // Test that fields of the wrong wire type are treated like unknown fields
    // when parsing extensions.

    final bizarroData = getBizarroData();
    final allExtensionsMessage = TestAllExtensions.fromBuffer(bizarroData);
    final emptyMessage_ = TestEmptyMessage.fromBuffer(bizarroData);

    // All fields should have been interpreted as unknown, so the debug strings
    // should be the same.
    expect(allExtensionsMessage.toString(), emptyMessage_.toString());
  });

  test('testParseUnknownEnumValue', () {
    final singularFieldNum = testAllTypes.getTagNumber('optionalNestedEnum')!;
    final repeatedFieldNum = testAllTypes.getTagNumber('repeatedNestedEnum')!;
    expect(singularFieldNum, isNotNull);
    expect(repeatedFieldNum, isNotNull);

    final fieldSet =
        UnknownFieldSet()
          ..addField(
            singularFieldNum,
            UnknownFieldSetField()
              ..addVarint(make64(TestAllTypes_NestedEnum.BAR.value))
              ..addVarint(make64(5)),
          )
          ..addField(
            repeatedFieldNum,
            UnknownFieldSetField()
              ..addVarint(make64(TestAllTypes_NestedEnum.FOO.value))
              ..addVarint(make64(4))
              ..addVarint(make64(TestAllTypes_NestedEnum.BAZ.value))
              ..addVarint(make64(6)),
          );

    final writer = CodedBufferWriter();
    fieldSet.writeToCodedBufferWriter(writer);
    {
      final message = TestAllTypes.fromBuffer(writer.toBuffer());
      expect(message.optionalNestedEnum, TestAllTypes_NestedEnum.BAR);
      expect(message.repeatedNestedEnum, [
        TestAllTypes_NestedEnum.FOO,
        TestAllTypes_NestedEnum.BAZ,
      ]);
      final singularVarints =
          message.unknownFields.getField(singularFieldNum)!.varints;
      expect(singularVarints.length, 1);
      expect(singularVarints[0], expect64(5));
      final repeatedVarints =
          message.unknownFields.getField(repeatedFieldNum)!.varints;
      expect(repeatedVarints.length, 2);
      expect(repeatedVarints[0], expect64(4));
      expect(repeatedVarints[1], expect64(6));
    }
    {
      final message = TestAllExtensions.fromBuffer(
        writer.toBuffer(),
        getExtensionRegistry(),
      );
      expect(
        message.getExtension(Unittest.optionalNestedEnumExtension),
        TestAllTypes_NestedEnum.BAR,
      );

      expect(message.getExtension(Unittest.repeatedNestedEnumExtension), [
        TestAllTypes_NestedEnum.FOO,
        TestAllTypes_NestedEnum.BAZ,
      ]);
      final singularVarints =
          message.unknownFields.getField(singularFieldNum)!.varints;
      expect(singularVarints.length, 1);
      expect(singularVarints[0], expect64(5));
      final repeatedVarints =
          message.unknownFields.getField(repeatedFieldNum)!.varints;
      expect(repeatedVarints.length, 2);
      expect(repeatedVarints[0], expect64(4));
      expect(repeatedVarints[1], expect64(6));
    }
  });

  test('testLargeVarint', () {
    final unknownFieldSet =
        UnknownFieldSet()..addField(
          1,
          UnknownFieldSetField()..addVarint(make64(0x7FFFFFFF, 0xFFFFFFFF)),
        );
    final writer = CodedBufferWriter();
    unknownFieldSet.writeToCodedBufferWriter(writer);

    final parsed =
        UnknownFieldSet()
          ..mergeFromCodedBufferReader(CodedBufferReader(writer.toBuffer()));
    final field = parsed.getField(1)!;
    expect(field.varints.length, 1);
    expect(field.varints[0], expect64(0x7FFFFFFF, 0xFFFFFFFFF));
  });

  test('testEquals', () {
    final a =
        UnknownFieldSet()..addField(1, UnknownFieldSetField()..addFixed32(1));

    final b =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addFixed64(make64(1)));

    final c =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addVarint(make64(1)));

    final d =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addLengthDelimited([]));

    final e =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addGroup(unknownFields));

    checkEqualsIsConsistent(a);
    checkEqualsIsConsistent(b);
    checkEqualsIsConsistent(c);
    checkEqualsIsConsistent(d);
    checkEqualsIsConsistent(e);

    checkNotEqual(a, b);
    checkNotEqual(a, c);
    checkNotEqual(a, d);
    checkNotEqual(a, e);
    checkNotEqual(b, c);
    checkNotEqual(b, d);
    checkNotEqual(b, e);
    checkNotEqual(c, d);
    checkNotEqual(c, e);
    checkNotEqual(d, e);

    final f1 =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addLengthDelimited([1, 2]));
    final f2 =
        UnknownFieldSet()
          ..addField(1, UnknownFieldSetField()..addLengthDelimited([2, 1]));

    checkEqualsIsConsistent(f1);
    checkEqualsIsConsistent(f2);

    checkNotEqual(f1, f2);
  });

  test(
    'consistent hashcode for messages with no unknown fields set and an empty unknown field set',
    () {
      final m = TestAllExtensions();
      // Force an unknown field set.
      final m2 = TestAllExtensions()..unknownFields;
      expect(m.hashCode, m2.hashCode);
    },
  );

  test('Copy length delimited fields', () {
    // Length-delimited fields should be copied before adding to the unknown
    // field set to avoid aliasing.
    final originalBytes = [1, 2, 3, 4, 5, 6];
    final bytes = Uint8List.fromList([
      10, // tag = 1, type = length delimited
      originalBytes.length,
      ...originalBytes,
    ]);

    final parsed =
        UnknownFieldSet()..mergeFromCodedBufferReader(CodedBufferReader(bytes));

    expect(parsed.getField(1)?.lengthDelimited, [originalBytes]);

    // Modify the message. Input buffer should not be updated.
    final newBytes = [9, 8, 7, 6, 5, 4];
    parsed.getField(1)!.lengthDelimited[0].setRange(0, 6, newBytes);
    expect(bytes.sublist(2), originalBytes);

    // Modify the input buffer. Message should not be updated.
    bytes.setRange(2, 8, [10, 11, 12, 13, 14, 15]);
    expect(parsed.getField(1)!.lengthDelimited[0], newBytes);
  });

  test('WriteTextFormat', () {
    final nestedFieldSet =
        UnknownFieldSet()
          ..addField(2, UnknownFieldSetField()..addVarint(make64(2)))
          ..addField(3, UnknownFieldSetField()..addVarint(make64(-3)))
          ..addField(4, UnknownFieldSetField()..addFixed32(1))
          ..addField(5, UnknownFieldSetField()..addFixed64(make64(1)))
          ..addField(6, UnknownFieldSetField()..addVarint(make64(1)))
          ..addField(7, UnknownFieldSetField()..addLengthDelimited([1, 2, 3]))
          ..addField(8, UnknownFieldSetField()..addFixed64(make64(-5)))
          ..addField(9, UnknownFieldSetField()..addFixed32(-6));
    final fieldSet =
        UnknownFieldSet()
          ..addField(8, UnknownFieldSetField()..addVarint(make64(3)))
          ..addField(9, UnknownFieldSetField()..addVarint(make64(-4)))
          ..addField(10, UnknownFieldSetField()..addFixed32(2))
          ..addField(11, UnknownFieldSetField()..addFixed64(make64(5)))
          ..addField(12, UnknownFieldSetField()..addVarint(make64(6)))
          ..addField(13, UnknownFieldSetField()..addLengthDelimited([7, 8, 9]))
          ..addField(14, UnknownFieldSetField()..addGroup(nestedFieldSet));

    final out = StringBuffer();
    fieldSet.writeTextFormat(out, 0);
    expect(out.toString(), expectedTextFormat);
  });
}

const expectedTextFormat = '''8: 3
9: 18446744073709551612
10: 0x00000002
11: 0x0000000000000005
12: 6
13: "\\a\\b\\t"
14 {
  2: 2
  3: 18446744073709551613
  4: 0x00000001
  5: 0x0000000000000001
  6: 1
  7: "\\001\\002\\003"
  8: 0xfffffffffffffffb
  9: 0xfffffffa
}
''';
