/*
    galaxy upload utilities - requires FormData and XMLHttpRequest
*/
import { buildLegacyPayload, fetchDatasets, submitUpload } from "@/utils/upload";

export class UploadQueue {
    constructor(options) {
        this.opts = {
            announce: (d) => {},
            get: (d) => {},
            progress: (d, m) => {},
            success: (d, m) => {},
            warning: (d, m) => {},
            error: (d, m) => {
                console.error(m);
            },
            complete: () => {},
            multiple: true,
            ...options,
        };
        this.queue = new Map(); // items stored by key (referred to as index)
        this.nextIndex = 0;
        this.fileSet = new Set(); // Used for fast duplicate checking
        this.isRunning = false;
        this.isPaused = false;
    }

    // Add new files to upload queue
    add(files) {
        if (files && files.length && !this.isRunning) {
            // files is a FileList which is not an array, convert to iterate.
            Array.from(files).forEach((file) => {
                const fileSetKey = file.name + file.size; // Concat name and size to create a "file signature".
                if (file.mode === "new" || !this.fileSet.has(fileSetKey)) {
                    this.fileSet.add(fileSetKey);
                    const index = String(this.nextIndex++);
                    this.queue.set(index, file);
                    this.opts.announce(index, file);
                }
            });
        }
    }

    // Set options
    configure(options) {
        this.opts = Object.assign(this.opts, options);
        return this.opts;
    }

    // Remove file from queue and file set by index
    remove(index) {
        const file = this.queue.get(index);
        const fileSetKey = file.name + file.size;
        this.queue.delete(index) && this.fileSet.delete(fileSetKey);
    }

    // Remove all entries from queue
    reset() {
        this.queue.clear();
        this.fileSet.clear();
    }

    // Returns queue size
    get size() {
        return this.queue.size;
    }

    // Initiate upload process
    start() {
        if (!this.isRunning) {
            this.isRunning = true;
            this._processUrls();
            this._process();
        }
    }

    // Pause upload process
    stop() {
        this.isPaused = true;
    }

    // Process an upload, recursive
    _process() {
        if (this.size === 0 || this.isPaused) {
            this.isRunning = false;
            this.isPaused = false;
            this.opts.complete();
            return;
        } else {
            this.isRunning = true;
        }
        // Return index to first item in queue (in FIFO order).
        const index = this._processIndex();
        try {
            // Remove item from queue
            this.remove(index);
            // Collect upload request data
            const item = this.opts.get(index);
            if (!item.targetHistoryId) {
                throw new Error(`Missing target history for upload item [${index}] ${item.fileName}`);
            }
            const data = buildLegacyPayload([item], item.targetHistoryId);
            // Initiate upload request
            this._processSubmit(index, data);
        } catch (e) {
            // Parse error message for failed upload item
            this.opts.error(index, String(e));
            // Continue queue
            this._process();
        }
    }

    // Submit remote files as single batch request per target history
    _processUrls() {
        const batchByHistory = {};
        for (const index of this.queue.keys()) {
            const model = this.opts.get(index);
            if (model.status === "queued" && model.fileMode === "url") {
                if (!model.targetHistoryId) {
                    throw new Error(`Missing target history for upload item [${index}] ${model.fileName}`);
                }
                if (!batchByHistory[model.targetHistoryId]) {
                    batchByHistory[model.targetHistoryId] = [];
                }
                batchByHistory[model.targetHistoryId].push({ index, ...model });
                this.remove(index);
            }
        }

        for (const historyId in batchByHistory) {
            const list = batchByHistory[historyId];
            try {
                const data = buildLegacyPayload(list, historyId);
                fetchDatasets(data, {
                    success: (message) => {
                        list.forEach((model) => {
                            this.opts.success(model.index, message);
                        });
                    },
                    error: (message) => {
                        list.forEach((model) => {
                            this.opts.error(model.index, message);
                        });
                    },
                });
            } catch (e) {
                list.forEach((model) => {
                    this.opts.error(model.index, String(e));
                });
            }
        }
    }

    // Get next item to be processed
    _processIndex() {
        return this.queue.keys().next().value;
    }

    // Submit request data
    _processSubmit(index, data) {
        submitUpload({
            data: data,
            success: (message) => {
                this.opts.success(index, message);
                this._process();
            },
            warning: (message) => {
                this.opts.warning(index, message);
            },
            error: (message) => {
                this.opts.error(index, message);
                this._process();
            },
            progress: (percentage) => {
                this.opts.progress(index, percentage);
            },
        });
    }
}
