// Copyright (c) 2015, 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:async';
import 'dart:convert';

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

void main() {
  group('encoder', () {
    test('converts byte arrays to hex', () {
      expect(hex.encode([0x1a, 0xb2, 0x3c, 0xd4]), equals('1ab23cd4'));
      expect(hex.encode([0x00, 0x01, 0xfe, 0xff]), equals('0001feff'));
    });

    group('with chunked conversion', () {
      test('converts byte arrays to hex', () {
        var results = <String>[];
        var controller = StreamController<String>(sync: true);
        controller.stream.listen(results.add);
        var sink = hex.encoder.startChunkedConversion(controller.sink);

        sink.add([0x1a, 0xb2, 0x3c, 0xd4]);
        expect(results, equals(['1ab23cd4']));

        sink.add([0x00, 0x01, 0xfe, 0xff]);
        expect(results, equals(['1ab23cd4', '0001feff']));
      });

      test('handles empty and single-byte lists', () {
        var results = <String>[];
        var controller = StreamController<String>(sync: true);
        controller.stream.listen(results.add);
        var sink = hex.encoder.startChunkedConversion(controller.sink);

        sink.add([]);
        expect(results, equals(['']));

        sink.add([0x00]);
        expect(results, equals(['', '00']));

        sink.add([]);
        expect(results, equals(['', '00', '']));
      });
    });

    test('rejects non-bytes', () {
      expect(() => hex.encode([0x100]), throwsFormatException);

      var sink =
          hex.encoder.startChunkedConversion(StreamController(sync: true));
      expect(() => sink.add([0x100]), throwsFormatException);
    });
  });

  group('decoder', () {
    test('converts hex to byte arrays', () {
      expect(hex.decode('1ab23cd4'), equals([0x1a, 0xb2, 0x3c, 0xd4]));
      expect(hex.decode('0001feff'), equals([0x00, 0x01, 0xfe, 0xff]));
    });

    test('supports uppercase letters', () {
      expect(
          hex.decode('0123456789ABCDEFabcdef'),
          equals([
            0x01,
            0x23,
            0x45,
            0x67,
            0x89,
            0xab,
            0xcd,
            0xef,
            0xab,
            0xcd,
            0xef
          ]));
    });

    group('with chunked conversion', () {
      late List<List<int>> results;
      late StringConversionSink sink;
      setUp(() {
        results = [];
        var controller = StreamController<List<int>>(sync: true);
        controller.stream.listen(results.add);
        sink = hex.decoder.startChunkedConversion(controller.sink);
      });

      test('converts hex to byte arrays', () {
        sink.add('1ab23cd4');
        expect(
            results,
            equals([
              [0x1a, 0xb2, 0x3c, 0xd4]
            ]));

        sink.add('0001feff');
        expect(
            results,
            equals([
              [0x1a, 0xb2, 0x3c, 0xd4],
              [0x00, 0x01, 0xfe, 0xff]
            ]));
      });

      test('supports trailing digits split across chunks', () {
        sink.add('1ab23');
        expect(
            results,
            equals([
              [0x1a, 0xb2]
            ]));

        sink.add('cd');
        expect(
            results,
            equals([
              [0x1a, 0xb2],
              [0x3c]
            ]));

        sink.add('40001');
        expect(
            results,
            equals([
              [0x1a, 0xb2],
              [0x3c],
              [0xd4, 0x00, 0x01]
            ]));

        sink.add('feff');
        expect(
            results,
            equals([
              [0x1a, 0xb2],
              [0x3c],
              [0xd4, 0x00, 0x01],
              [0xfe, 0xff]
            ]));
      });

      test('supports empty strings', () {
        sink.add('');
        expect(results, isEmpty);

        sink.add('0');
        expect(results, equals([<Never>[]]));

        sink.add('');
        expect(results, equals([<Never>[]]));

        sink.add('0');
        expect(
            results,
            equals([
              <Never>[],
              [0x00]
            ]));

        sink.add('');
        expect(
            results,
            equals([
              <Never>[],
              [0x00]
            ]));
      });

      test('rejects odd length detected in close()', () {
        sink.add('1ab23');
        expect(
            results,
            equals([
              [0x1a, 0xb2]
            ]));
        expect(() => sink.close(), throwsFormatException);
      });

      test('rejects odd length detected in addSlice()', () {
        sink.addSlice('1ab23cd', 0, 5, false);
        expect(
            results,
            equals([
              [0x1a, 0xb2]
            ]));

        expect(
            () => sink.addSlice('1ab23cd', 5, 7, true), throwsFormatException);
      });
    });

    group('rejects non-hex character', () {
      for (var char in [
        'g',
        'G',
        '/',
        ':',
        '@',
        '`',
        '\x00',
        '\u0141',
        '\u{10041}'
      ]) {
        test('"$char"', () {
          expect(() => hex.decode('a$char'), throwsFormatException);
          expect(() => hex.decode('${char}a'), throwsFormatException);

          var sink =
              hex.decoder.startChunkedConversion(StreamController(sync: true));
          expect(() => sink.add(char), throwsFormatException);
        });
      }
    });

    test('rejects odd length detected in convert()', () {
      expect(() => hex.decode('1ab23cd'), throwsFormatException);
    });
  });
}
