Source: public/javascript/olqv2d.js

import { FITSHeaderTable, LicensePanel } from "./modules/page_overlay.js";
import { CoordinatesFormatter } from "./modules/utils.js";
import { InfosBlock } from "./modules/olqv_infosblock.js";
import { Settings } from "./modules/olqv_settings.js";
import { MarkersFactory, MarkerList } from "./modules/olqv_markers.js";
import { ContoursFactory, ContoursFactoryGUI } from "./modules/olqv_contours.js";
import { BoxesFactory } from "./modules/olqv_boxes.js";
import { Viewer } from "./modules/olqv_viewer.js";
import { KeyCodeProcessor } from "./modules/olqv_keyboardevents.js";
import { getProjection } from "./modules/olqv_projections.js";
import { CustomControls } from "./modules/olqv_customcontrols.js";
import { FITS_HEADER } from "./modules/fitsheader.js";
import { AxesFactory } from "./modules/olqv_axes.js";
import { sAMPPublisher,setOnHubAvailabilityButtons } from "./modules/samp_utils.js";
import { PublishSAMPButton, ResetButton, DeleteButton } from "./modules/olqv_olbuttons.js";
import { withSAMP, dataPaths, testMode, default_lut_index} from './modules/init.js';
import { DOMAccessor } from "./modules/domelements.js";
import { LastClickMarkerNoChannel } from "./modules/lastclickmarker.js";
import { ServerApi } from "./modules/serverApi.js";
import {setNedUiListeners } from "./modules/listeners.js";
import {Tester} from "./tests/test2d.js";
import { SourceTable } from "./modules/olqv_ned.js";
import { SpectroscopyUI } from "./modules/olqv_spectro.js";


/**
 * Returns an event signaling that a slice has been modified
 * data packed inside the event are used in testing context
 * @param {object} data spectrum data
 * @returns Event
 */

function get2DImageLoadedEvent(meta){
    let event = new Event("2d");

    event.rmsValue = meta.rmsVal;
    event.rmsUnit = meta.rmsUnit;

    event.xRef = Math.round(FITS_HEADER.naxis1 / 2);
    event.yRef = Math.round(FITS_HEADER.naxis2 / 2);
    event.boxExtent = getInitialBoxCoordinates();

    event.minValue = meta.minValue;
    event.maxValue = meta.maxValue;

    return event;
}

/**
 * Returns the coordinates of a box automatically displayed 
 * when a 2D cube is opened
 * @returns array
 */
function getInitialBoxCoordinates() {
    const naxis1Index = FITS_HEADER.naxis1 -1;
    const naxis2Index = FITS_HEADER.naxis2 -1;
    const iRA0 = Math.round(naxis1Index / 2 - naxis1Index / 16);
    const iRA1 = Math.round(naxis1Index / 2 + naxis1Index / 16);
    const iDEC0 = Math.round(naxis2Index / 2 - naxis2Index / 16);
    const iDEC1 = Math.round(naxis2Index / 2 + naxis2Index / 16);
    return [iRA0, iRA1, iDEC0, iDEC1];
}

/**
 * Triggers the test procedure
 * @param {object} statistics some statistics related to the opened data cube
 * @param {BoxesFactory} boxesFactory object to create a box on the cube image
 */
function triggerTest(statistics, boxesFactory){
    let coords = getInitialBoxCoordinates();
    let f = boxesFactory.addBox(coords[0], coords[1], coords[2], coords[3]);

    //viewer.map.addFeature(feature);
    let meta = {"rmsVal": statistics.stdev, "rmsUnit": FITS_HEADER.bunit, "minValue": statistics.min, "maxValue": statistics.max};
    let tester = new Tester();
    tester.imageLoaded( get2DImageLoadedEvent(meta));
}

var enter = function(what) {
    console.group(what);
}

var exit = function() {
    console.groupEnd();
}

//
// This section determines the projection that will be used to calculate the physical coordinates.
//
//var projection; // This variable will contain the appropriate instance of a class derived of Projection.

//
// Projections are deduced from the three last non blank letters of CTYPEn keywords in the FITS header
//
try{
    var projection = getProjection(FITS_HEADER.projectionType);
}catch(e){
    alert(e);
}


