import { $ } from 'meteor/jquery';
import { _ } from 'meteor/underscore';
import { OHIF } from 'meteor/ohif:core';
import { cornerstone } from 'meteor/ohif:cornerstone';

function renderIntoViewport(measurementData, enabledElement, viewportIndex) {
    const { activateMeasurements, findAndRenderDisplaySet } = OHIF.measurements;
    const { element } = enabledElement;
    const { studyInstanceUid, seriesInstanceUid, sopInstanceUid } = measurementData;

    return new Promise((resolve, reject) => {
        const renderedCallback = element => {
            activateMeasurements(element, measurementData);
            $(element).one('cornerstoneimagerendered', () => resolve());
        };

        // Find the study by studyInstanceUid and render the display set
        const findAndRender = () => {
            // @TypeSafeStudies
            const study = OHIF.viewer.Studies.findBy({ studyInstanceUid });

            // TODO: Support frames? e.g. for measurements on multi-frame instances
            findAndRenderDisplaySet(
                study.displaySets,
                viewportIndex,
                studyInstanceUid,
                seriesInstanceUid,
                sopInstanceUid,
                renderedCallback
            );
        };

        // Check if the study / series we need is already the one in the viewport.
        // Otherwise, re-render the viewport with the required study/series, then add a rendered
        // callback to activate the measurements
        if (enabledElement && enabledElement.image) {
            const imageId = enabledElement.image.imageId;
            const series = cornerstone.metaData.get('series', imageId);
            const study = cornerstone.metaData.get('study', imageId);

            const isSameStudy = study.studyInstanceUid === measurementData.studyInstanceUid;
            const isSameSeries = series.seriesInstanceUid === measurementData.seriesInstanceUid;
            if (isSameStudy && isSameSeries) {
                // If it is, activate the measurements in this viewport and stop here
                OHIF.viewerbase.viewportUtils.resetViewport(viewportIndex);
                renderedCallback(element);
            } else {
                findAndRender();
            }
        } else {
            findAndRender();
        }
    });
}

function syncViewports(viewportsIndexes) {
    const synchronizer = OHIF.viewer.stackImagePositionOffsetSynchronizer;
    const linkableViewports = synchronizer.getLinkableViewports();
    if (linkableViewports.length) {
        const linkableViewportsIndexes = _.pluck(linkableViewports, 'index');
        const indexes = _.intersection(linkableViewportsIndexes, viewportsIndexes);
        if (indexes.length) {
            OHIF.viewer.stackImagePositionOffsetSynchronizer.activateByViewportIndexes(indexes);
        }
    }
}

// Store the lastActivatedRowItem to cancel jumping if another rowItem was triggered during loading
let lastActivatedRowItem;

/**
 * Activates a set of lesions when lesion table row is clicked
 *
 * @param measurementId The unique key for a specific Measurement
 */
OHIF.measurements.jumpToRowItem = (rowItem, timepoints, childToolKey) => {
    const { isZoomed, zoomedViewportIndex } = OHIF.viewerbase.layoutManager;

    lastActivatedRowItem = rowItem;

    // Retrieve the list of available viewports
    const $viewports = $('.imageViewerViewport');
    const numViewports = Math.max($viewports.length, 0);

    // Clone the timepoint list to prevent modifying the original object
    let timepointList;
    if (isZoomed) {
        timepointList = [timepoints[zoomedViewportIndex]];
    } else {
        timepointList = _.clone(timepoints);
    }

    // Reverse the timepointList array if the flag is set
    if (OHIF.viewer.invertViewportTimepointsOrder) {
        timepointList.reverse();
    }

    // Retrieve the timepoints that are currently being displayed in the  Measurement Table
    const numTimepoints = Math.max(timepointList.length, 1);

    const numViewportsToUpdate = Math.min(numTimepoints, numViewports);

    // Retrieve the measurements data
    const measurementsData = [];
    const promises = new Set();
    for (let i = 0; i < numViewportsToUpdate; i++) {
        const { timepointId } = timepointList[i];

        const dataAtThisTimepoint = _.where(rowItem.entries, { timepointId });
        if (!dataAtThisTimepoint || !dataAtThisTimepoint.length) {
            measurementsData.push(null);
            continue;
        }

        const measurement = dataAtThisTimepoint[0];
        let measurementData = measurement;
        const { toolType } = measurementData;
        const { tool } = OHIF.measurements.getToolConfiguration(toolType);
        if (childToolKey) {
            measurementData = measurementData[childToolKey];
        } else if (Array.isArray(tool.childTools)) {
            tool.childTools.every(key => {
                measurementData = measurementData[key];
                return !measurementData;
            });
        }

        measurementsData.push(measurementData);
        const promise = OHIF.studies.loadStudy(measurementData.studyInstanceUid);
        promise.then(() => OHIF.measurements.syncMeasurementAndToolData(measurement));
        promises.add(promise);
    }

    // Wait for studies metadata to be retrieved before jumpint to the given row item
    Promise.all(promises).then(() => {
        // Stop here if another rowItem was activated during loading process
        if (rowItem !== lastActivatedRowItem) return;

        OHIF.measurements.deactivateAllToolData();

        const activatedViewportIndexes = [];

        // Deactivate stack synchronizer because it will be re-activated later
        OHIF.viewer.stackImagePositionOffsetSynchronizer.deactivate();

        const renderPromises = [];
        for (let viewportIndex = 0; viewportIndex < numViewportsToUpdate; viewportIndex++) {
            const measurementData = measurementsData[viewportIndex];
            if (!measurementData) continue;

            activatedViewportIndexes.push(viewportIndex);

            const element = $viewports.get(viewportIndex);

            // TODO: Implement isEnabledElement in Cornerstone
            // or maybe just remove the 'error' this throws?
            let enabledElement;
            try {
                enabledElement = cornerstone.getEnabledElement(element);
            } catch(error) {
                continue;
            }

            const promise = renderIntoViewport(measurementData, enabledElement, viewportIndex);
            renderPromises.push(promise);
        }

        // Wait for all viewports to be rendered then sync them
        Promise.all(renderPromises).then(() => syncViewports(activatedViewportIndexes));
    });
};
