const operations = crypto.subtle
const pbkdf2_iterations = 250000


function stringToArrayBuffer(str) {
    var buf = new ArrayBuffer(str.length);
    var bufView = new Uint8Array(buf);
    for (var i = 0, strLen = str.length; i < strLen; i++) {
        bufView[i] = str.charCodeAt(i);
    }
    return buf;
}


function arrayBufferToString(str) {
    var byteArray = new Uint8Array(str);
    var byteString = '';
    for (var i = 0; i < byteArray.byteLength; i++) {
        byteString += String.fromCodePoint(byteArray[i]);
    }
    return byteString;
}


function formatDate (date) {
    const d = new Date(date)
    const hours = d.getHours().toString().padStart(2, '0')
    const minutes = d.getMinutes().toString().padStart(2, '0')
    const formattedTime = `${hours}:${minutes}`
    return `${d.toLocaleDateString()} ${formattedTime}`
}


const EncryptionPlugin = {
    install(Vue, options) {
        Vue.prototype.deriveKeyFromPassphrase = async (passphrase, salt) => {

            const encoder = new TextEncoder();
            const keyFromPassword = await operations.importKey(
                "raw",
                encoder.encode(passphrase),
                "PBKDF2",
                false,
                ["deriveKey"]
            )

            return await operations.deriveKey(
                {
                    name: "PBKDF2",
                    salt: stringToArrayBuffer(salt),
                    iterations: pbkdf2_iterations,
                    hash: "SHA-256",
                },
                keyFromPassword,                       
                {
                    name: "AES-GCM",
                    length: 256
                },
                true,
                ["wrapKey", "unwrapKey"]
            )
        },

        Vue.prototype.generateKeyPair = async () => {
            return await operations.generateKey(
                {
                    name: "RSA-OAEP",
                    modulusLength: 4096,
                    publicExponent: new Uint8Array([1, 0, 1]),
                    hash: "SHA-256"
                },
                true,
                ["encrypt", "decrypt"]
            );
        },

        Vue.prototype.wrapKey = async (key, wrappingKey, iv) => {

            return btoa(arrayBufferToString(await operations.wrapKey(
                "jwk",
                key,
                wrappingKey, 
                {name: "AES-GCM", iv: stringToArrayBuffer(iv)}
            )))

        },

        Vue.prototype.unwrapKey = async (unwrappingKey, armored_jwk_data, iv, args) => {

            return await operations.unwrapKey(
                "jwk",
                stringToArrayBuffer(atob(armored_jwk_data)),
                unwrappingKey,
                {name: "AES-GCM", iv: stringToArrayBuffer(iv)},
                {
                    name: "RSA-OAEP",
                    hash: "SHA-256",
                },
                true,
                args,
            )
        },

        Vue.prototype.encrypt = async function(data) {

            return btoa(arrayBufferToString(await operations.encrypt(
                { name: "RSA-OAEP" },
                this.$store.state.encryption.keyPair.publicKey,
                stringToArrayBuffer(data),
            )))

        },

        Vue.prototype.decrypt = async function(armored_data) {

            return arrayBufferToString(await operations.decrypt(
                { name: "RSA-OAEP" },
                this.$store.state.encryption.keyPair.privateKey,
                stringToArrayBuffer(atob(armored_data))
            ))

        },

        Vue.prototype.decryptObject = async function(efields, obj) {
            // Decrypt all fields and return a new object

            var newobj = {}

            await Promise.all(Object.keys(obj).map(async field => {
                if (efields.includes(field) && obj[field] != null && obj[field] != "") {
                    // TODO: Catch error
                    newobj[field] = await this.decrypt(obj[field])
                } else {
                    newobj[field] = obj[field]
                }
            }))

            return newobj
        },

        Vue.prototype.encryptObject = async function(efields, obj) {
            // Encrypt all fields and return a new object

            var newobj = {}

            await Promise.all(Object.keys(obj).map(async field => {
                if (efields.includes(field) && obj[field] != null) {
                    // TODO: Catch error
                    newobj[field] = await this.encrypt(obj[field])
                } else {
                    newobj[field] = obj[field]
                }
            }))

            return newobj
        },

        Vue.prototype.object_edit = async function (url_edit, url_create, type, method, obj) {

            let url = null

            if (obj.id == undefined || obj.id == null) {
                url = Urls[url_create]()
            } else {
                url = Urls[url_edit](obj.id)
            }

            const efields = this.$store.getters[`${type}/encryptedFields`]

            try {

                const newobj = await this.encryptObject(efields, obj)
                const response = await this.$http[method](url, newobj)

                if (method != "delete") {
                    return await this.decryptObject(efields, response.data.object)
                }

            } catch (err) {

                let msg = "{{_('Error during edition') | escapejs}}"
                if (method == "delete") {
                    msg = "{{_('Error during deletion') | escapejs}}"
                }

                Swal.fire({title: msg, icon: "error", position:"top-end", showConfirmButton: false, toast: true, timer: 1000})

                throw err

            }
            
        }

    }

}
