{% extends 'atlas/base.html' %}

{% load static%}

{% block content %}
    <h2>Upload dicoms</h2>
    User: {{user}}<br/>

    <div role="alert" class="alert alert-warning">
        Please note this form will not annonymise files, ensure that you have done so prior to using it. If files that do not appear to be annonymised are selected you will not be able to upload them.
    </div>

    <div>
        <p>This form allows you to quickly upload dicom files to the server. Select the folder containing the dicom files below. If you wish to upload files directly from Insight you may prefer to use the <a href='{% url "atlas:help" %}'>Uploader tool.</a></p>
        <details class="help-text">
            <summary><i class="bi bi-info-circle"></i> Help</summary>
            <p>Only valid dicom files will be uploaded. </p>
            <p>It is possible to upload multiple files by selecting the parent (containing) folder. Folders within will then be searched recursively for any valid dicom files.</p>
            <p>Files to be uploaded will be displayed below. </p>
            <p>DICOM tags will be checked to see if valid accession or hospital numbers are present but no annonymisation will be performed.</p>
            <p>An attempt to detect duplicates will be made by calculating a hash of the pixel data in the DICOM files. If this check is not run and the files are uploaded they will be rejected.</p>
            <p>If enabled a basic OCR will also be performed to check for burned in details on images, this is resource intensive and can be disabled if you know the content you are uploading does not feature any patient details.</p>
        </details>
        <details>
            <summary>Settings</summary>
            <input type="checkbox" id="check-annonymisation-ocr"><label for="check-annonymisation-ocr">Check for annonymisation (OCR)</label>
            <input type="checkbox" id="show-thumbnails"><label for="Show thumbnails">Show thumbnails</label>
            <input type="checkbox" id="show-viewer"><label for="Show Viewer">Show viewer</label>
            <input type="checkbox" id="check-duplicates" checked><label for="check-duplicates">Check for duplicates before upload</label>
        </details>
    </div>

    <form method="post" enctype="multipart/form-data">
        {% csrf_token %}
        <input type="file" name="dicom_files" id="filepicker" webkitdirectory multiple>
        <button type="button" id="uploadButton">Upload</button>

    </form>

    <details id="upload-results"><summary>Upload results</summary>
        <div id="upload-results-list">
            <h5>Uploaded</h5>
            <ul id="uploaded-files"></ul>
            <h5>Duplicates</h5>
            <ul id="duplicate-series"></ul>
            <details>
                <summary>Files</summary>
                <ul id="duplicate-files"></ul>
            </details>
            <h5>Failed</h5>
            <ul id="failed-files"></ul>
        </div>
    </details>

    <div class="alert alert-warning hide anon-alert" role="alert">
        <p>The system has detected files that do not appear to be annonymised. These have been highlighted below.</p>

        <p>If you believe this is a false positive please contact ross.kruger@nhs.net with details about the file that has been flagged.</p>

        <details><summary>Override annonymisation check</summary>
            <p>By clicking the button below you can override the annonymisation check and upload the files anyway. Only do this if you are sure this is a false positive.</p>
            <button id="override-anon" _="on click remove @disabled from #uploadButton then alert('Upload enabled')">Override annonymisation check</button>
        </div>

        <h4>Selected files:</h4>
        <div id="duplicate-progress"></div>
        <div id="single-dicom-viewer" class="dicom-viewer" data-images="" data-annotations=''></div>
        <ol id="listing"></ol>


        <div id="loading" class="fullscreen-overlay">
            <div class="progress-block">
                <div id="progress"></div>
                <div class="sk-cube-grid full">
                    <div class="sk-cube sk-cube1"></div>
                    <div class="sk-cube sk-cube2"></div>
                    <div class="sk-cube sk-cube3"></div>
                    <div class="sk-cube sk-cube4"></div>
                    <div class="sk-cube sk-cube5"></div>
                    <div class="sk-cube sk-cube6"></div>
                    <div class="sk-cube sk-cube7"></div>
                    <div class="sk-cube sk-cube8"></div>
                    <div class="sk-cube sk-cube9"></div>
                </div>
                <div id="progress-text">Uploading...</div>
            </div>
        </div>

{% endblock %}


