const channel = document.getElementById("channel");
const chatin = document.getElementById("chatin");
const chatoutput = document.getElementById("Chatoutput");
const status1 = document.getElementById("status");
let button1 = document.getElementById("button1");
const button2 = document.getElementById("button2");
const button3 = document.getElementById("button3");
const button4 = document.getElementById("button4");
let ready = false;
let channelClosedBySelf = false;
let serverid = "";
let uiid = "";
let uipin = "";
let filename = "";
let filesize = 0;
let download_remaining_size = 0;
let filetype = "";
let key = false;
let downloadId = "";
let decrypted_chunks = [];
let socket = new WebSocket("wss://" + location.host + "/ws");
//LAST CALL:
initSocket();
//NOTHING CAN BE CALLED HERE!

function verifySocket() {
    if (!ready) {
        socket = new WebSocket("wss://" + location.host + "/ws");
        initSocket()
    }
}

async function initSocket() {
    socket.onopen = function (evt) {
        console.log("Websocket open");
        ready = true;
        if (document.URL.includes("?id=")) {
            channel.value = document.URL.split("=")[1];
            button2Handler()
        }
    }
    socket.onclose = function (evt) {
        console.log("Websocket closed");
        ready = false;
    }
    socket.onerror = function (evt) {
        console.log("Websocket error: " + evt.data);
        ready = false;
    }
    socket.addEventListener("message", async (event) => {
        //console.log(event.data.split('\n')[0]);
        if (typeof event.data === "object") { //TODO: more checks (event.data instanceof Blob)
            //console.log("received blob");
            try {
                const transformed_data = await event.data.arrayBuffer();
                await decrypt_and_queue(transformed_data);
            } catch (error) {
                console.log("Error decrypt and download:", error);
            }
            return;
        }
        const response = event.data.split(" ");
        if (response[0] === "Created") {
            uiid = response[1] + "-" + randomId(4);
            serverid = response[1]
            channel.value = uiid;
            button2.innerText = "Close channel";
            button1.disabled = true;
            button1.innerText = "Create channel";
            button1.title = "You have to close the channel before creating a new one, or join the channel on another device";
            document.getElementById("link").innerHTML = "";
            document.getElementById("link").innerHTML = `<a class="download" onclick="copy()">COPY LINK</a>`;
        } else if (response[0] === "AskPin") {
            uipin = prompt("give pin");
            if (uipin.length != 9) {
                alert("Wrong pin length");
            } else {
                let pin = uipin.substring(0, uipin.lastIndexOf("-"));
                let askpinmessage = "Pin " + serverid + " " + pin;
                console.log(askpinmessage);
                socket.send(askpinmessage);
            }
        } else if (response[0] === "AskJoin") {
            const clientpin = randomPin(4);
            uipin = `${response[2]}-${clientpin}`;
            askjoininfo = `Enter Pin for other client: ip= ${response[1]}, pin= ${uipin}`;
            console.log(askjoininfo);
            status1.value = askjoininfo;
        } else if (response[0] === "Joined") {
            id = channel.value;
            button2.innerText = "Close channel";
            button2.disabled = false;
            button1.disabled = false;
            buttonchatout.disabled = false;
            status1.value = `${event.data}`;
            button1.innerText = "Open File";
            button1.title = "You have to join the channel on another device before opening a file";
            document.getElementById("filetd").innerHTML = "";
            document.getElementById("filetd").innerHTML = `<input type="file" class="yesbutton" id="button1" onChange="button1Handler()"/>`;
            button1 = document.getElementById("button1");
            await createKey();
        } else if (response[0] === "Metadata") {
            //status1.value = `${event.data}`;
            const splittedmetadata = event.data.split(":");
            let encrypted_file_name = splittedmetadata[2];
            encrypted_file_name = base64ToUint8Array(encrypted_file_name);
            const rawfilename = await decrypt(encrypted_file_name);
            filename = new TextDecoder().decode(rawfilename);
            filesize = Number(splittedmetadata[0].split(" ")[1]);
            download_remaining_size = filesize;
            filetype = splittedmetadata[1];
            downloadId = splittedmetadata[3];
            console.log(filename);
            status1.value = `size=${filesize} type=${filetype} name=${filename}`;
            document.getElementById("filetd").innerHTML = "";
            document.getElementById("filetd").innerHTML = `<a class="download" id="button1" onclick="accept();">DOWNLOAD FILE!</a>`;
            button1 = document.getElementById("button1");
        } else if (response[0] === "Message") {
            const splittedmessagedata = event.data.split(" ");
            let encrypted_message = splittedmessagedata[1];
            encrypted_message = base64ToUint8Array(encrypted_message);
            const rawmessage = await decrypt(encrypted_message);
            recv_message = new TextDecoder().decode(rawmessage);
            console.log(recv_message);
            chatoutput.append("\n", recv_message);
        } else if (response[0] === "Sent") {
            document.getElementById("filetd").innerHTML = "";
            document.getElementById("filetd").innerHTML = `<input type="file" class="yesbutton" id="button1" onChange="button1Handler()"/>`;
            button1 = document.getElementById("button1");
        } else if (response[0] === "Accepted") {
            status1.value = `${event.data}`;
            await sendTheFile();
            console.log("streaming file ...");
        } else if (response[0] === "NoChannel") {
            alert("Enter a valid channel id!");
            channel.value = "";
            document.getElementById("filetd").innerHTML = ""
            document.getElementById("filetd").innerHTML = `<button class="yesbutton" id="button1" onclick="button1Handler()">Create channel</button>`;
            button2.innerText = "Join channel";
            button2.disabled = false
            channel.value = "";
            status1.value = "Disconnected";
            document.getElementById("download").innerHTML = "";
            channelClosedBySelf = true;
            button1 = document.getElementById("button1");
        } else if (response[0] === "Closed") {
            if (channelClosedBySelf) {
                document.getElementById("download").innerHTML = "";
                button1 = document.getElementById("button1");
                button1.innerText = "Create channel";
            } else {
                status1.value = `Peer closed connection`;
                document.getElementById("download").innerHTML = "";
                document.getElementById("filetd").innerHTML = ""
                document.getElementById("filetd").innerHTML = `<button class="yesbutton" id="button1" onclick="button1Handler()">Create channel</button>`;
                button1 = document.getElementById("button1");
                button1.disabled = true;
                button1.innerText = "Create channel";
                button1.title = "You have to close the channel before creating a new one, or join the channel on another device";
                buttonchatout.disabled = true;
            }
            channelClosedBySelf = false;
        } else if (response[0] === "Timeout") {
            alert("Connection expired");
        }
    });
}

