function teaEncrypt(plaintext, password) {
    var v = new Array(2), k = new Array(4), s = "", i;
    var len;

    for (var i=0; i<4; i++) k[i] = Str4ToLong(password.slice(i*4,(i+1)*4));
    var btxt = new TextEncoder().encode(plaintext);
    if ((btxt.byteLength+1)&0x7) len = (((btxt.byteLength+1)>>>3)+1)*8
    else len = (btxt.byteLength+1)
    var teadata = new ArrayBuffer(len);
    for (i=0; i<len; i+=8) {  // encode plaintext into s in 64-bit (8 char) blocks
        v[0] = byteArryToLong(btxt, i)
        v[1] = byteArryToLong(btxt, i+4)
        code(v, k);
        LongToBuffer(i, v, teadata);
    }
    return arrayBufferToBase64(teadata);
}

function teaDecrypt(ciphertext, password) {
    var v = new Array(2), k = new Array(4), s = "", i;

    for (var i=0; i<4; i++) k[i] = Str4ToLong(password.slice(i*4,(i+1)*4));

    var teadata = base64ToarrayBuffer(ciphertext);
    var stxt = new Uint8Array(teadata.byteLength);
    for (i=0; i<teadata.length; i+=8) {
        BufferToLong(i,v,teadata)
        decodevvv(v, k);
        LongToBuffer(i, v, stxt);
    }
 
    for (i=0; i<stxt.byteLength; i++) {
        if (stxt[i]==0) {
            break;
        }    
    }

    const decoder = new TextDecoder("utf-8");
    return decoder.decode(stxt.slice(0, i));
}


function code(v, k) {
    var v0 = v[0], v1 = v[1];
    var delta = 0x479E3919, i, sum = 0;

    for (i=0; i<32; i++) {
		sum += delta;
        v0 += ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>>5) + k[1]);
        v0 &= 0xffffffff
        v1 += ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>>5) + k[3]);   
        v1 &= 0xffffffff     
    }
    v[0] = v0; v[1] = v1;
}


function decodevvv(v, k) {
    var v0 = v[0], v1 = v[1];
    var delta = 0x479E3919, sum = delta*32, i;

    for (i=0; i<32; i++) {
        v1 -= ((v0<<4) + k[2]) ^ (v0 + sum) ^ ((v0>>>5) + k[3]);
        v1 &= 0xffffffff
        v0 -= ((v1<<4) + k[0]) ^ (v1 + sum) ^ ((v1>>>5) + k[1]);
        v0 &= 0xffffffff
        sum -= delta;
    }
    v[0]=v0; v[1]=v1;    
}


// supporting functions

function Str4ToLong(s) {  // convert 4 chars of s to a numeric long
    var v = 0;
    for (var i=0; i<4; i++) v |= s.charCodeAt(i) << i*8;
    return isNaN(v) ? 0 : v;
}

function LongToStr4(v) {  // convert a numeric long to 4 char string
    var s = String.fromCharCode(v & 0xFF, v>>8 & 0xFF, v>>16 & 0xFF, v>>24 & 0xFF);
    return s;
}

function LongLognToStr8(v0, v1) {
    var s = '';
    var n = v0;
    for (var i=0; i<4; i++) {
        if ((n&0xFF)==0) {
            return s;
        }
        else {
            s += String.fromCharCode(n & 0xFF)
            n >>= 8
        }
    }
    n = v1
    for (var i=0; i<4; i++) {
        if ((n&0xFF)==0) {
            return s;
        }
        else {
            s += String.fromCharCode(n & 0xFF)
            n >>= 8
        }
    }    
    return s
}

function LongToHex4(v) {  // convert a numeric long to 4 char hex
    var s = (v & 0xFF).toString(16);
    s += (v>>8 & 0xFF).toString(16);
    s += (v>>16 & 0xFF).toString(16);
    s += (v>>24 & 0xFF).toString(16);
    return s;
}

function byteArryToLong(txt, i) {
    var v = 0;
    
    if (i<txt.byteLength) {
        v = txt[i];
        if ((i+1)<txt.byteLength) v |= (txt[i+1] << 8);
        if ((i+2)<txt.byteLength) v |= (txt[i+2] << 16);
        if ((i+3)<txt.byteLength) v |= (txt[i+3] << 24);
    }
    return v;
}


function LongToBuffer(n, v, teadata) {
    teadata[n] = v[0] & 0xFF;
    teadata[n+1] = v[0]>>8 & 0xFF;
    teadata[n+2] = v[0]>>16 & 0xFF;
    teadata[n+3] = v[0]>>24 & 0xFF;    
    teadata[n+4] = v[1] & 0xFF;
    teadata[n+5] = v[1]>>8 & 0xFF;
    teadata[n+6] = v[1]>>16 & 0xFF;
    teadata[n+7] = v[1]>>24 & 0xFF;   
}


function BufferToLong(n, v, teadata) {
    v[0] = 0;
    for (var i=0; i<4; i++) {
        v[0] |= teadata[n+i] << i*8;
    }
    v[0] =  isNaN(v[0]) ? 0 : v[0];
    v[1] = 0;
    for (var i=0; i<4; i++) {
        v[1] |= teadata[n+4+i] << i*8;
    }
    v[1] =  isNaN(v[1]) ? 0 : v[1];    
}


function arrayBufferToBase64(bytes) {
    const byteArray = new Uint8Array(bytes.byteLength);
    for(var i=0; i<byteArray.length; i++) {
        byteArray[i] = bytes[i]
    }
    const binString = String.fromCodePoint(...byteArray);
    return btoa(binString);
}

function base64ToarrayBuffer(base64) {
    var binaryString = atob(base64);
    var bytes = new Uint8Array(binaryString.length);
    for (var i = 0; i < binaryString.length; i++) {
        bytes[i] = binaryString.charCodeAt(i);
    }
    return bytes;
}

function escCtrlCh(str) {  // escape control chars which might cause problems with encrypted texts
    return str.replace(/[\0\t\n\v\f\r\xa0'"!]/g, function(c) { return '!' + c.charCodeAt(0) + '!'; });
}

function unescCtrlCh(str) {  // unescape potentially problematic nulls and control characters
    return str.replace(/!\d\d?\d?!/g, function(c) { return String.fromCharCode(c.slice(1,-1)); });
}
