require("lockbox").insecure();

local Bit = require("lockbox.util.bit");

local AND = Bit.band;
local OR  = Bit.bor;
local XOR = Bit.bxor;
local LSHIFT = Bit.lshift;
local RSHIFT = Bit.rshift;


--NOTE: TEA is endian-dependent!
--The spec does not seem to specify which to use.
--It looks like most implementations use big-endian
local bytes2word = function(b0, b1, b2, b3)
    local i = b0; i = LSHIFT(i, 8);
    i = OR(i, b1); i = LSHIFT(i, 8);
    i = OR(i, b2); i = LSHIFT(i, 8);
    i = OR(i, b3);
    return i;
end

local word2bytes = function(word)
    local b0, b1, b2, b3;
    b3 = AND(word, 0xFF); word = RSHIFT(word, 8);
    b2 = AND(word, 0xFF); word = RSHIFT(word, 8);
    b1 = AND(word, 0xFF); word = RSHIFT(word, 8);
    b0 = AND(word, 0xFF);
    return b0, b1, b2, b3;
end

local TEA = {};

TEA.blockSize = 8;

TEA.encrypt = function(key, data)
    local y = bytes2word(data[1], data[2], data[3], data[4]);
    local z = bytes2word(data[5], data[6], data[7], data[8]);
    local delta = 0x9e3779b9;
    local sum = 0;

    local k0 = bytes2word(key[ 1], key[ 2], key[ 3], key[ 4]);
    local k1 = bytes2word(key[ 5], key[ 6], key[ 7], key[ 8]);
    local k2 = bytes2word(key[ 9], key[10], key[11], key[12]);
    local k3 = bytes2word(key[13], key[14], key[15], key[16]);

    for _ = 1, 32 do
        local temp;

        sum = AND(sum + delta, 0xFFFFFFFF);

        temp = z + sum;
        temp = XOR(temp, LSHIFT(z, 4) + k0);
        temp = XOR(temp, RSHIFT(z, 5) + k1);
        y = AND(y + temp, 0xFFFFFFFF);

        temp = y + sum;
        temp = XOR(temp, LSHIFT(y, 4) + k2);
        temp = XOR(temp, RSHIFT(y, 5) + k3);
        z = AND( z + temp, 0xFFFFFFFF);
    end

    local out = {};

    out[1], out[2], out[3], out[4] = word2bytes(y);
    out[5], out[6], out[7], out[8] = word2bytes(z);

    return out;
end

TEA.decrypt = function(key, data)
    local y = bytes2word(data[1], data[2], data[3], data[4]);
    local z = bytes2word(data[5], data[6], data[7], data[8]);

    local delta = 0x9e3779b9;
    local sum = 0xc6ef3720; --AND(delta*32,0xFFFFFFFF);

    local k0 = bytes2word(key[ 1], key[ 2], key[ 3], key[ 4]);
    local k1 = bytes2word(key[ 5], key[ 6], key[ 7], key[ 8]);
    local k2 = bytes2word(key[ 9], key[10], key[11], key[12]);
    local k3 = bytes2word(key[13], key[14], key[15], key[16]);

    for _ = 1, 32 do
        local temp;

        temp = y + sum;
        temp = XOR(temp, LSHIFT(y, 4) + k2);
        temp = XOR(temp, RSHIFT(y, 5) + k3);
        z = AND(z + 0x100000000 - temp, 0xFFFFFFFF);

        temp = z + sum;
        temp = XOR(temp, LSHIFT(z, 4) + k0);
        temp = XOR(temp, RSHIFT(z, 5) + k1);
        y = AND(y + 0x100000000 - temp, 0xFFFFFFFF);

        sum = AND(sum + 0x100000000 - delta, 0xFFFFFFFF);
    end

    local out = {};

    out[1], out[2], out[3], out[4] = word2bytes(y);
    out[5], out[6], out[7], out[8] = word2bytes(z);

    return out;
end

return TEA;
