import 'package:base32/base32.dart' as base32;
import 'package:base32/encodings.dart';
import 'package:base_codecs/base_codecs.dart' as base_codecs;
import 'package:hashlib_codecs/hashlib_codecs.dart';
import 'package:test/test.dart';

import './utils.dart';

void main() {
  group('Test base32', () {
    // source: https://github.com/daegalus/dart-base32
    test('parameter overrides', () {
      var s = 'foobar';
      var r = 'MZXW6YTBOI======';
      var np = 'MZXW6YTBOI';
      var a = toBase32(s.codeUnits);
      expect(a, equals(r));
      a = toBase32(
        s.codeUnits,
        codec: Base32Codec.standard,
        padding: false,
      );
      expect(a, equals(np));
      a = toBase32(
        s.codeUnits,
        codec: Base32Codec.standard,
        lower: true,
      );
      expect(a, equals(r));
      a = toBase32(
        s.codeUnits,
        codec: Base32Codec.standard,
        padding: false,
        lower: true,
      );
      expect(a, equals(np));
    });

    group('encoding <-> decoding', () {
      test('standard', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b);
          expect(r.toUpperCase(), equals(r), reason: 'length $i');
          var a = fromBase32(r);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('lowercase', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, lower: true);
          expect(r.toLowerCase(), equals(r), reason: 'length $i');
          var a = fromBase32(r);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('standard no padding', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, padding: false);
          expect(r.toUpperCase(), equals(r), reason: 'length $i');
          expect(r, isNot(endsWith('=')), reason: 'length $i');
          var a = fromBase32(r);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('hex uppercase', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.hex);
          var a = fromBase32(r, codec: Base32Codec.hex);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('hex lowercase', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.hexLower);
          var a = fromBase32(r, codec: Base32Codec.hexLower);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('crockford', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.crockford);
          expect(r, isNot(endsWith('=')), reason: 'length $i');
          var a = fromBase32(r, codec: Base32Codec.crockford);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('geohash', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.geohash);
          expect(r.toLowerCase(), equals(r), reason: 'length $i');
          var a = fromBase32(r, codec: Base32Codec.geohash);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('geohash no padding', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.geohash, padding: false);
          expect(r, isNot(endsWith('=')), reason: 'length $i');
          var a = fromBase32(r, codec: Base32Codec.geohash);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('word-safe', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.wordSafe);
          var a = fromBase32(r, codec: Base32Codec.wordSafe);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('word-safe no padding', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.wordSafe, padding: false);
          expect(r, isNot(endsWith('=')), reason: 'length $i');
          var a = fromBase32(r, codec: Base32Codec.wordSafe);
          expect(a, equals(b), reason: 'length $i');
        }
      });
      test('z-base-32', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var r = toBase32(b, codec: Base32Codec.z);
          expect(r, isNot(endsWith('=')), reason: 'length $i');
          var a = fromBase32(r, codec: Base32Codec.z);
          expect(a, equals(b), reason: 'length $i');
        }
      });
    });

    group('encoding', () {
      group('no padding', () {
        test('"" -> ""', () {
          var s = '';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals(''));
        });
        test('f -> MY', () {
          var s = 'f';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals('MY'));
        });
        test('fo -> MZXQ', () {
          var s = 'fo';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals('MZXQ'));
        });
        test('foo -> MZXW6', () {
          var s = 'foo';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals('MZXW6'));
        });
        test('foob -> MZXW6YQ', () {
          var s = 'foob';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals('MZXW6YQ'));
        });
        test('fooba -> MZXW6YTB', () {
          var s = 'fooba';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals('MZXW6YTB'));
        });
        test('foobar -> MZXW6YTBOI', () {
          var s = 'foobar';
          var a = toBase32(s.codeUnits, padding: false);
          expect(a, equals('MZXW6YTBOI'));
        });
        test('[0, 0, 0] => AAAAA', () {
          var inp = [0, 0, 0];
          var out = "AAAAA";
          var act = toBase32(inp, padding: false);
          expect(act, equals(out));
        });
        test('48656c6c6f21deadbeef -> JBSWY3DPEHPK3PXP', () {
          var encoded = fromHex('48656c6c6f21deadbeef');
          var actual = toBase32(encoded, padding: false);
          expect(actual, equals('JBSWY3DPEHPK3PXP'));
        });
        test('48656c6c6f21deadbe -> JBSWY3DPEHPK3PQ', () {
          var encoded = fromHex('48656c6c6f21deadbe');
          var actual = toBase32(encoded, padding: false);
          expect(actual, equals('JBSWY3DPEHPK3PQ'));
        });
        test('foobar --lower--> mzxw6ytboi', () {
          var text = 'foobar';
          var actual = toBase32(text.codeUnits, padding: false, lower: true);
          expect(actual, equals('mzxw6ytboi'));
        });
        test('48656c6c6f21deadbeef --lower--> jbswy3dpehpk3pxp', () {
          var text = fromHex('48656c6c6f21deadbeef');
          var actual = toBase32(text, padding: false, lower: true);
          expect(actual, equals('jbswy3dpehpk3pxp'));
        });
      });
      group('with padding', () {
        test('f -> MY======', () {
          var s = 'f';
          var r = 'MY======';
          expect(toBase32(s.codeUnits), equals(r));
        });
        test('fo -> MZXQ====', () {
          var s = 'fo';
          var r = 'MZXQ====';
          expect(toBase32(s.codeUnits), equals(r));
        });
        test('foo -> MZXW6===', () {
          var s = 'foo';
          var r = 'MZXW6===';
          expect(toBase32(s.codeUnits), equals(r));
        });
        test('foob -> MZXW6YQ=', () {
          var s = 'foob';
          var r = 'MZXW6YQ=';
          expect(toBase32(s.codeUnits), equals(r));
        });
        test('foobar -> MZXW6YTBOI======', () {
          var s = 'foobar';
          var r = 'MZXW6YTBOI======';
          expect(toBase32(s.codeUnits), equals(r));
        });
        test('48656c6c6f21deadbe -> JBSWY3DPEHPK3PQ=', () {
          var s = String.fromCharCodes(fromHex('48656c6c6f21deadbe'));
          var r = 'JBSWY3DPEHPK3PQ=';
          expect(toBase32(s.codeUnits), equals(r));
        });
        test('[0, 0, 0] => AAAAA===', () {
          var inp = [0, 0, 0];
          var out = "AAAAA===";
          expect(toBase32(inp), equals(out));
        });
      });
    });

    group('decoding', () {
      group('no padding', () {
        test('"" -> ""', () {
          var s = '';
          expect(fromBase32(''), equals(s.codeUnits));
        });
        test('MY -> f', () {
          var s = 'f';
          expect(fromBase32('MY'), equals(s.codeUnits));
        });
        test('MZXQ -> fo', () {
          var s = 'fo';
          expect(fromBase32('MZXQ'), equals(s.codeUnits));
        });
        test('MZXW6 -> foo', () {
          var s = 'foo';
          expect(fromBase32('MZXW6'), equals(s.codeUnits));
        });
        test('MZXW6YQ -> foob', () {
          var s = 'foob';
          expect(fromBase32('MZXW6YQ'), equals(s.codeUnits));
        });
        test('MZXW6YTB -> fooba', () {
          var s = 'fooba';
          expect(fromBase32('MZXW6YTB'), equals(s.codeUnits));
        });
        test('MZXW6YTBOI -> foobar', () {
          var s = 'foobar';
          expect(fromBase32('MZXW6YTBOI'), equals(s.codeUnits));
        });
        test('JBSWY3DPEHPK3PXP -> 48656c6c6f21deadbeef', () {
          var decoded = fromBase32('JBSWY3DPEHPK3PXP');
          expect(toHex(decoded), equals('48656c6c6f21deadbeef'));
        });
        test('JBSWY3DPEHPK3PQ -> 48656c6c6f21deadbe', () {
          var decoded = fromBase32('JBSWY3DPEHPK3PQ');
          expect(toHex(decoded), equals('48656c6c6f21deadbe'));
        });
        test('mzxw6ytboi -> foobar', () {
          var s = 'foobar';
          expect(fromBase32('mzxw6ytboi'), equals(s.codeUnits));
        });
        test('jbswy3dpehpk3pxp -> 48656c6c6f21deadbeef', () {
          var decoded = fromBase32('jbswy3dpehpk3pxp');
          expect(toHex(decoded), equals('48656c6c6f21deadbeef'));
        });
        test('jbswy3dpehpk3pq -> 48656c6c6f21deadbe', () {
          var decoded = fromBase32('jbswy3dpehpk3pq');
          expect(toHex(decoded), equals('48656c6c6f21deadbe'));
        });
      });
      group('with padding', () {
        test('MY====== -> f', () {
          var s = 'MY======';
          var r = 'f';
          expect(fromBase32(s), equals(r.codeUnits));
        });
        test('MZXQ==== -> fo', () {
          var s = 'MZXQ====';
          var r = 'fo';
          expect(fromBase32(s), equals(r.codeUnits));
        });
        test('MZXW6=== -> foo', () {
          var s = 'MZXW6===';
          var r = 'foo';
          expect(fromBase32(s), equals(r.codeUnits));
        });
        test('MZXW6YQ= -> foob', () {
          var s = 'MZXW6YQ=';
          var r = 'foob';
          expect(fromBase32(s), equals(r.codeUnits));
        });
        test('MZXW6YTBOI====== -> foobar', () {
          var s = 'MZXW6YTBOI======';
          var r = 'foobar';
          expect(fromBase32(s), equals(r.codeUnits));
        });
        test('JBSWY3DPEHPK3PQ= -> 48656c6c6f21deadbe', () {
          var s = 'JBSWY3DPEHPK3PQ=';
          var r = String.fromCharCodes(fromHex('48656c6c6f21deadbe'));
          expect(fromBase32(s), equals(r.codeUnits));
        });
        test('AAAA => [0, 0, 0]', () {
          var inp = [0, 0, 0];
          var out = "AAAAA===";
          expect(fromBase32(out), equals(inp));
        });
      });
      group(' with invalid chars', () {
        test('"Error!"', () {
          expect(() => fromBase32("Error!"), throwsFormatException);
        });
        test('"-10"', () {
          expect(() => fromBase32("-10"), throwsFormatException);
        });
        test('"s*mething"', () {
          expect(() => fromBase32("s*mething"), throwsFormatException);
        });
      });
      group('with invalid length', () {
        test('"1"', () {
          expect(() => fromBase32("1"), throwsFormatException);
        });
        test('"12"', () {
          expect(() => fromBase32("12"), throwsFormatException);
        });
        test('"123"', () {
          expect(() => fromBase32("123"), throwsFormatException);
        });
        test('"1234"', () {
          expect(() => fromBase32("1234"), throwsFormatException);
        });
        test('"12345"', () {
          expect(() => fromBase32("12345"), throwsFormatException);
        });
        test('"123456"', () {
          expect(() => fromBase32("123456"), throwsFormatException);
        });
        test('"1234567"', () {
          expect(() => fromBase32("1234567"), throwsFormatException);
        });
        test('"123456789"', () {
          expect(() => fromBase32("123456789"), throwsFormatException);
        });
        test('"1234567890"', () {
          expect(() => fromBase32("1234567890"), throwsFormatException);
        });
        test('"12345678901"', () {
          expect(() => fromBase32("12345678901"), throwsFormatException);
        });
        test('"123456789012"', () {
          expect(() => fromBase32("123456789012"), throwsFormatException);
        });
        test('"1234567890123"', () {
          expect(() => fromBase32("1234567890123"), throwsFormatException);
        });
        test('"12345678901234"', () {
          expect(() => fromBase32("12345678901234"), throwsFormatException);
        });
        test('"123456789012345"', () {
          expect(() => fromBase32("123456789012345"), throwsFormatException);
        });
      });
    });

    group('compare against package: base_codecs', () {
      test('encoding', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var hashlib = toBase32(b);
          var other = base_codecs.base32RfcEncode(b);
          expect(hashlib, other, reason: 'length $i');
        }
      });
      test('decoding (uppercase)', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var h = toBase32(b);
          var hashlib = fromBase32(h);
          var other = base_codecs.base32RfcDecode(h);
          expect(hashlib, other, reason: 'length $i');
        }
      });
      test('decoding (lowercase)', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var h = toBase32(b, lower: true);
          var hashlib = fromBase32(h);
          var other = base_codecs.base32RfcDecode(h);
          expect(hashlib, other, reason: 'length $i');
        }
      });
    });

    group('compare against package: base32', () {
      test('encoding (uppercase)', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var hashlib = toBase32(b);
          var other = base32.base32.encodeString(String.fromCharCodes(b));
          expect(hashlib, other, reason: 'length $i');
        }
      });
      test('encoding (lowercase)', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var hashlib = toBase32(b, lower: true);
          var other = base32.base32.encodeString(String.fromCharCodes(b),
              encoding: Encoding.nonStandardRFC4648Lower);
          expect(hashlib, other, reason: 'length $i');
        }
      });
      test('decoding (uppercase)', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var h = toBase32(b);
          var hashlib = fromBase32(h);
          var other = base32.base32.decode(h);
          expect(hashlib, other, reason: 'length $i');
        }
      });
      test('decoding (lowercase)', () {
        for (int i = 0; i < 100; ++i) {
          var b = randomBytes(i);
          var h = toBase32(b, lower: true);
          var hashlib = fromBase32(h);
          var other = base32.base32.decode(
            h,
            encoding: Encoding.nonStandardRFC4648Lower,
          );
          expect(hashlib, other, reason: 'length $i');
        }
      });
    });
  });
}
