Source: public/javascript/modules/olqv_linkedviews.js

import {
    unitRescale,
} from "./utils.js";
import { withSAMP, dataPaths, testMode } from './init.js';
import { ServerApi } from './serverApi.js'
import { FITS_HEADER } from './fitsheader.js';
import { Constants } from "./constants.js";
import { setOnHubAvailability } from "./samp_utils.js";
import { getProjection } from "./olqv_projections.js";
import { DOMAccessor } from "./domelements.js";
import { EventFactory } from './customevents.js';
import { LastClickMarker, LastClickMarkerSummed } from './lastclickmarker.js';
import {fireChartSelectionEvent, SingleSpectrumViewer, SingleSpectrumViewer1D, SummedPixelsSpectrumViewer} from './spectrum/olqv_spectrum.js';
import {Slice, SingleSlice, SummedSlice, getSliceUpdateEvent} from './olqv_slice.js';


/**
 * Class creating link between slices and spectra
 * @typedef {Object} ViewLinker
 * 
 * @property {SpectrumViewer} spectrumViewer
 * @property {SummedPixelsSpectrumViewer} summedPixelsSpectrumViewer
 * @property {array} extent
 * @property {RaLabelFormatter} raLabelFormatter
 * @property {SpectruDecLabelFormattermViewer} decLabelFormatter
 * @property {SliceViewer} singleSliceImage
 * @property {SummedSliceViewer} summedSlicesImage
 * @property {Projection} coordsProjection
 * @property {SpectroscopyUI} spectroUI
 * 
 */
class ViewLinker {
    constructor(paths, width, height,
        RADECRangeInDegrees, divSlice, divSummedSlices, spectroUI) {

        // public attributes
        this.spectrumViewer = null;
        this.summedPixelsSpectrumViewer = null;
        this.spectroUI = spectroUI;

        // a flag used to prevent display refresh if values are not set correctly
        // (i.e valocity/redshift change has not been validated)
        this.isRefreshable = true;

        this.extent = [0, 0, width - 1, height - 1];

        this.singleSliceImage = new SingleSlice(this, divSlice, "hidden-" + divSlice, RADECRangeInDegrees, width, height);
        this.summedSlicesImage = new SummedSlice(this, divSummedSlices, "hidden-" + divSummedSlices, RADECRangeInDegrees, width, height);


        this.singleSliceImage.addGridEventListener(this.summedSlicesImage);
        this.summedSlicesImage.addGridEventListener(this.singleSliceImage);

        // projection object for coordinates calculation
        try {
            this.coordsProjection = getProjection(FITS_HEADER.projectionType);
        } catch (e) {
            alert(e);
        }

        this._currentDensity = null;

        // private attributes
        this._relFITSFilePath = paths.relFITSFilePath;
        this._lastClickMarker = new LastClickMarker(this.singleSliceImage._map, 'popup-single');
        this._lastClickMarkerSummed = new LastClickMarkerSummed(this.summedSlicesImage._map, 'popup-summed');

        //events
        this.singleSliceImage._map.getView().on('change:resolution', (event) => {
            this.updateView(event, this.summedSlicesImage._map.getView());
        });

        this.singleSliceImage._map.getView().on('change:center', (event) => {
            this.updateView(event, this.summedSlicesImage._map.getView());
        });

        this.summedSlicesImage._map.getView().on('change:resolution', (event) => {
            this.updateView(event, this.singleSliceImage._map.getView());
        });

        this.summedSlicesImage._map.getView().on('change:center', (event) => {
            this.updateView(event, this.singleSliceImage._map.getView());
        });
    }

    get relFITSFilePath(){
        return this._relFITSFilePath;
    }

    getSliceIndex() {
        return this.singleSliceImage.sliceIndex;
    }

    updateSummedSlicesFreqIndexes(iFreq0, iFreq1) {
        this.summedSlicesImage.regionOfInterest.iFREQ0 = Math.min(iFreq0, iFreq1);
        this.summedSlicesImage.regionOfInterest.iFREQ1 = Math.max(iFreq0, iFreq1);
        this.summedSlicesImage.onclick(EventFactory.getEvent(EventFactory.EVENT_TYPES.refreshFrequency, {}));
    }

    /**
     * Sets spectrumViewer
     * @param {SpectrumViewer} spectrumViewer 
     */
    setSpectrumViewer(spectrumViewer) {
        this.spectrumViewer = spectrumViewer
    }

    /**
     * Sets summedPixelsSpectrumViewer 
     * @param {SummedPixelsSpectrumViewer} summedPixelsSpectrumViewer 
     */
    setSummedPixelsSpectrumViewer(summedPixelsSpectrumViewer) {
        this.summedPixelsSpectrumViewer = summedPixelsSpectrumViewer
    }

