<template>
    <BasePage :title="$t('firmwareupgrade.FirmwareUpgrade')">
        <div class="position-relative" v-if="loading">
            <div class="position-absolute top-50 start-50 translate-middle">
                <div class="spinner-border" role="status">
                    <span class="visually-hidden">{{ $t('firmwareupgrade.Loading') }}</span>
                </div>
            </div>
        </div>

        <CardElement
            :text="$t('firmwareupgrade.OtaError')"
            textVariant="text-bg-danger"
            center-content
            v-if="!loading && !uploading && OTAError != ''"
        >
            <p class="h1 mb-2">
                <BIconExclamationCircleFill />
            </p>
            {{ OTAError }}
            <div class="mt-3 d-flex gap-3 justify-content-center">
                <button class="btn btn-light" @click="clear">
                    <BIconArrowLeft /> {{ $t('firmwareupgrade.Back') }}
                </button>
                <button class="btn btn-primary" @click="retryOTA">
                    <BIconArrowRepeat /> {{ $t('firmwareupgrade.Retry') }}
                </button>
            </div>
        </CardElement>

        <CardElement
            :text="$t('firmwareupgrade.OtaStatus')"
            textVariant="text-bg-success"
            center-content
            v-else-if="!loading && !uploading && OTASuccess"
        >
            <BIconCheckCircle class="fs-1" />&nbsp;{{ $t('firmwareupgrade.OtaSuccess') }}
        </CardElement>

        <CardElement
            :text="$t('firmwareupgrade.FirmwareUpload')"
            textVariant="text-bg-primary"
            center-content
            v-else-if="!loading && !uploading"
        >
            <div class="form-group">
                <input class="form-control" type="file" ref="file" accept=".bin,.bin.gz" @change="uploadOTA" />
            </div>
        </CardElement>

        <CardElement
            :text="$t('firmwareupgrade.UploadProgress')"
            textVariant="text-bg-primary"
            center-content
            v-else-if="!loading && uploading"
        >
            <div class="progress">
                <div
                    class="progress-bar"
                    role="progressbar"
                    :style="{ width: progress + '%' }"
                    v-bind:aria-valuenow="progress"
                    aria-valuemin="0"
                    aria-valuemax="100"
                >
                    {{ progress }}%
                </div>
            </div>
        </CardElement>
    </BasePage>
</template>

<script lang="ts">
import BasePage from '@/components/BasePage.vue';
import CardElement from '@/components/CardElement.vue';
import { authHeader, isLoggedIn } from '@/utils/authentication';
import { BIconArrowLeft, BIconArrowRepeat, BIconCheckCircle, BIconExclamationCircleFill } from 'bootstrap-icons-vue';
import SparkMD5 from 'spark-md5';
import { defineComponent } from 'vue';
import { waitRestart } from '@/utils/waitRestart';

export default defineComponent({
    components: {
        BasePage,
        CardElement,
        BIconArrowLeft,
        BIconArrowRepeat,
        BIconCheckCircle,
        BIconExclamationCircleFill,
    },
    data() {
        return {
            loading: true,
            uploading: false,
            progress: 0,
            OTAError: '',
            OTASuccess: false,
            type: 'firmware',
            file: {} as Blob,
        };
    },
    methods: {
        fileMD5(file: Blob) {
            return new Promise((resolve, reject) => {
                const blobSlice = File.prototype.slice;
                const chunkSize = 2097152; // Read in chunks of 2MB
                const chunks = Math.ceil(file.size / chunkSize);
                const spark = new SparkMD5.ArrayBuffer();
                const fileReader = new FileReader();
                let currentChunk = 0;
                fileReader.onload = () => {
                    spark.append(fileReader.result as ArrayBuffer); // Append array buffer
                    currentChunk += 1;
                    if (currentChunk < chunks) {
                        loadNext();
                    } else {
                        const md5 = spark.end();
                        resolve(md5);
                    }
                };
                fileReader.onerror = (e) => {
                    reject(e);
                };
                const loadNext = () => {
                    const start = currentChunk * chunkSize;
                    const end = start + chunkSize >= file.size ? file.size : start + chunkSize;
                    fileReader.readAsArrayBuffer(blobSlice.call(file, start, end));
                };
                loadNext();
            });
        },
        uploadOTA(event: Event | null) {
            this.uploading = true;
            const formData = new FormData();
            if (event !== null) {
                const target = event.target as HTMLInputElement;
                if (target.files !== null && target.files[0]) {
                    this.file = target.files[0];
                }
            }
            const request = new XMLHttpRequest();
            request.addEventListener('load', () => {
                // request.response will hold the response from the server
                if (request.status === 200) {
                    this.OTASuccess = true;
                    waitRestart(this.$router);
                } else if (request.status !== 500) {
                    this.OTAError = `[HTTP ERROR] ${request.statusText}`;
                } else {
                    this.OTAError = request.responseText;
                }
                this.uploading = false;
                this.progress = 0;
            });
            // Upload progress
            request.upload.addEventListener('progress', (e) => {
                this.progress = Math.trunc((e.loaded / e.total) * 100);
            });
            request.withCredentials = true;
            this.fileMD5(this.file)
                .then((md5) => {
                    formData.append('MD5', md5 as string);
                    formData.append('firmware', this.file, 'firmware');
                    request.open('post', '/api/firmware/update');
                    authHeader().forEach((value, key) => {
                        request.setRequestHeader(key, value);
                    });
                    request.send(formData);
                })
                .catch(() => {
                    this.OTAError = 'Unknown error while upload, check the console for details.';
                    this.uploading = false;
                    this.progress = 0;
                });
        },
        retryOTA() {
            this.OTAError = '';
            this.OTASuccess = false;
            this.uploadOTA(null);
        },
        clear() {
            this.OTAError = '';
            this.OTASuccess = false;
        },
    },
    mounted() {
        if (!isLoggedIn()) {
            this.$router.push({
                path: '/login',
                query: { returnUrl: this.$router.currentRoute.value.fullPath },
            });
        }
        this.loading = false;
    },
});
</script>