async function startLoad() {
    channel.value = "";
    status1.value = "Disconnected";
}

async function buttonchatoutHandler() {
    verifySocket()
    if (!ready) {
        alert("socket not ready, refresh page!");
        return;
    }
    const unencrypted_message = chatin.value;
    console.log(unencrypted_message);
    chatin.value = "";
    const encrypted_message_arr = await encrypt(unencrypted_message);
    const encrypted_message = uint8ArrayTobase64(encrypted_message_arr);
    socket.send(`chatMessage ${serverid} ${encrypted_message}`);
}

async function button1Handler() {
    verifySocket()
    if (!ready) {
        alert("socket not ready, refresh page!");
        return;
    }
    if (button1.innerText === "Create channel") {
        button1.disabled = true;
        const idtype = document.getElementById('idtype');
        if (idtype.value == "words") {
            socket.send("createShortID")
        } else {
            socket.send("createID");
        }
    } else {
        const fileInput = document.getElementById('button1');
        const file = fileInput.files[0];
        button1.disabled = true;
        console.log(file.name);
        let encrypted_file_name = await encrypt(file.name);
        encrypted_file_name = uint8ArrayTobase64(encrypted_file_name);
        socket.send(`fileMetadata ${serverid} ${file.size} ${file.type} ${encrypted_file_name}`);
        console.log(`fileMetadata ${serverid} ${file.size} ${file.type} ${encrypted_file_name}`);
    }
}

function button2Handler() {
    verifySocket()
    if (!ready) {
        alert("socket not ready, refresh page!");
        return;
    }
    if (button2.innerText === "Close channel") {
        buttonchatout.disabled = true;
        socket.send("closeId " + serverid);
        console.log("close channel");
        channel.value = "";
        document.getElementById("filetd").innerHTML = ""
        document.getElementById("filetd").innerHTML = `<button class="yesbutton" id="button1" onclick="button1Handler()">Create channel</button>`;
        button2.innerText = "Join channel";
        channel.value = "";
        status1.value = "Disconnected";
        document.getElementById("download").innerHTML = "";
        channelClosedBySelf = true;
        button1 = document.getElementById("button1");
    } else if (button2.innerText === "Join channel") {
        if (channel.value === "") {
            alert("Enter a valid channel id!");
            return;
        }
        button1.disabled = true;
        button2.disabled = true;
        uiid = channel.value
        serverid = uiid.substring(0, uiid.lastIndexOf("-"));
        socket.send("joinID " + serverid);
    } else {
        console.log("Error, you broke my app!")
    }
}

function base64ToUint8Array(base64) {
    const binaryString = atob(base64);
    const byteArray = new Uint8Array(binaryString.length);
    for (let i = 0; i != binaryString.length; i++) {
        byteArray[i] = binaryString.charCodeAt(i);
    }
    return byteArray;
}

function uint8ArrayTobase64(arr) {
    return btoa(String.fromCharCode(...arr));
}