    /**
     * Executes a POST request and updates singleSlice.
     * Request parameters are : 
     *  -for slice retrieval : selected slice index, fits file path
     *  -for image configuration :  ittName, lutName, vmName
     * 
     * @param {number} sliceIndex index of searched slice (int)
     */
    getAndPlotSingleSlice(sliceIndex) {
        console.log('getAndPlotSingleSlice: entering');
        let self = this;
        let config = DOMAccessor.getConfiguration();
        this.singleSliceImage.setSliceIndex(sliceIndex);
        let apiQuery = new ServerApi();
        apiQuery.getSingleSlice(
            this.singleSliceImage.sliceIndex, this._relFITSFilePath, 
            config.ittName, config.lutName, config.vmName,
            (resp)=>{
                self.singleSliceImage.updateSlice(resp["result"]);
                self.singleSliceImage.setRms(parseFloat(resp["result"]["statistics"]["stdev"]), FITS_HEADER.bunit);
                self.singleSliceImage.setMean(parseFloat(resp["result"]["statistics"]["mean"]), FITS_HEADER.bunit);
                DOMAccessor.setSliceChannel("Chan#" + sliceIndex);
                if (withSAMP) {
                    dataPaths.relSlicePNG = resp["result"]["path_to_png"];
                }
                if(testMode){
                    self.singleSliceImage._executeSliceLoadedListener(
                        getSliceUpdateEvent(self.singleSliceImage, sliceIndex, "single",resp["result"]));
                }
            }
        );
    }

    /**
     * Executes a POST request and updates summedSlice
     * Request parameters are : 
     *  -for slice retrieval : start index (on averaged spectrum), end index (on averaged spectrum), fits file path
     *  -for image configuration :  ittName, lutName, vmName
     * 
     * @param {number} sliceIndex0 start index (int)
     * @param {number} sliceIndex1 end index (int)
     */
    getAndPlotSummedSlices(sliceIndex0, sliceIndex1, callback) {
        let self = this;
        let config = DOMAccessor.getConfiguration();
        this.summedSlicesImage.sliceIndex0 = sliceIndex0;
        this.summedSlicesImage.sliceIndex1 = sliceIndex1;
        let apiQuery = new ServerApi();
        apiQuery.getSummedSlice(
            self.summedSlicesImage.sliceIndex0, self.summedSlicesImage.sliceIndex1,
            self._relFITSFilePath, config.ittName, config.lutName, config.vmName, 
            (resp)=>{
                self.summedSlicesImage.updateSlice(resp["result"]);
                self.summedSlicesImage.setRms(parseFloat(resp["result"]["statistics"]["stdev"]), FITS_HEADER.bunit + "*km/s");
                self.summedSlicesImage.setMean(parseFloat(resp["result"]["statistics"]["mean"]), FITS_HEADER.bunit + "*km/s");

                if (callback !== undefined) {
                    callback();
                }

                if (withSAMP) {
                    dataPaths.relSummedSlicesPNG = resp["result"]["path_to_png"];
                }

                if(testMode){
                    self.summedSlicesImage._executeSliceLoadedListener(
                        getSliceUpdateEvent(self.summedSlicesImage, self.getSliceIndex(), "summed", resp["result"]));
                }
            });

        console.log("_updateSummedSlicesWithPOST : exiting");
    }


    /**
     * Refreshes both slices display from current parameters
     */
    refresh() {
        this.getAndPlotSingleSlice(this.singleSliceImage.sliceIndex);
        this.getAndPlotSummedSlices(this.summedSlicesImage.sliceIndex0, this.summedSlicesImage.sliceIndex1);
    }

    /**
     * Notifies to the view that an action occured on an image (image has been moved or zoomed in/out)
     * @param {*} event type of action
     * @param {*} viewRef modified view
     */
    updateView(event, viewRef) {
        let newValue = event.target.get(event.key);
        viewRef.set(event.key, newValue);
    }

    /**
     * Removes the currently selected box on the summedSlicesViewer
     */
    forgetSelectedBox() {
        this.summedSlicesImage.forgetSelectedBox();
    }

    /**
     * Displays a popup at the position clicked in the slice. It shows the coordinates of the click in the image, 
     * the chanel index, ra/dec values and flux density. Flux density is always set as "To Be Determined" because a query to the server 
     * it necessary to get the value. "t.b.d." will be replaced once the value has been obtained.
     * @param {Slice} target the slice where the click occured
     * @param {array} coordinate a 2 elements array containing the x,y coordinates
     */
    _markLastClickInSlice(target, coordinate) {
        let raDec = this.coordsProjection.iRaiDecToHMSDMS(coordinate[0], coordinate[1]);
        target.setPositions(coordinate[0], coordinate[1], raDec["ra"], raDec["dec"]);
        target.setChanIndex(this.singleSliceImage.sliceIndex);
        target.setFluxDensity("t.b.d", "");
        target.updateLastClickInfos();
    };

