import * as metaData from '../../metaData';
import { Events, ImageQualityStatus, RequestType } from '../../enums';
import eventTarget from '../../eventTarget';
import imageLoadPoolManager from '../../requestPool/imageLoadPoolManager';
import ProgressiveIterator from '../../utilities/ProgressiveIterator';
import imageRetrieveMetadataProvider from '../../utilities/imageRetrieveMetadataProvider';
import { hasFloatScalingParameters } from '../../utilities/hasFloatScalingParameters';
import autoLoad from '../../utilities/autoLoad';
import triggerEvent from '../../utilities/triggerEvent';
import ImageVolume from './ImageVolume';
import ProgressiveRetrieveImages from '../../loaders/ProgressiveRetrieveImages';
import { canRenderFloatTextures } from '../../init';
import { loadAndCacheImage } from '../../loaders/imageLoader';
const requestTypeDefault = RequestType.Prefetch;
export default class BaseStreamingImageVolume extends ImageVolume {
    constructor(imageVolumeProperties, streamingProperties) {
        super(imageVolumeProperties);
        this.framesLoaded = 0;
        this.framesProcessed = 0;
        this.framesUpdated = 0;
        this.autoRenderOnLoad = true;
        this.cachedFrames = [];
        this.reRenderTarget = 0;
        this.reRenderFraction = 2;
        this.imagesLoader = this;
        this.cancelLoading = () => {
            const { loadStatus } = this;
            if (!loadStatus || !loadStatus.loading) {
                return;
            }
            loadStatus.loading = false;
            loadStatus.cancelled = true;
            this.clearLoadCallbacks();
            const filterFunction = ({ additionalDetails }) => {
                return additionalDetails.volumeId !== this.volumeId;
            };
            imageLoadPoolManager.filterRequests(filterFunction);
        };
        this.loadStatus = streamingProperties.loadStatus;
    }
    invalidateVolume(immediate) {
        const { vtkOpenGLTexture } = this;
        const { numFrames } = this;
        for (let i = 0; i < numFrames; i++) {
            vtkOpenGLTexture.setUpdatedFrame(i);
        }
        this.modified();
        if (immediate) {
            autoLoad(this.volumeId);
        }
    }
    clearLoadCallbacks() {
        this.loadStatus.callbacks = [];
    }
    callLoadStatusCallback(evt) {
        const { framesUpdated, framesProcessed, totalNumFrames } = evt;
        const { volumeId, reRenderFraction, loadStatus, metadata } = this;
        const { FrameOfReferenceUID } = metadata;
        if (this.autoRenderOnLoad) {
            if (framesUpdated > this.reRenderTarget ||
                framesProcessed === totalNumFrames) {
                this.reRenderTarget += reRenderFraction;
                autoLoad(volumeId);
            }
        }
        if (framesProcessed === totalNumFrames) {
            loadStatus.callbacks.forEach((callback) => callback(evt));
            const eventDetail = {
                FrameOfReferenceUID,
                volumeId: volumeId,
            };
            triggerEvent(eventTarget, Events.IMAGE_VOLUME_LOADING_COMPLETED, eventDetail);
        }
    }
    updateTextureAndTriggerEvents(imageIdIndex, imageId, imageQualityStatus = ImageQualityStatus.FULL_RESOLUTION) {
        const frameIndex = this.imageIdIndexToFrameIndex(imageIdIndex);
        const { cachedFrames, numFrames, totalNumFrames } = this;
        const { FrameOfReferenceUID } = this.metadata;
        const currentStatus = cachedFrames[frameIndex];
        if (currentStatus > imageQualityStatus) {
            return;
        }
        if (cachedFrames[frameIndex] === ImageQualityStatus.FULL_RESOLUTION) {
            return;
        }
        const complete = imageQualityStatus === ImageQualityStatus.FULL_RESOLUTION;
        cachedFrames[imageIdIndex] = imageQualityStatus;
        this.framesUpdated++;
        if (complete) {
            this.framesLoaded++;
            this.framesProcessed++;
        }
        const eventDetail = {
            FrameOfReferenceUID,
            volumeId: this.volumeId,
            numberOfFrames: numFrames,
            framesProcessed: this.framesProcessed,
        };
        triggerEvent(eventTarget, Events.IMAGE_VOLUME_MODIFIED, eventDetail);
        if (complete && this.framesProcessed === this.totalNumFrames) {
            this.loadStatus.loaded = true;
            this.loadStatus.loading = false;
        }
        this.callLoadStatusCallback({
            success: true,
            imageIdIndex,
            imageId,
            framesLoaded: this.framesLoaded,
            framesProcessed: this.framesProcessed,
            framesUpdated: this.framesUpdated,
            numFrames,
            totalNumFrames,
            complete,
            imageQualityStatus,
        });
        this.vtkOpenGLTexture.setUpdatedFrame(frameIndex);
        if (this.loadStatus.loaded) {
            this.loadStatus.callbacks = [];
        }
    }
    successCallback(imageId, image) {
        const imageIdIndex = this.getImageIdIndex(imageId);
        const { imageQualityStatus } = image;
        if (this.loadStatus.cancelled) {
            console.warn('volume load cancelled, returning for imageIdIndex: ', imageIdIndex);
            return;
        }
        this.updateTextureAndTriggerEvents(imageIdIndex, imageId, imageQualityStatus);
        if (this.isDynamicVolume()) {
            this.checkDimensionGroupCompletion(imageIdIndex);
        }
    }
    errorCallback(imageId, permanent, error) {
        if (!permanent) {
            return;
        }
        const { totalNumFrames, numFrames } = this;
        const imageIdIndex = this.getImageIdIndex(imageId);
        this.framesProcessed++;
        if (this.framesProcessed === totalNumFrames) {
            this.loadStatus.loaded = true;
            this.loadStatus.loading = false;
        }
        this.callLoadStatusCallback({
            success: false,
            imageId,
            imageIdIndex,
            error,
            framesLoaded: this.framesLoaded,
            framesProcessed: this.framesProcessed,
            framesUpdated: this.framesUpdated,
            numFrames,
            totalNumFrames,
        });
        if (this.loadStatus.loaded) {
            this.loadStatus.callbacks = [];
        }
        const eventDetail = {
            error,
            imageIdIndex,
            imageId,
        };
        triggerEvent(eventTarget, Events.IMAGE_LOAD_ERROR, eventDetail);
    }
    load(callback) {
        const { imageIds, loadStatus, numFrames } = this;
        const { transferSyntaxUID } = metaData.get('transferSyntax', imageIds[0]) || {};
        const imageRetrieveConfiguration = metaData.get(imageRetrieveMetadataProvider.IMAGE_RETRIEVE_CONFIGURATION, this.volumeId, transferSyntaxUID, 'volume');
        this.imagesLoader = this.isDynamicVolume()
            ? this
            : imageRetrieveConfiguration
                ? (imageRetrieveConfiguration.create ||
                    ProgressiveRetrieveImages.createProgressive)(imageRetrieveConfiguration)
                : this;
        if (loadStatus.loading === true) {
            return;
        }
        const { loaded } = this.loadStatus;
        const totalNumFrames = imageIds.length;
        if (loaded) {
            if (callback) {
                callback({
                    success: true,
                    framesLoaded: totalNumFrames,
                    framesProcessed: totalNumFrames,
                    numFrames,
                    totalNumFrames,
                });
            }
            return;
        }
        if (callback) {
            this.loadStatus.callbacks.push(callback);
        }
        this._prefetchImageIds();
    }
    getLoaderImageOptions(imageId) {
        const { transferSyntaxUID: transferSyntaxUID } = metaData.get('transferSyntax', imageId) || {};
        const imagePlaneModule = metaData.get('imagePlaneModule', imageId) || {};
        const { rows, columns } = imagePlaneModule;
        const imageIdIndex = this.getImageIdIndex(imageId);
        const modalityLutModule = metaData.get('modalityLutModule', imageId) || {};
        const generalSeriesModule = metaData.get('generalSeriesModule', imageId) || {};
        const scalingParameters = {
            rescaleSlope: modalityLutModule.rescaleSlope,
            rescaleIntercept: modalityLutModule.rescaleIntercept,
            modality: generalSeriesModule.modality,
        };
        if (scalingParameters.modality === 'PT') {
            const suvFactor = metaData.get('scalingModule', imageId);
            if (suvFactor) {
                this._addScalingToVolume(suvFactor);
                scalingParameters.suvbw = suvFactor.suvbw;
            }
        }
        const floatAfterScale = hasFloatScalingParameters(scalingParameters);
        const allowFloatRendering = canRenderFloatTextures();
        this.isPreScaled = true;
        if (scalingParameters &&
            scalingParameters.rescaleSlope !== undefined &&
            scalingParameters.rescaleIntercept !== undefined) {
            const { rescaleSlope, rescaleIntercept } = scalingParameters;
            this.isPreScaled =
                typeof rescaleSlope === 'number' &&
                    typeof rescaleIntercept === 'number';
        }
        if (!allowFloatRendering && floatAfterScale) {
            this.isPreScaled = false;
        }
        const targetBuffer = {
            type: this.dataType,
            rows,
            columns,
        };
        return {
            targetBuffer,
            allowFloatRendering,
            preScale: {
                enabled: this.isPreScaled,
                scalingParameters,
            },
            transferPixelData: true,
            requestType: requestTypeDefault,
            transferSyntaxUID,
            additionalDetails: {
                imageId,
                imageIdIndex,
                volumeId: this.volumeId,
            },
        };
    }
    callLoadImage(imageId, imageIdIndex, options) {
        const { cachedFrames } = this;
        if (cachedFrames[imageIdIndex] === ImageQualityStatus.FULL_RESOLUTION) {
            return;
        }
        const handleImageCacheAdded = (event) => {
            const { image } = event.detail;
            if (image.imageId === imageId) {
                this.vtkOpenGLTexture.setUpdatedFrame(imageIdIndex);
                eventTarget.removeEventListener(Events.IMAGE_CACHE_IMAGE_ADDED, handleImageCacheAdded);
            }
        };
        eventTarget.addEventListener(Events.IMAGE_CACHE_IMAGE_ADDED, handleImageCacheAdded);
        const uncompressedIterator = ProgressiveIterator.as(loadAndCacheImage(imageId, options));
        return uncompressedIterator.forEach((image) => {
            this.successCallback(imageId, image);
        }, this.errorCallback.bind(this, imageIdIndex, imageId));
    }
    getImageIdsRequests(imageIds, priorityDefault) {
        this.totalNumFrames = this.imageIds.length;
        const autoRenderPercentage = 2;
        if (this.autoRenderOnLoad) {
            this.reRenderFraction =
                this.totalNumFrames * (autoRenderPercentage / 100);
            this.reRenderTarget = this.reRenderFraction;
        }
        const requests = imageIds.map((imageId) => {
            const imageIdIndex = this.getImageIdIndex(imageId);
            const requestType = requestTypeDefault;
            const priority = priorityDefault;
            const options = this.getLoaderImageOptions(imageId);
            return {
                callLoadImage: this.callLoadImage.bind(this),
                imageId,
                imageIdIndex,
                options,
                priority,
                requestType,
                additionalDetails: {
                    volumeId: this.volumeId,
                },
            };
        });
        return requests;
    }
    getImageLoadRequests(priority) {
        throw new Error('Abstract method');
    }
    getImageIdsToLoad() {
        throw new Error('Abstract method');
    }
    loadImages() {
        this.loadStatus.loading = true;
        const requests = this.getImageLoadRequests(5);
        requests.reverse().forEach((request) => {
            if (!request) {
                return;
            }
            const { callLoadImage, imageId, imageIdIndex, options, priority, requestType, additionalDetails, } = request;
            imageLoadPoolManager.addRequest(callLoadImage.bind(this, imageId, imageIdIndex, options), requestType, additionalDetails, priority);
        });
        return Promise.resolve(true);
    }
    _prefetchImageIds() {
        this.loadStatus.loading = true;
        const imageIds = [...this.getImageIdsToLoad()];
        this.totalNumFrames = this.imageIds.length;
        const autoRenderPercentage = 2;
        if (this.autoRenderOnLoad) {
            this.reRenderFraction =
                this.totalNumFrames * (autoRenderPercentage / 100);
            this.reRenderTarget = this.reRenderFraction;
        }
        return this.imagesLoader.loadImages(imageIds, this).catch((e) => {
            console.debug('progressive loading failed to complete', e);
        });
    }
    _addScalingToVolume(suvFactor) {
        if (this.scaling) {
            return;
        }
        const { suvbw, suvlbm, suvbsa } = suvFactor;
        const petScaling = {};
        if (suvlbm) {
            petScaling.suvbwToSuvlbm = suvlbm / suvbw;
        }
        if (suvbsa) {
            petScaling.suvbwToSuvbsa = suvbsa / suvbw;
        }
        if (suvbw) {
            petScaling.suvbw = suvbw;
        }
        this.scaling = { PT: petScaling };
    }
    checkDimensionGroupCompletion(imageIdIndex) { }
}