async function generateKey(keyString) {
    // Derive a key from the string
    const encoder = new TextEncoder();
    let keyData = encoder.encode(keyString);
    if (keyData.length != 32) {
        const hash = await window.crypto.subtle.digest('SHA-256', keyData);
        keyData = new Uint8Array(hash);
    }
    return await window.crypto.subtle.importKey(
        "raw",                  // Raw key material
        keyData,                // Key bytes
        { name: "AES-GCM" },    // Algorithm
        false,                  // Non-extractable
        ["encrypt", "decrypt"]  // Allowed operations
    );
}

async function createKey() {
    console.log("creating key");
    key = await generateKey(`${uiid}${uipin}${downloadId}`);
    console.log("key is ", key);
}

async function encrypt(block_data) {
    if (typeof block_data === "string") {
        block_data = new TextEncoder().encode(block_data);
        block_data = new Uint8Array(block_data);
    }
    const iv = crypto.getRandomValues(new Uint8Array(12));
    const encrypted_data = await window.crypto.subtle.encrypt(
        {
            name: "AES-GCM",
            iv: iv,
        },
        key,
        block_data,
    );
    const combined_data = new Uint8Array(iv.length + encrypted_data.byteLength);
    combined_data.set(new Uint8Array(iv), 0);
    combined_data.set(new Uint8Array(encrypted_data), iv.length);
    return combined_data;
}

async function decrypt(crypt_data) {
    const iv = crypt_data.slice(0, 12);
    const encrypted_data = crypt_data.slice(12);
    const decrypted_data = await window.crypto.subtle.decrypt(
        {
            name: "AES-GCM",
            iv: iv,
        },
        key,
        encrypted_data
    );
    return decrypted_data;
}

async function decrypt_and_queue(crypt_data) {
    const data = await decrypt(crypt_data);
    decrypted_chunks.push(data);
    //console.log("block size= ", data.byteLength);
    download_remaining_size -= data.byteLength;
    if (download_remaining_size == 0) {
        await save_file_to_disk();
    }
}

async function sendTheFile() {
    const fileInput = document.getElementById('button1');
    const file = fileInput.files[0];
    const reader = file.stream().getReader();
    let blocksSent = 0;
    reader.read().then(async function sendBlock({ done, value }) {
        if (done) {
            button1.disabled = false;
            return;
        }
        const header = `FileBlockWs ${serverid} ${blocksSent} \n`;
        const h = strToBytes(header);
        const encryptetvalue = await encrypt(value);
        const packet = new Uint8Array(h.length + encryptetvalue.byteLength);
        packet.set(new Uint8Array(h), 0);
        packet.set(encryptetvalue, h.length);
        socket.send(packet)
        blocksSent++;
        return reader.read().then(sendBlock);
    })
}

async function save_file_to_disk() {
    try {
        const decrypted_blob = new Blob(decrypted_chunks, { type: filetype });
        const url = URL.createObjectURL(decrypted_blob);
        const a = document.createElement("a");
        a.href = url;
        a.download = filename;
        a.click();
        URL.revokeObjectURL(url);
        alert("file decrypted and downloaded succesfully");
    } catch (error) {
        console.error("Error downloading file: ", error);
        alert("download failed:", error)
    }
}

function strToBytes(s) {
    let string = [];
    for (let i = 0; i != s.length; i++) {
        string.push(s.charCodeAt(i));
    }
    return string;
}

function randomId(length) {
    const chars = "abcdefghijklmnopqrstuvwxyz";
    let result = "";
    if (window.crypto && window.crypto.getRandomValues) {
        // Use crypto.getRandomValues
        const array = new Uint8Array(length);
        window.crypto.getRandomValues(array);
        result = Array.from(array, (value) => chars[value % chars.length]).join("");
    } else {
        // Fallback method (Math.random)
        console.log("Alternative random generator")
        let j = 0;
        while (j != length) {
            j++;
            const randomIndex = Math.floor(Math.random() * chars.length);
            result += chars[randomIndex];
        }
    }
    return result;
}

function randomPin(length) {
    const chars = "0123456789";
    let result = "";
    if (window.crypto && window.crypto.getRandomValues) {
        // Use crypto.getRandomValues
        const array = new Uint8Array(length);
        window.crypto.getRandomValues(array);
        result = Array.from(array, (value) => chars[value % chars.length]).join("");
    } else {
        // Fallback method (Math.random)
        console.log("Alternative random generator")
        let j = 0;
        while (j != length) {
            j++;
            const randomIndex = Math.floor(Math.random() * chars.length);
            result += chars[randomIndex];
        }
    }
    return result;
}

function accept() {
    document.getElementById("filetd").innerHTML = "";
    document.getElementById("filetd").innerHTML = `<a class="downloadD" id="button1">DOWNLOAD FILE!</a>`;
    socket.send("FileAccepted " + serverid);
}

function copy() {
    const text = `https://${location.host}/filestreamer?id=${channel.value}`;

    navigator.clipboard.writeText(text).then(function () {
        console.log('Async: Copying to clipboard was successful!');
    }, function (err) {
        console.error('Async: Could not copy text: ', err);
    });
}