    /**
     * Calls _markLastClickInSlice in both single and summed slices
     * @param {array} coordinate a 2 elements array containing the x,y coordinates
     */
    markLastClickInSlices(coordinate) {
        this._markLastClickInSlice(this._lastClickMarker, coordinate);
        this._markLastClickInSlice(this._lastClickMarkerSummed, coordinate);
    };


    /**
     * Sets the value of flux density in a popup in the single slice
     * @param {number} density  density value (float)
     * @param {string} source provenance of the call (spectrum or data slice)
     */
    setFluxDensityInPopup(density, source) {
        // marker is shown if click on a slice or click on a spectrum if it is already visible
        if(source === Slice.objectType ||
          (source === SpectrumViewer.objectType && this._lastClickMarker.isVisible)){
            this._currentDensity = density;
            this._lastClickMarker.setFluxDensity(density * unitRescale(FITS_HEADER.bunit), FITS_HEADER.bunit);
            this._lastClickMarker.setChanIndex(this.singleSliceImage.sliceIndex);
            this._lastClickMarker.updateLastClickInfos();
        }
    }

    /**
     * Sets the value of flux density in a popup in the averaged slice
     * density is already known from _currentDensity attribute
     * if isRefresh is true, the popup area will not be displayed if it is currently
     * hidden
     * @param {number} density  density value (float)
     * @param {boolean} isRefresh  only ask for a refresh of the displayed value
     */
    setFluxDensityInSummedPopup(value, isRefresh) {
        this._lastClickMarkerSummed.setFluxDensity(value, FITS_HEADER.bunit + "*km/s");
        this._lastClickMarkerSummed.updateLastClickInfos(isRefresh);

    }
}

/**
 * Returns a ViewLinker object that provides an interface to manipulate Spectrums and Slices
 * 
 * @param {array} radecRange RA/DEC ranges corresponding to opened fits file 
 * @param {SpectroUI} spectroUI spectroscopy interface control object
 * @param {SourceTable} sourceTable NED interface object
 * @param {MarkerList} markerList list of markers
 * @returns {ViewLinker}
 */
function getViewLinker(radecRange, spectroUI, sourceTable, markerList) {
    let RADECRangeInDegrees = radecRange;
    console.log("Data of '" + dataPaths.relFITSFilePath + "' are contained in " + JSON.stringify(radecRange));

    let viewLinker = new ViewLinker(dataPaths, FITS_HEADER.width, FITS_HEADER.height, RADECRangeInDegrees, "slice",
        "summed-slices", spectroUI);

    // naxis3 is slice number, central channel by default
    viewLinker.getAndPlotSingleSlice(Math.round(FITS_HEADER.naxis3 / 2));

    // upper spectrum
    let spectrumViewer = new SingleSpectrumViewer(dataPaths, 'spectrum', 
                                                  Constants.PLOT_WIDTH_3D_LARGE, 
                                                  Constants.PLOT_HEIGHT_RATIO_3D_LARGE, spectroUI);
    spectrumViewer.setViewLinker(viewLinker);
    //spectrumViewer.setSpectroUI(spectroUI);
    viewLinker.setSpectrumViewer(spectrumViewer);

    // show centered channel
    const initialPixel = FITS_HEADER.getCentralPixelPosition();
    spectrumViewer.plot(initialPixel[0], initialPixel[1]);

    // summed spectrum
    let summedPixelsSpectrumViewer = new SummedPixelsSpectrumViewer(dataPaths, 'summed-pixels-spectrum', 
                                                                    Constants.PLOT_WIDTH_3D_LARGE, 
                                                                    Constants.PLOT_HEIGHT_RATIO_3D_LARGE, spectroUI);
    summedPixelsSpectrumViewer.setViewLinker(viewLinker);
    //summedPixelsSpectrumViewer.setSpectroUI(spectroUI);

    viewLinker.setSummedPixelsSpectrumViewer(summedPixelsSpectrumViewer);

    // summed slices image with default selection box
    viewLinker.getAndPlotSummedSlices(summedPixelsSpectrumViewer.defaultIndexMin, 
                                      summedPixelsSpectrumViewer.defaultIndexMax, 
                                      () => {
        viewLinker.summedSlicesImage.drawInitialBox();
    });

    //refresh lines display when redshift selection changes
    sourceTable.addListener(summedPixelsSpectrumViewer);
    sourceTable.addListener(spectrumViewer);
    sourceTable.addListener(viewLinker.summedSlicesImage);
    sourceTable.addListener(viewLinker.singleSliceImage);

    markerList.addListener(viewLinker.summedSlicesImage);
    markerList.addListener(viewLinker.singleSliceImage);

    viewLinker.singleSliceImage.enableSelectorMode();
    viewLinker.summedSlicesImage.enableSelectorMode();

    /*if (withSAMP) {
        // all implement setSampButtonVisible
        setOnHubAvailability([spectrumViewer,
            summedPixelsSpectrumViewer,
            viewLinker.singleSliceImage,
            viewLinker.summedSlicesImage]);
    }*/
    return viewLinker;
}

export {getViewLinker}