let projCode1 = FITS_HEADER.ctype1.trim().slice(-3); // FITS_HEADER_DIRECT["CTYPE1"].trim().slice(-3);
let projCode2 = FITS_HEADER.ctype2.trim().slice(-3); //FITS_HEADER_DIRECT["CTYPE2"].trim().slice(-3);
let optionsMenuDisplay = "none";

if (projCode1 != projCode2) {
    alert(`Don't know how to proceed with two different projections : ${projCode1} , ${projCode1}`);
    throw new Error(`Don't know how to proceed with two different projections : ${projCode1} , ${projCode1}`);
}

// This is the (hidden) canvas where the images are actually drawn.
var hiddenCanvas = document.getElementById("hiddenSlice");

var displaySlice = function(relFITSFilePath, sliceIndex, viewer, contourer, sAMPPublisher) {
    enter(displaySlice.name);
    const lutConf = DOMAccessor.getConfiguration();

    let lutName =  lutConf.lutName;
    let ittName = lutConf.ittName;
    let vmName = lutConf.vmName;

    DOMAccessor.showLoaderAction(true);
    $.post('png', { 'si': sliceIndex, 'relFITSFilePath': relFITSFilePath, 'ittName': ittName, 'lutName': lutName, 'vmName': vmName }).done(
        function(resp) {
            console.group("$.post('/png', {'si': _sliceIndex, 'relFITSFilePath': _relFITSFilePath})");
            DOMAccessor.showLoaderAction(false);
            if (resp["status"] == false) {
                alert("Something went wrong during the generation of the image. The message was '" + resp["message"] + "'");
            } else {
                let dataSteps = resp["result"]["data_steps"];
                let statistics = resp["result"]["statistics"];
                let path_to_png = resp["result"]["path_to_png"];
                let path_to_legend_png = resp["result"]["path_to_legend_png"];
                let imageURL = `${ctxt["urlRoot"]}/${path_to_png}`;
                let colorBarURL = `${ctxt["urlRoot"]}/${path_to_legend_png}`;
                console.log("Image's URL is " + imageURL);
                DOMAccessor.updateSingleSliceColorBar(colorBarURL);
                viewer.display(imageURL, sliceIndex, dataSteps, statistics);

                // executes the test procedure if testMode is activated
                if(testMode)
                    triggerTest(statistics, new BoxesFactory(viewer));

                if (sAMPPublisher && typeof sAMPPublisher === "object") { 
                    // This is a poor test to ensure that it's maybe a function
                    dataPaths.relSlicePNG= path_to_png;
                }
                if (contourer) {
                    contourer.setDefaultValue([statistics["mean"] + statistics["stdev"]]);
                }
            }

            DOMAccessor.showLoaderAction(false);
            DOMAccessor.markLoadingDone();
            console.groupEnd();
        });
    exit();
};

// The header is a template parameter.
var header = ctxt["header"];

// The complete URL of the present page is built upon two template parameters
var urlRoot = ctxt["urlRoot"];
var originalURL = ctxt["originalURL"];
var url = `${urlRoot}${originalURL}`;
var viewer;

// list of sources downloaded from NED catalog
let sourceTable;
// get user input from spectroscopy form
let spectroUI = new SpectroscopyUI();
//let markerList = new MarkerList("input-markers", "show-markers", "clear-markers");
//ned data selection
sourceTable = new SourceTable('ned-data', 'ned-modal-title', spectroUI);
//sourceTable.addListener(markerList);
setNedUiListeners(sourceTable, spectroUI);


