// 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 '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 = new 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 = new 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(new 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", () {
      List<List<int>> results;
      var sink;
      setUp(() {
        results = [];
        var controller = new 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([[]]));

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

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

        sink.add("");
        expect(
            results,
            equals([
              [],
              [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(new StreamController(sync: true));
          expect(() => sink.add(char), throwsFormatException);
        });
      }
    });

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