{% block js %}
    <script src="https://cdn.jsdelivr.net/npm/hash-wasm@4"></script>

    <script src="{% static 'js/upload_form_helpers.js' %}"></script>
    <script>
        window.to_upload = [];
        window.upload_count = 1;
        window.duplicate_series = new Set()

        async function calculateBlake3Hash(file) {
    // Read file as ArrayBuffer
            const buffer = await file.arrayBuffer();
    // Parse as DICOM
            let pixelDataElement;
            try {
                const byteArray = new Uint8Array(buffer);
                const dataSet = dicomParser.parseDicom(byteArray);
                pixelDataElement = dataSet.elements.x7fe00010; // Pixel Data tag
                if (!pixelDataElement) {
                    throw new Error("No Pixel Data found in DICOM file.");
                }
        // Extract pixel data bytes
                const pixelData = byteArray.subarray(pixelDataElement.dataOffset, pixelDataElement.dataOffset + pixelDataElement.length);
        // Hash just the pixel data
                return await hashwasm.blake3(pixelData);
            } catch (err) {
                console.error("DICOM parse/hash error:", err, file.name);
        // Fallback: hash the whole file if pixel data is missing or parsing fails
                return await hashwasm.blake3(new Uint8Array(buffer));
            }
        }
        async function checkForDuplicates(files) {
            const hashes = [];
            for (const file of files) {
                const hash = await calculateBlake3Hash(file);
                hashes.push(hash);
                file._blake3_hash = hash; // Store for later use if needed
            }
    // Call the API
            return fetch("{% url 'api-1:check_images_hashes' %}", {
                method: "POST",
                headers: {
                    "Content-Type": "application/json",
                    "X-CSRFToken": "{{ csrf_token }}"
                },
                body: JSON.stringify(hashes)
            }).then(res => res.json());
        }


        async function processFilesWithDuplicateCheck() {
            const file_set = document.getElementById("filepicker").files;
            let hasDuplicates = false;
            const $dupProgress = $("#duplicate-progress");
            $dupProgress.empty();

            if ($("#check-duplicates").is(":checked")) {
                $dupProgress.html('<span style="color:#0d6efd;">Calculating hashes...</span>');
                $("#progress-text").text("Checking for duplicates...");

        // Calculate hashes and update progress
                const hashes = [];
                let n = 0;
                for (const file of file_set) {
                    n += 1;
                    $dupProgress.html(`<span style="color:#0d6efd;">Hashing: ${file.webkitRelativePath} (${n} of ${file_set.length})</span>`);
                    const hash = await calculateBlake3Hash(file);
                    hashes.push(hash);
                    file._blake3_hash = hash;
                }

                $dupProgress.html('<span style="color:#0d6efd;">Checking hashes with server...</span>');
                const result = await fetch("{% url 'api-1:check_images_hashes' %}", {
                    method: "POST",
                    headers: {
                        "Content-Type": "application/json",
                        "X-CSRFToken": "{{ csrf_token }}"
                    },
                    body: JSON.stringify(hashes)
                }).then(res => res.json());

        // Build a map from hash to file for quick lookup
                const hashToFile = {};
                for (const file of file_set) {
                    if (file._blake3_hash) {
                        hashToFile[file._blake3_hash] = file;
                    }
                }

                let duplicateCount = 0;
        // Map of unique series url to {url, id}
                const duplicateSeriesMap = {};
                for (const hash in result) {
                    if (result[hash].id) {
                        hasDuplicates = true;
                        duplicateCount += 1;
                // Highlight the file in the listing
                        const file = hashToFile[hash];
                        if (file) {
                            const selector = `.dicom-file[data-path='${file.webkitRelativePath}']`;
                            const li = document.querySelector(selector);
                            if (li) {
                                li.classList.add("duplicate-file-highlight");
                            }
                        }
                // Collect unique series by url
                        if (result[hash].url) {
                            duplicateSeriesMap[result[hash].url] = {
                                url: result[hash].url,
                                id: result[hash].id
                            };
                        }
                    }
                }

                if (hasDuplicates) {
                    $("#uploadButton").prop("disabled", true);
            // Show summary in red and list duplicate series as links if any
                    let html = `<div style="color:#dc3545;font-weight:bold;">Found ${duplicateCount} duplicate file(s). Please review before uploading.</div>`;
                    const seriesUrls = Object.values(duplicateSeriesMap);
                    if (seriesUrls.length > 0) {
                        html += `<div style="margin-top:8px;">Duplicate series:</div><ul>`;
                        seriesUrls.forEach(series => {
                            html += `<li><a href="${series.url}" target="_blank" style="color:#dc3545;text-decoration:underline;">Series ${series.id}</a></li>`;
                        });
                        html += `</ul>`;
                    }
                    $dupProgress.html(html);
                    toastr.error("Duplicates detected. Please review before uploading.");
                } else {
                    $("#uploadButton").prop("disabled", false);
                    $dupProgress.html('<span style="color:#198754;">No duplicates detected.</span>');
                }
            } else {
                $("#uploadButton").prop("disabled", false);
                $dupProgress.empty();
            }
        }


        function* chunks(arr, n) {
            for (let i = 0; i < arr.length; i += n) {
                yield arr.slice(i, i + n);
            }
        }

        function onUploadButtonClicked(event) {
            event.preventDefault();
            const fileInput = document.getElementById("filepicker");
            const selectedFiles = fileInput.files;
    // Check if any files are selected
            if (selectedFiles.length === 0) {
                alert("Please select at least one file to upload.");
                return;
            }
            //alert(`${window.to_upload.length} files selected. Uploading...`);

            window.upload_results = {
                "uploaded": [],
                "duplicates": [],
                "failed": []
            };

            chunked_files = [...chunks(window.to_upload, 10)];


            $("#loading").show();
            $("#progress-text").text(`Uploading..`);
            for (let i = 0; i < chunked_files.length; i++) {
                uploadFiles(chunked_files[i], i, chunked_files.length);

            }
        }

        function uploadFiles(files_to_upload, current_chunk, total_chunks){
  // Create a FormData object to store the form data
            const formData = new FormData();
  // Append each selected file to the FormData object
            for (let i = 0; i < files_to_upload.length; i++) {
                formData.append("files", files_to_upload[i]);
            }
            const xhr = new XMLHttpRequest();
            xhr.open("POST", "{% url 'api-1:upload_dicom' %}", true);
            xhr.setRequestHeader("X-CSRFToken", "{{ csrf_token }}");
            xhr.onreadystatechange = function () {
                $("#progress-text").text(`Uploading ${window.upload_count} of ${total_chunks} chunks`);
                if (xhr.readyState === XMLHttpRequest.DONE) {
                    window.upload_count += 1;
                    if (xhr.status === 200) {
                    // Handle successful response from the server
                        window.upload_results["uploaded"].push(...JSON.parse(xhr.response)["uploaded"]);
                        window.upload_results["duplicates"].push(...JSON.parse(xhr.response)["duplicates"]);
                        window.upload_results["failed"].push(...JSON.parse(xhr.response)["failed"]);

                        for (let i = 0; i < JSON.parse(xhr.response)["uploaded"].length; i++) {
                            let item = document.createElement("li");
                            item.textContent = JSON.parse(xhr.response)["uploaded"][i];
                            $("#uploaded-files").append(item);
                        }

                        for (let i = 0; i < JSON.parse(xhr.response)["duplicates"].length; i++) {
                            let item = document.createElement("li");
                            item.textContent = JSON.parse(xhr.response)["duplicates"][i];
                            $("#duplicate-files").append(item);
                        }
                        window.duplicate_series.add(...JSON.parse(xhr.response)["duplicate_series"]);

                        for (let i = 0; i < JSON.parse(xhr.response)["failed"].length; i++) {
                            let item = document.createElement("li");
                            item.textContent = JSON.parse(xhr.response)["failed"][i];
                            $("#failed-files").append(item);
                        }
                    } else {
                   // Handle error response from the server
                        console.error('Failed to upload files.');
                        alert("Error occurred during file upload. Please try again.");
                    }
                    if (current_chunk + 1 == total_chunks) {
                        $("#loading").hide();

                        // If there are duplicates, open the details and highlight the series
                        if (window.duplicate_series.size > 0) {
                            $("#upload-results").attr("open", true);
                            $("#duplicate-series").closest("details").attr("open", true);
                            // Show a global alert for duplicate files
                            toastr.warning(
                                "Some duplicate files have been detected and not re-imported.",
                                "Duplicate Files Warning",
                                {
                                    "closeButton": true,
                                    "progressBar": true,
                                    "positionClass": "toast-top-full-width",
                                    "timeOut": 7000
                                }
                            );
                            window.duplicate_series.forEach((element) => {
                                const wrapper = document.createElement("div");
                                wrapper.className = "alert alert-warning duplicate-series-highlight";
                                const link = document.createElement("a");
                                link.href = element;
                                link.textContent = `Series exists: ${element}`;
                                link.target = "_blank";
                                wrapper.appendChild(link);
                                $("#duplicate-series").append(wrapper);
                            });
                        };
                        //$("#duplicate-series").append([...window.duplicate_series].join("<br/>"));  
                        alert("Uploading complete")
                    }
                }
            };
            xhr.send(formData);
        }

        function checkAnnonymisation(byteArray, file) {
        // We need to setup a try/catch block because parseDicom will throw an exception
        // if you attempt to parse a non dicom part 10 file (or one that is corrupted)
            try {
            // parse byteArray into a DataSet object using the parseDicom library
                var dataSet = dicomParser.parseDicom(byteArray);

            // dataSet contains the parsed elements.  Each element is available via a property
            // in the dataSet.elements object.  The property name is based on the elements group
            // and element in the following format: xggggeeee where gggg is the group number
            // and eeee is the element number with lowercase hex characters.

            // To access the data for an element, we need to know its type and its tag.
            // We will get the sopInstanceUid from the file which is a string and
            // has the tag (0020,000D)
                var study_description = dataSet.string('x00081030');
                var accession_number = dataSet.string('x00080050').toLowerCase();
                var patient_id = dataSet.string('x00100020').toLowerCase();

                not_anon = false;

                site_codes = ["ref", "rk9", "ra9", "rh8", "rbz", "rba"]

                errors = []
                for (let i = 0; i < site_codes.length; i++) {
                    if ( accession_number.startsWith(site_codes[i]))
                    {
                        not_anon = true;
                        errors.push(`Accession number starts with ${site_codes[i]} (${accession_number})`);
                    }
                    if ( patient_id.startsWith(site_codes[i]))
                    {
                        not_anon = true;
                        errors.push(`Patient ID starts with ${site_codes[i]} (${patient_id})`);
                    }
                }

                if (not_anon) {
                    toastr.warning(`File does not appear to be annonymised<br/>Accession: ${accession_number} <br/>Patient ID: ${patient_id}<br/>`, 'Anonymisation warning', {
                        "closeButton": false,
                        "debug": false,
                        "newestOnTop": false,
                        "progressBar": false,
                        "positionClass": "toast-top-full-width",
                        "preventDuplicates": false,
                        "onclick": null,
                        "showDuration": "0",
                        "hideDuration": "0",
                        "timeOut": 0,
                        "extendedTimeOut": 0,
                        "showEasing": "swing",
                        "hideEasing": "linear",
                        "showMethod": "fadeIn",
                        "hideMethod": "fadeOut",
                    });
                    $("#uploadButton").prop("disabled", true);
                    $(".anon-alert").show();

                    setTimeout(
                        function(errors) {
                            console.log(errors)
                        // We delay this a few seconds otherwise it does not work
                            $(`.dicom-file[data-path='${file.webkitRelativePath}']`).addClass("image-ident-warning").append($(`<div>${errors.join("<br/>")}</div>`));
                        }, 2000, errors
                    )
                }

            // TODO: fix
            //$.ajax({
            //    url: `{% url 'generic:examination-autocomplete' %}/${encodeURI(study_description)}`,
            //    //context: document.body,
            //    success: function(data){
            //        console.log(data)
            //    }
            //});

            //// try to match with a study description (if not already selected)
            //if ($(`#id_examination_to option`).length < 1) {
            //    option = $(`#id_examination_from option[title*='${study_description}' i]`);

            //    if (option.length) {
            //        option.appendTo($("#id_examination_to"));
            //        toastr.success(`Examination set to ${option.attr('title')} from dicom data`);
            //    } else {
            //        toastr.warning(
            //            `Unable to set examination ${study_description} from dicom data (it needs creating)`);

            //    }
            //}
                return true;

            } catch (err) {
                console.log(err);
                return false;
            }
        }
        async function loadDicomViewerUploads(callback) {
            $("#uploadButton").attr("disabled", false);
            $("#single-dicom-viewer").hide();
            console.log("load")
            $("#single-dicom-viewer").empty()
            //images = []
            //Function that returns a promise to read the file
            const reader = (file) => {
                return new Promise((resolve, reject) => {
                    const fileReader = new FileReader();
                    fileReader.onload = () => resolve(fileReader);
                    fileReader.readAsDataURL(file);
                });
            }

            const readFile = (file, el) => {
                reader(file).then(reader => {
                    //if ($("#id_examination").find(":selected")[0].dataset.select2Id == "2") {
                    //    var byteArray = new Uint8Array(reader.result);
                    //    console.log("etrxact", reader)
                    //    extractDicomStudyDescription(byteArray);
                    //}
                    var image = new Image();
                    image.height = 100;
                    image.title = file.name;
                    image.src = reader.result;
                    image.className = "temp-thumb";
                    $(el).append(image);
                    //images.push(reader.result);
                    loadViewer(images);

                });
            }

            const dicomReader = (file) => {
                return new Promise((resolve, reject) => {
                    const fileReader = new FileReader();
                    fileReader.onload = () => resolve(fileReader);
                    fileReader.readAsArrayBuffer(file);
                });
            }

            const readDicomFile = (file) => {
                return dicomReader(file).then(reader => {
                    var byteArray = new Uint8Array(reader.result);
                    return checkAnnonymisation(byteArray, file);
                });
            }



            file_set = document.getElementById("filepicker").files;


            let output = document.getElementById("listing");
            images = [];
            window.to_upload = [];
            $("#loading").show();
            $("#progress-text").text(`Processing..`);
            n = 0;
            for (const file of file_set) {
                n += 1;
                $("#progress-text").text(`Processing ${file.name} (${n} of ${file_set.length})`);
                    //await readFile(file);
                dicom = await readDicomFile(file);

                if (dicom) {
                    let item = document.createElement("li");
                    item.className = "dicom-file";
                    item.textContent = file.webkitRelativePath;
                    item.dataset.path = file.webkitRelativePath;
                    output.appendChild(item);
                        //await readFile(file, item);


                    let url = URL.createObjectURL(file)
                    imageId = `wadouri:${url}`;
                    if ($("#show-viewer").is(":checked")) {
                        images.push(imageId);
                    }
                    window.to_upload.push(file);

                    if (!$("#check-annonymisation-ocr").is(":checked") && !$("#show-thumbnails").is(":checked")) {

                    } else {
                        const element = $(`<div class='temp-thumb' src=${imageId}></div>`).get(0)
                        item.appendChild(element);
                        cornerstone.enable(element);
                        cornerstone.loadAndCacheImage(imageId).then(function (image) {
                            cornerstone.displayImage(element, image);

                            if ($("#check-annonymisation-ocr").is(":checked")) {
                                $(element).addClass("temp-thumb-large");
                                cornerstone.resize(element)
                                setTimeout(function () {
                                    ocr($(element).find("canvas").get(0).toDataURL(), element);
                                    $(element).removeClass("temp-thumb-large");
                                    cornerstone.resize(element)


                                    if (!$("#show-thumbnails").is(":checked")) {
                                        cornerstone.disable(element);
                                        element.remove();
                                    }
                                }, 500);
                            }


                        });
                    }
                }
            }


            const event = new CustomEvent('loadDicomViewerUrls', {
                "detail": {"images": images, "annontations": undefined}
            });

            $("#loading").hide()
            if ($("#show-viewer").is(":checked")) {
                window.dispatchEvent(event);
                $("#single-dicom-viewer").show();
            }



        }
        function ocr(url, input) {
            Tesseract.recognize(
                url,
                'eng',
                //{ logger: m => console.log(m) }
            ).then(({
                data: {
                    text
                }
            }) => {
                l = text.toLowerCase();
                uploading_el = $(`img[data-input-id='${input.id}'], div[data-input-id='${input.id}']`)
                uploading_el.removeClass("image-ident-loading");
                if (l.includes("accession") || l.includes("number") || l.search(
                    /(ref|rk9|rh8|ra9|rbz)\d+/g) > -1) {
                        console.log("Match found ", input);
                        $(input).addClass("image-ident-warning");
                        uploading_el.addClass("image-ident-warning");
                        $("uploadButton").prop("disabled", true);
                        toastr.warning(`File does not appear to be annonymised<br/>Image data burned in<br/>${element}`, 'Anonymisation warning', {
                            "closeButton": false,
                            "debug": false,
                            "newestOnTop": false,
                            "progressBar": false,
                            "positionClass": "toast-top-full-width",
                            "preventDuplicates": false,
                            "onclick": null,
                            "showDuration": "0",
                            "hideDuration": "0",
                            "timeOut": 0,
                            "extendedTimeOut": 0,
                            "showEasing": "swing",
                            "hideEasing": "linear",
                            "showMethod": "fadeIn",
                            "hideMethod": "fadeOut",
                        });
                    } else {
                        $(input).addClass("image-ident-ok");

                    }
            })

        }

        $(document).ready(function () {
            document.getElementById("filepicker").addEventListener("change", async () => {
                await loadDicomViewerUploads();
                await processFilesWithDuplicateCheck();
            });


        // Add event listener to the button element
            const uploadButton = document.getElementById("uploadButton");
            uploadButton.addEventListener("click", onUploadButtonClicked);
        });

    </script>
    <script type="text/hyperscript">

        init
        add @disabled to #uploadButton
        end
    </script>
{% endblock %}

{% block css %}
    <style>
        .duplicate-file-highlight {
            background: #fff3cd !important;
            border-left: 5px solid #dc3545 !important;
            color: #856404 !important;
        }
    </style>
{% endblock %}