$(document).ready(function() {
    enter("What to do when document is ready");
    DOMAccessor.selectLut(default_lut_index);

    $('[data-tooltip="tooltip"]').tooltip();

    var FITSHDR = new FITSHeaderTable(document.getElementById("FITSHDR"), header);

    $('#show-fits-header').click(function() {
        FITSHDR.show()
    });

    $("#spectrum-info-title").html(FITS_HEADER.getSpectrumTitle());

    $('#show-license').click(function() {
        let Licences = new LicensePanel(document.getElementById("licences"));
        Licences.show()
    });

    $("#toggle-options").on("click", function(event) {
       
        if(optionsMenuDisplay ==="block" ){
            optionsMenuDisplay = "none";
            document.getElementById("img-canvas").classList.replace("col-9", "col-12");
            event.currentTarget.textContent = "<<<";

        }else{
            optionsMenuDisplay = "block";
            document.getElementById("img-canvas").classList.replace("col-12", "col-9");
            event.currentTarget.textContent = ">>>";
        }

        Array.from(document.getElementsByClassName("last-col")).forEach(function(element){
            element.style.display = optionsMenuDisplay;
        });
        // update size of image in viewport. If not called, it is simply stretched and coordinates are messed up.
        viewer.map.updateSize();

    });

    DOMAccessor.showLoaderAction(true);
    let serverApi = new ServerApi();
    serverApi.getRADECRangeInDegrees(dataPaths.relFITSFilePath, (resp)=>{
        $("#cube-infos").html(FITS_HEADER.getFitsSummary(true));
        //let RADECRangeInDegrees = resp.data["result"];

        /*
        var naxis3 = 1;
        if (naxis > 2) {
            naxis3 = FITS_HEADER.naxis3;
        }
        
        var naxis4 = 1;
        if (naxis > 4) {
            naxis4 = FITS_HEADER.naxis4;
        }*/

        /* Keep things square */
        if (width > height) {
            height = width;
        } else if (width < height) {
            width = height;
        }

        var naxis = FITS_HEADER.naxis;
        var naxis1 = FITS_HEADER.naxis1;
        var naxis2 = FITS_HEADER.naxis2;

        // Let's keep things square
        var width = Math.max(naxis1, naxis2);
        var height = width;

        // Please note that cdelt is expected to contain values in degree unit !!!
        var coordinatesFormatter = new CoordinatesFormatter(hiddenCanvas, FITS_HEADER.bunit, projection);

        //var infosLine = document.getElementById("infos-line");
        var context = {
            'file': dataPaths.relFITSFilePath,
            'url': url,
            'pixel-slice-range': 0,
            'slice-phys-chars': {
                'type': FITS_HEADER.ctype3,
                'unit': FITS_HEADER.cunit3,
                'value': FITS_HEADER.crval3
            },
            'array-phys-chars': { 'type': FITS_HEADER.btype, 'unit': FITS_HEADER.bunit }
        };

        var infosBlock = new InfosBlock(document.getElementById("infos-line"),
                                        document.getElementsByTagName("body")[0], context);

        viewer = new Viewer(dataPaths.relFITSFilePath, width, height, "slice",
                                "hiddenSlice", coordinatesFormatter, infosBlock);

        let lastClickMarker = new LastClickMarkerNoChannel(viewer.map, "popup-single");

        // displays value at clicked pixel on canvas
        viewer.addClickEvent((event)=>{
            // do request if click is in canvas ( not in left menu )  and no interaction enabled
            //show flux value at clicked position
            function processResult(data){
                // show click marker if value exists
                if(data.result !== null){
                    const x = Math.round(event.coordinate[0]);
                    const y = Math.round(event.coordinate[1]);
                    const raDec = coordinatesFormatter.getRaDec(x, y);
                    if(x >= 0 && y >= 0){
                        lastClickMarker.setPositions(x, y, raDec['ra'], raDec['dec']);
                        lastClickMarker.setFluxDensity(data.result.value, data.result.unit);
                        lastClickMarker.updateLastClickInfos();
                    }
                }
            }

            if( event.originalEvent.target.localName == "canvas" &&  !viewer.hasCurrentInteraction()){
                if(FITS_HEADER.naxis == 2){
                    serverApi.getPixelValue(dataPaths.relFITSFilePath, 
                                            Math.round(event.coordinate[0]), 
                                            Math.round(event.coordinate[1]), 
                                            processResult);
                }else if(FITS_HEADER.naxis >= 3){
                    serverApi.getPixelFreqValue(dataPaths.relFITSFilePath, 
                                                Math.round(event.coordinate[0]), 
                                                Math.round(event.coordinate[1]), 
                                                0, 
                                                processResult);
                }else{
                    alert("Incorrect number of axis : " + FITS_HEADER.naxis);
                }
            }
        });

        var keyCodeProcessor = new KeyCodeProcessor(viewer);
        keyCodeProcessor.open();

        // buttons in side bar
        var customControls = new CustomControls(viewer);

        /*var settings = new Settings(viewer, infosBlock);

        ctxt["luts"].forEach(lut => settings.appendLUT(lut));
        settings.setLUTSelectorIndex(ctxt["default_lut_index"]);

        ctxt["itts"].forEach(itt => settings.appendITT(itt));
        settings.setITTSelectorIndex(ctxt["default_itt_index"]);

        ctxt["vmodes"].forEach(vmode => settings.appendVM(vmode));
        settings.setVideoModeSelectorIndex(ctxt["default_vmode_index"]);

        customControls.addButton(settings.getButton());
        keyCodeProcessor.teach("V", () => settings.getButton().click());
        keyCodeProcessor.teach("v", () => settings.getButton().click());*/

        
        var selector = viewer.getSelector();
        customControls.addButton(selector.getButtonObject().getButton());
        for(let key of selector.getButtonObject().getKeyboardMapping()){
            keyCodeProcessor.teach(key, () => selector.getButtonObject().getButton().click());
        }

        var markersFactory = new MarkersFactory(viewer);
        let nedListener = {
            // a source has been selected in NED
            sourceTableCall(event){
                markersFactory.addMarkers([{"RAInDD": event.detail.ra, 
                                            "DECInDD":event.detail.dec, 
                                            "label": event.detail.object}]);
                markersFactory.source.refresh();
                if(!markersFactory.isOpened){
                    markersFactory.button.click();
                }
            }
        }
        
        sourceTable.addListener(nedListener);

        customControls.addButton(markersFactory.getButtonObject().getButton());
        for(let key of markersFactory.getButtonObject().getKeyboardMapping()){
            keyCodeProcessor.teach(key, () => markersFactory.getButtonObject().getButton().click());
        }

        var contoursFactory = new ContoursFactory(viewer /*, infosBlock*/ );
        customControls.addButton(contoursFactory.getButtonObject().getButton());
        //contoursFactory.getButtonObject().setClickAction(()=>{contoursFactory.getButtonClick()});

        for(let key of contoursFactory.getButtonObject().getKeyboardMapping()){
            keyCodeProcessor.teach(key, () => contoursFactory.getButtonObject().getButton().click());
        }

        var contoursFactoryGUI = new ContoursFactoryGUI(viewer, dataPaths.relFITSFilePath);
        contoursFactoryGUI.connect(contoursFactory);

        var boxesFactory = new BoxesFactory(viewer, infosBlock);
        customControls.addButton(boxesFactory.getButtonObject().getButton());
        for(let key of boxesFactory.getButtonObject().getKeyboardMapping()){
            keyCodeProcessor.teach(key, () => boxesFactory.getButtonObject().getButton().click());
        }


        if (withSAMP) {
            var publishSAMP = new PublishSAMPButton(this.viewer);
            publishSAMP.setClickAction(()=>{
                sAMPPublisher.sendPNGSlice();
                sAMPPublisher.sendMarkers(markersFactory.getMarkersList());
            });
            setOnHubAvailabilityButtons(DOMAccessor.get2DSampConnection(),customControls,publishSAMP);
        }

        // Pressing x|X => remove the selected feature.
        let deleteButton = new DeleteButton(this.viewer);
        for(let key of deleteButton.getKeyboardMapping()){
            keyCodeProcessor.teach(key, () =>  { if (selector.isOpened()) selector.removeSelection() });
        }

        let axesFactory = new AxesFactory(viewer, FITS_HEADER.projectionType);
        axesFactory.build();
        axesFactory.getButtonObject().setClickAction(()=>{axesFactory.getButtonClick()});
        customControls.addButton(axesFactory.getButtonObject().getButton());
        for(let key of axesFactory.getButtonObject().getKeyboardMapping()){
            keyCodeProcessor.teach(key, () => axesFactory.getButtonObject().getButton().click());
        }

        let reset = new ResetButton(this.viewer);
        reset.setClickAction(()=>{viewer.reset()});
        customControls.addButton(reset.getButton());
        for(let key of reset.getKeyboardMapping()){
            keyCodeProcessor.teach(key, () => reset.getButton().click());
        }

        selector.getButtonObject().getButton().click()
        displaySlice(dataPaths.relFITSFilePath, 0, viewer, contoursFactoryGUI, sAMPPublisher);

        $('#rccap').click(function() {
            displaySlice(dataPaths.relFITSFilePath, 0, viewer, contoursFactoryGUI, sAMPPublisher);
        });

        exit;
    });
    exit();
});