var solady = require("./solady.js");

function test(msg, fn) {
    msg = msg.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, "").replace(/([^\.])$/, "$1.");
    try {
        fn();
        console.log("\x1b[32m[PASS]\x1b[0m", msg);
    } catch (e) {
        process.exitCode = 1;
        console.error("\x1b[31m[FAIL]\x1b[0m", msg);
        console.error(e.stack);
    }
}

function assert(cond, msg) {
    if (!cond) throw new Error(msg);
}

function assertEq(a, b) {
    assert(a === b, "Assertion failed!\n    Expected: " + b + "\n    Actual: " + a);
}

function expectRevert(fn) {
    var hasRevert = false;
    try { fn() } catch (e) { hasRevert = true }
    assert(hasRevert, "Revert expected.\n" + fn);
}

function randomData() {
    var n = ~~(Math.random() * 2000);
    var s = Math.random() < 0.5 ? "" : "0x";
    var g = Math.random() < 0.5 ? 0.45 : (Math.random() ? 0.99 : 0.999);
    var h = g + 0.5 * (1.0 - g);
    for (var i = 0; i < n; ++i) {
        var r = Math.random();
        if (r < g) {
            s += "00";
        } else if (r < h) {
            s += "ff";
        } else {
            var b = ((Math.random() * 0x100) & 0xff).toString(16);
            s += b.length === 1 ? "0" + b : b;
        }
    }
    return Math.random() < 0.5 ? s.toUpperCase() : s.toLowerCase();
}

function padRandomWhitespace(data) {
    var before = "";
    var after = "";
    while (Math.random() < 0.5) before += Math.random() ? "\t" : " ";
    while (Math.random() < 0.5) after += Math.random() ? "\t" : " ";
    return before + data + after;
}

function testCompressDecompress(compress, decompress) {
    var totalDataLength = 0;
    var totalCompressedLength = 0;
    for (var t = 0; t < 1000; ++t) {
        var data = randomData();
        var compressed = compress(padRandomWhitespace(data));
        var decompressed = decompress(padRandomWhitespace(compressed));
        totalDataLength += data.length;
        totalCompressedLength += compressed.length;
        assertEq(compressed.slice(0, 2), "0x");
        assertEq(decompressed.slice(0, 2), "0x");
        assertEq(decompressed.replace(/^0x/, ""), data.toLowerCase().replace(/^0x/, ""));
    }
    assert(totalCompressedLength < totalDataLength, "Compress not working as intended.");

    assertEq(compress(""), "0x");
    assertEq(compress("0x"), "0x");
    assertEq(decompress(""), "0x");
    assertEq(decompress("0x"), "0x");

    function checkRevertOnInvalidInputs(fn) {
        expectRevert(function () { fn("hehe") });
        expectRevert(function () { fn("0xa") });
        expectRevert(function () { fn("0xas") });
        expectRevert(function () { fn(123) });
        expectRevert(function () { fn(false) });
        expectRevert(function () { fn(null) });
        expectRevert(function () { fn(undefined) });
        expectRevert(function () { fn([]) });
        expectRevert(function () { fn({}) });
    }

    checkRevertOnInvalidInputs(compress);
    checkRevertOnInvalidInputs(decompress);
}

test("LibZip: FastLZ compress / decompress.", function() {
    testCompressDecompress(solady.LibZip.flzCompress, solady.LibZip.flzDecompress);
});

test("LibZip: Calldata compress / decompress.", function() {
    testCompressDecompress(solady.LibZip.cdCompress, solady.LibZip.cdDecompress);
});

test("LibZip: Calldata compress", function() {
    var data = "0xac9650d80000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000160000000000000000000000000000000000000000000000000000000000000022000000000000000000000000000000000000000000000000000000000000002a000000000000000000000000000000000000000000000000000000000000000a40c49ccbe000000000000000000000000000000000000000000000000000000000005b70e00000000000000000000000000000000000000000000000000000dfc79825feb0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000645c48a7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000084fc6f7865000000000000000000000000000000000000000000000000000000000005b70e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000ffffffffffffffffffffffffffffffff00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004449404b7c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000064df2ab5bb0000000000000000000000007f5c764cbc14f9669b88837ca1490cca17c3160700000000000000000000000000000000000000000000000000000000000000000000000000000000000000001f1cdf1a632eaaab40d1c263edf49faf749010a100000000000000000000000000000000000000000000000000000000";
    var expected = "0x5369af27001e20001e04001e80001d0160001d0220001d02a0001ea40c49ccbe001c05b70e00190dfc79825feb005b645c48a7003a84fc6f7865001c05b70e002f008f000f008f003a4449404b7c002b1f1cdf1a632eaaab40d1c263edf49faf749010a1003a64df2ab5bb000b7f5c764cbc14f9669b88837ca1490cca17c31607002b1f1cdf1a632eaaab40d1c263edf49faf749010a1001b";
    assertEq(solady.LibZip.cdCompress(data), expected);
});

test("LibZip: Calldata decompress on invalid input", function() {
    var data = "0xffffffff00ff";
    var expected = "0x0000ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000";
    assertEq(solady.LibZip.cdDecompress(data), expected);
});

test("ERC1967Factory: ABI and address", function() {
    function hashFnv32a(s) {
        var h = 0x811c9dc5;
        for (var i = 0; i < s.length; i++) {
            h ^= s.charCodeAt(i);
            h += (h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24);
        }
        return h >>> 0;
    }
    assertEq(hashFnv32a(JSON.stringify(solady.ERC1967Factory.abi)), 1277805820);
    assertEq(solady.ERC1967Factory.address, "0x0000000000006396FF2a80c067f99B3d2Ab4Df24");
});