Source: public/javascript/modules/olqv_contours.js

import {ShapesFactory} from "./olqv_shapes.js";
import {createAndAppendFromHTML, str2FloatArray} from "./utils.js";
import {DOMAccessor} from "./domelements.js";
import { ServerApi } from "./serverApi.js";
import {ContoursButton} from "./olqv_olbuttons.js";

/** 
 * @classdesc A class to create and to manage contours in the 2D part of YAFITS. A contour is a close line joining pixels sharing a given value: the contour level.
 * Contours are created and manipulated with the {@link https://openlayers.org/en/v5.3.0/apidoc/|OpenLayers} API. In particular {@link https://openlayers.org/en/v5.3.0/apidoc/module-ol_Feature-Feature.html|Feature} and {@link https://openlayers.org/en/v5.3.0/apidoc/module-ol_geom_LineString-LineString.html| LineString} classes are used.
 *
 * This class only allows to create the contours as OpenLayers features and to define their behaviour in front of end user interactions.
 * How the contour levels are defined and how their edges are calculated are tasks accomplished by instances of the class {@link ContoursFactoryGUI}.
 *this.button = document.createElement("button");
 * @extends ShapesFactory
 */
class ContoursFactory extends ShapesFactory {

    static enter(what) {
        //console.group(this.name + "." + what);
    }

    static exit() {
       // console.groupEnd();
    }

 
    /**
     * Creates an instance
     * @param {Viewer} viewer instance of the {@link ./Viewer.html|Viewer} class that will host the contours.
     */
    constructor(viewer) {
        super(viewer, 'contour');
        ContoursFactory.enter(this.constructor.name);
        this.mode = this;
        this.layer.setZIndex(10);

        this.lastBox = null;

        this._buttonObject = new ContoursButton(viewer);
        /*this.button = document.createElement("button");
        this.button.setAttribute("type", "button");
        this.button.setAttribute("class", "btn btn-primary btn-sm");
        this.button.setAttribute("data-toggle", "modal");
        this.button.setAttribute("data-tooltip", "tooltip");
        this.button.setAttribute("title", "Work with contours");
        this.button.append("C");*/

        /*
        ** This the behaviour when a contour has been selected.
        */
        //let self = this;
        this.selector.select['LineString'] = (feature) => {
            ContoursFactory.enter("A contour is selected");

            var p = this.getMeasureContourPromise(feature, this.relFITSFilePath);
            p.then(
                result => this.selected(result),
                error => alert(error)
            );
            ContoursFactory.exit();
        };

        /*
        ** This the behaviour when a contour has been unselected.
        */
        this.selector.unselect['LineString'] = (feature) => {
            ContoursFactory.enter("A contour is unselected");
            this.unselected(feature);
            ContoursFactory.exit();
        };

        /*
        ** This the behaviour when a selected contour is going to be removed.
        */
        this.selector.remove['LineString'] = (f) => {
            ContoursFactory.enter("A selected contour is about to be removed");
            this.remove(f);
            ContoursFactory.exit();
        };

        ContoursFactory.exit(); // end of ctor
    }

    /**
     * Utility. Returns the toolbox button that will activate this instance.
     * @returns a {@link https://developer.mozilla.org/fr/docs/Web/HTML/Element/Button|button}
     */
    getButtonObject() {
        return this._buttonObject;
    }

    /*getButtonClick(){
        if(this.isVisible){
            this._buttonObject.getButton().classList.add("active");
        }else{
            this._buttonObject.getButton().classList.remove("active");
        }
    }*/

    box(feature) {
        console.log("box : entering");
        let br = feature.get("properties")["measurements"]["boundingRect"]["value"];
        let corners = [[br[0], br[1]],
        [br[0] + br[2] - 1, br[1]],
        [br[0] + br[2] - 1, br[1] + br[3] - 1],
        [br[0], br[1] + br[3] - 1],
        [br[0], br[1]]];

        this.lastBox = new ol.Feature({ geometry: new ol.geom.Polygon([corners]) });
        this.source.addFeature(this.lastBox);
        console.log("box : exiting");
    }

    grid(feature) {
        console.log("grid : entering");
        let br = feature.get("properties")["measurements"]["boundingRect"]["value"];
        let segments = new Array();
        for (i = 0; i < br[2]; i++) {
            segments.push(new ol.Feature({ geometry: new ol.geom.LineString([[br[0] + i, br[1]], [br[0] + i, br[1] + br[3] - 1]]) }));
        }
        for (i = 0; i < br[3]; i++) {
            segments.push(new ol.Feature({ geometry: new ol.geom.LineString([[br[0], br[1] + i], [br[0] + br[2] - 1, br[1] + i]]) }));
        }
        this.source.addFeatures(segments);
        console.log("grid : exiting");
    }

    /**
     * Physical measure are defined by a numerical value and a physical unit
     * @typedef PhysMeas
     * @property {number} value - the numerical value
     * @property {string} unit  - the physical unit 
     */

    /**
     * @typedef {Object} LineStringProperties
     * @property {PhysMeas} level - the level of the contour 
     * 
     */
 
    /**
     * Bounding rectangle.
     * @typedef BoundingRect
     * @property {number[4]} value - An array of four numbers representing in that order x, y, w and h.
     * @property {string} unit - The unit in which are expressed x, y, w and h. Must be "pixels" 
     */

    /**
     * Statistics on the area delimited by a contour.
     * @typedef ContourMeasurement
     * @property {PhysMeas} min - the minimum over the pixels values
     * @property {PhysMeas} max - the maximum over the pixel values
     * @property {PhysMeas} mean - the average of the pixel values
     * @property {PhysMeas} stdev - the standard deviation of the pixel values
     * @property {number} numpix - the number of pixels
     * @property {PhysMeas} "percentage of total number of pixels" - self explanatory ( note that unit in that case is "%" )
     * @property {BoundingRect} boundingRect - self explanatory
     * @param {*} yAFITSContours 
     */

    /** 
     * What is a contour as expected by importYAFITSContours.
     * @typedef {Object} LineStringFeature
     * @property {string} type - <b>Must be equal to "LineString"</b>
     * @property {number[][]} coordinates - an array of 2D coordinates
     * @property {LineStringProperties} properties - its properties
     */

    /**
     * Adds an array of LineStringFeature to the {@link ./Viewer.html|Viewer} and draws them.
     * 
     * @param {LineStringFeature[]} yAFITSContours - an array of {@link LineStringFeature}.
     */
    importYAFITSContours(yAFITSContours) {
        //ContoursFactory.enter(this.importYAFITSContours.name);
        this.clear();
        var features = new Array();
        yAFITSContours.forEach((contour) => {
            let coordinates = contour["coordinates"];
            coordinates.push(coordinates[0]);;
            let properties = contour["properties"];
            properties["type"] = "contour"
            let f = new ol.Feature({
                geometry: new ol.geom.LineString(coordinates),
                properties: properties
            });
            f.set("label", properties["level"]["value"].toExponential(3));
            f.setStyle(this.style_f(f));
            features.push(f);
        }
        );
        this.source.addFeatures(features);
        this.show();
        this.close();
        //ContoursFactory.exit()
    }

    /**
     * Promise - Define the behaviour when a contour is selected on the graphic interface.
     * @param {LineStringFeature} feature - The contour of area to measure
     * @param {string} relFITSFilePath - the path to the FITS file of interest. 
     * @returns 
     */
    getMeasureContourPromise(feature, relFITSFilePath) {
        ContoursFactory.enter(this.getMeasureContourPromise.name);
        var p = new Promise(function (resolve, reject) {
            var coordinates = feature.getGeometry().getCoordinates();
            let properties = feature.get("properties");
            let level = properties["level"]["value"].toExponential(3);

            // If the contour has already been measured then victory ...
            if ("measurements" in properties) {
                resolve(feature);
            }
            // else measure it,asynchronously, and victory...or defeat
            else {
                DOMAccessor.showLoaderAction(true);
                let serverApi = new ServerApi();
                serverApi.measureContours(0, JSON.stringify(coordinates), level, relFITSFilePath, (resp)=>{
                    if (resp["status"] == false) {
                        reject(`Something went wrong with the measurements of contour. The message was '${resp["message"]}'`)
                    } else {
                        properties["measurements"] = resp["result"];
                        feature.set("properties", properties);
                        resolve(feature);
                    }
                });
            }
        });
        ContoursFactory.exit();
        return p;
    };

    selected(contour) {
        ContoursFactory.enter(this.selected.name);
        /*let properties = contour.get("properties");
        let level = properties["level"]["value"].toExponential(3);
        let levelUnit = properties["level"]["unit"];
        let title = 'Contour level at ' + level + ' ' + levelUnit;
 
        this.infosBlock.headline(title);
        this.infosBlock.populate(title, properties["measurements"]);*/
        

        super.selected(contour);
        ContoursFactory.exit()
    }

    open(interaction, cursor) {
        ContoursFactory.enter(this.open.name);
        super.open(interaction, cursor);
        ContoursFactory.exit();
    }
}; // End of class ContoursFactory

/**
 * @class
 * A complete GUI to define the contours levels and trigger their drawing (see {@link ContoursFactory}) once they are defined.
 * The GUI is implemented as a modal whose HTML content is defined in the constructor.
 * It provides the user with :
 * <ul>
 * <li> an interactive graphic representation of the pixels values histogram </li>
 * <li> a text input area where contours levels numerical values can be entered/edited </li>
 * <li> a interactive graphic representation of the pixel values cumulative distribution </li>
 * <li> a text input area where quantiles can be entered/edited </li>
 * </ul>
 * Both graphics representations are clickable and can be used to define contours levels.
 * Graphics are done with {@link https://www.highcharts.com/ HighCharts}
 */
class ContoursFactoryGUI {
    static enter(what) {
        //console.group(this.name + "." + what);
    }

    static exit() {
        //console.groupEnd();
    }

    static interpolate(x, X, Y, i0, i1) {
        ContoursFactoryGUI.enter(ContoursFactoryGUI.interpolate.name);
        let result;
        if ((i1 - i0) === 0) {
            result = Y[i0];
        }
        else if ((i1 - i0) === 1 ) {
            result = (x - X[i0]) * (Y[i1] - Y[i0]) / (X[i1] - X[i0]) + Y[i0]
        }
        else {
            let imid = Math.floor((i0 + i1) / 2);
            if (x < X[imid]) {
                i1 = imid;
            }
            else {
                i0 = imid;
            }
            result = ContoursFactoryGUI.interpolate(x, X, Y, i0, i1);
        }
        ContoursFactoryGUI.exit();
        return result        
    }

    /**
     * Creates an instance.
     * @param {Viewer} viewer - The Viewer that hosts the contours graphics representations. 
     * @param {string} relFITSFilePath - The path to the FITS file of interest.
     */
    constructor(viewer, relFITSFilePath) {
        ContoursFactoryGUI.enter(this.constructor.name);
        this.viewer = viewer;
        this.modalContoursFormId = `ModalContoursForm-${viewer.getDivId()}`;
        this.collapseHistogramId = `collapseHistogram-${viewer.getDivId()}`;
        this.collapseCumulDistId = `collapseCumulDist-${viewer.getDivId()}`;
        this.cumulDistId = `cumulDist-${viewer.getDivId()}`;
        this.histogramId = `histogram-${viewer.getDivId()}`;
        this.infosBlock = viewer.infosBlock;

        this.html = `
<div id="${this.modalContoursFormId}" class="modal fade">
  <div class="modal-dialog modal-dialog-scrollable" role="document">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title">Contours</h1>
      </div>
      <div class="modal-body">
        <form role="form" method="POST" action="">
          <input type="hidden" name="_token" value="">

          <div class="form-group">
            <a class="btn btn-primary" data-toggle="collapse" href="#${this.collapseHistogramId}" role="button"
              aria-expanded="false" aria-controls="collapseExample">
              Use the histogram to choose the contours levels...
            </a>
            <!-- <label class="control-label">Use the histogram to choose the contours levels...</label> -->
            <div class="collapse" id="${this.collapseHistogramId}">
              <div id="${this.histogramId}" style="height: 400px">
              </div>
            </div>
            <label class="control-label">Enter contour level[s] (comma separated if more than one)</label>
            <div>
              <input class="form-control levels"></input>
            </div>
          </div>
          <div class="form-group">
            <a class="btn btn-primary" data-toggle="collapse" href="#${this.collapseCumulDistId}" role="button"
              aria-expanded="false" aria-controls="collapseExample">
              ...or use the cumulative distribution function to choose the quantiles...
            </a>
            <div class="collapse" id="${this.collapseCumulDistId}">
              <div id="${this.cumulDistId}" style="height: 400px">
              </div>
            </div>
            <label class="control-label">Enter cumulative distribution function value[s] (comma separated if more than
              one)</label>
            <div>
              <input class="form-control text-of-quantiles"></input>
            </div>
          </div>
        </form>
        <div class="row">
            <div class="col">
                <div class="btn-toolbar" role="group" aria-label="Markers actions">
                <button type="button" class="btn btn-success update-contours btn-sm ml-1">Draw contours</button>
                <button type="button" class="btn btn-success clear-contours btn-sm ml-1">Clear</button>
                <button type="button" class="btn btn-success reset-contours btn-sm ml-1">Reset to default</button>
                <button type="button" class="btn btn-primary btn-sm ml-1" data-dismiss="modal">Close</button>
            </div>
        </div>
      </div>
    </div>
  </div>
</div>
`;
        this.modal = createAndAppendFromHTML(this.html, this.viewer.getMap().getTargetElement());
        this.text_of_quantiles = $(this.modal.querySelector(".text-of-quantiles"));
        this.levels = $(this.modal.querySelector(".levels"));
        this.defaultValue;

        this.update_contours = $(this.modal.querySelector(".update-contours"));
        let self = this;

        function call_contours(event){
            self.queryYAFITSContours(event.data.relFITSFilePath);
        }
        this.update_contours.click({relFITSFilePath : relFITSFilePath},call_contours);

        this.levels.keyup(function (event) {
            if (event.keyCode === 13) {
                event.preventDefault(); /* Not sure this is useful */
                this.queryYAFITSContours(relFITSFilePath);
            }
        }.bind(this));

        this.text_of_quantiles.keyup(function (event) {
            if (event.keyCode === 13) {
                event.preventDefault(); /* Not sure this is useful */
                this.queryYAFITSContours(relFITSFilePath);
            }
        }.bind(this));

        this.clear_contours = $(this.modal.querySelector(".clear-contours"));
        this.clear_contours.click(this.clear.bind(this));

        this.reset_contours = $(this.modal.querySelector(".reset-contours"));
        this.reset_contours.click(this.reset.bind(this));

        this.contoursFactory = undefined;
        this.contoursMethod = undefined;

        this.levels.focus(() => { this.contoursMethod = "levels" });
        this.text_of_quantiles.focus(() => { this.contoursMethod = "quantiles" });
        

        $(this.modal).on("shown.bs.modal", this.shown.bind(this));
        $(this.modal).on("hidden.bs.modal", this.hidden.bind(this));
        ContoursFactoryGUI.exit();
    }

    /**
     * Connect the GUI to a {@link ContoursFactory}
     * @param {ContoursFactory} contoursFactory - the instance of {@link ContoursFactory} where the contours will be drawn.
     */
    connect(contoursFactory) {
        ContoursFactoryGUI.enter(this.connect.name);
        this.contoursFactory = contoursFactory;
        this.contoursFactory.getButtonObject().getButton().setAttribute("data-target", `#${this.modalContoursFormId}`);
        ContoursFactoryGUI.exit();
    }

    /**
     * Clear the contents of the GUI input elements
     */
    clear() {
        ContoursFactoryGUI.enter(this.clear.name);
        this.text_of_quantiles.val("");
        this.levels.val("");
        this.contoursFactory.clear();
        this.infosBlock.clear("");
        ContoursFactoryGUI.exit();
    }


    /**
     * Reset the contents of the GUI input elements
     */
     reset() {
        ContoursFactoryGUI.enter(this.reset.name);
        this.clear();
        if(this.defaultValue !== undefined){
            this.levels.val(this.defaultValue);
            this.contoursMethod = "levels";
        }

        ContoursFactoryGUI.exit();
    }

    shown() {
        ContoursFactoryGUI.enter(this.shown.name);
        this.contoursFactory.open(this.viewer.defaultInteraction, this.viewer.defaultCursor);
        this.drawThings();
        ContoursFactoryGUI.exit();
    }

    hidden() {
        ContoursFactoryGUI.enter(this.hidden.name);
        this.contoursFactory.close();
        document.activeElement.blur()
        ContoursFactoryGUI.exit();
    }

    /**
     * Draw everything, i.e. histogram and cumulative distribution
     */
    drawThings() {
        ContoursFactoryGUI.enter(this.drawThings.name);
        this.drawHistogram();
        this.drawCumulativeDistribution();
        ContoursFactoryGUI.exit();
    }

    /**
     * Draw histogram
     */
    drawHistogram() {
        ContoursFactoryGUI.enter(this.drawHistogram.name);
        let histogram = this.viewer.getStatistics()["histogram"];
        let population = histogram[0];
        let bins = histogram[1];
        let data = new Array(2 * population.length);
        for (let i = 0; i < population.length; i++) {
            data[i] = [bins[i], population[i]];
            data[i + 1] = [bins[i + 1], population[i]];
        }

        let mean = this.viewer.getStatistics()["mean"]
        let stdev = this.viewer.getStatistics()["stdev"]

        let that = this;
        Highcharts.chart(`${this.histogramId}`, {
            chart: {
                type: 'line',
                zoomType: 'x',
                panning: true,
                panKey: 'shift'
            },
            title: {
                text: "Histogram of pixels values"
            },
            xAxis: {
                title: { text: "Pixels values" },
                minPadding: 0.05,
                maxPadding: 0.05
            },
            yAxis: {
                title: { text: "Population" }
            },

            plotOptions: {
                series: {
                    cursor: 'pointer',
                    point: {
                        events: {
                            click: function (e) {
                                if (that.hasLevelsDefined())
                                    that.levels.val(that.levels.val() + "," + e.point.x);
                                else
                                    that.levels.val(e.point.x);
                                that.levels.focus();
                            }
                        }
                    },
                    marker: {
                        lineWidth: 2
                    }
                }
            },
            series: [
                {
                    name: 'population per bin',
                    type: 'line',
                    data: data
                },

                {
                    name: 'mean',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean, 0],
                        [mean, Math.max(...population)]
                    ]
                },
                {
                    name: '+sigma',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean + stdev, 0],
                        [mean + stdev, Math.max(...population)]
                    ]
                },
                {
                    name: '+2*sigma',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean + 2 * stdev, 0],
                        [mean + 2 * stdev, Math.max(...population)]
                    ]
                },
                {
                    name: '+3*sigma',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean + 3 * stdev, 0],
                        [mean + 3 * stdev, Math.max(...population)]
                    ]
                }
            ]
        });
        ContoursFactoryGUI.exit();
    }

    /**
     * Draw cumulative distribution.
     */
    drawCumulativeDistribution() {
        ContoursFactoryGUI.enter(this.drawCumulativeDistribution.name);
        let histogram = this.viewer.getStatistics()["histogram"];
        let bins = histogram[1];
        let cumulDist = this.viewer.getStatistics()["cumuldist"];
        let data = new Array(cumulDist.length);
        for (let i = 0; i < data.length; i++) {
            data[i] = [bins[i], cumulDist[i]];
        }
        let mean = this.viewer.getStatistics()["mean"]
        let stdev = this.viewer.getStatistics()["stdev"]
        let that = this;
        let cumul_mean = ContoursFactoryGUI.interpolate(mean, bins, cumulDist, 0, data.length - 2);
        let cumul_stdev = ContoursFactoryGUI.interpolate(mean + stdev, bins, cumulDist, 0, data.length - 2);
        let cumul_two_stdev = ContoursFactoryGUI.interpolate(mean + 2 * stdev, bins, cumulDist, 0, data.length - 2);
        let cumul_three_stdev = ContoursFactoryGUI.interpolate(mean + 3 * stdev, bins, cumulDist, 0, data.length - 2);

        Highcharts.chart(`${this.cumulDistId}`, {
            chart: {
                type: 'line',
                zoomType: 'x',
                panning: true,
                panKey: 'shift'
            },
            title: {
                text: "Cumulative distribution function"
            },
            xAxis: {
                title: { text: "Pixels values" },
                minPadding: 0.05,
                maxPadding: 0.05
            },
            yAxis: {
                title: { text: "Cumulative distribution" }
            },
            plotOptions: {
                series: {
                    cursor: 'pointer',
                    point: {
                        events: {
                            click: function (e) {
                                if (that.hasQuantilesDefined())
                                    that.text_of_quantiles.val(that.text_of_quantiles.val() + "," + e.point.y);
                                else
                                    that.text_of_quantiles.val(e.point.y);
                                that.text_of_quantiles.focus();
                            }
                        }
                    },
                    marker: {
                        lineWidth: 1
                    }
                }
            },
            series: [
                {
                    name: "cumulative distribution",
                    type: "line",
                    data: data
                },
                {
                    name: 'mean',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean, 0.],
                        [mean, cumul_mean]
                    ]
                },
                {
                    name: '+sigma',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean + stdev, 0],
                        [mean + stdev, cumul_stdev]
                    ]
                },
                {
                    name: '+2*sigma',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean + 2 * stdev, 0],
                        [mean + 2 * stdev, cumul_two_stdev]
                    ]
                },
                {
                    name: '+3*sigma',
                    type: 'line',
                    dashStyle: "shortdot",
                    lineWidth: 2,
                    data: [
                        [mean + 3 * stdev, 0],
                        [mean + 3 * stdev, cumul_three_stdev]
                    ]
                }
            ]
        });
        ContoursFactoryGUI.exit();
    }

    /**
     * Checks if the levels input area has values (true) or not (false)
     * @returns {boolean}
     * 
     */
    hasLevelsDefined() {
        var result = this.levels.val().trim() != "";
        return result;
    }

    /**
     * Checks if the quantiles input area has values (true) or not (false)
     * @returns {boolean}
     * 
     */
    hasQuantilesDefined() {
        var result = this.text_of_quantiles.val().trim() != "";
        return result;
    }

    /**
     * Parse the content of the levels text input area into an array of numbers
     * @returns {number[]|false}
     */
    parseTextOfLevels() {
        ContoursFactoryGUI.enter(this.parseTextOfLevels.name);
        let result = false;
        let text = this.levels.val();
        let x = str2FloatArray(text);
        if (x) {
            result = x.sort();
            this.levels.val(result);
        }
        else {
            alert("Input invalid to express a sequence of levels.");
        }
        ContoursFactoryGUI.exit();
        return result;
    }

    /**
     * Parse the content of the quantiles text input area into an array of numbers
     * @returns {number[]|false}
    */
    parseTextOfQuantiles() {
        ContoursFactoryGUI.enter(this.parseTextOfQuantiles.name);
        let result = false;
        let text = this.text_of_quantiles.val();
        let x = str2FloatArray(text, [0, 1]);
        if (x) {
            result = x.sort();
            this.text_of_quantiles.val(result);
        }
        else {
            alert("Input invalid to express a sequence of quantiles. It must be a ascending sequence of numbers in [0., 1.]");
        }
        ContoursFactoryGUI.exit();
        return result;
    }

    /**
     * When contours are calculated from their levels values.
     * @typedef {Object} ContoursLevelsPostParams
     * @property {number} iFreq - the index of the RA/DEC plane of interest.
     * @property {number[]} levels - the array of levels numerical values.
     * 
     */
     
    /**
     * When contours are calculated from their quantiles values.
     * @typedef {Object} ContoursQuantilesPostParams
     * @property {number} iFreq - the index of the RA/DEC plane of interest.
     * @property {number[]} quantiles - the array of quantiles numerical values.
     */

    /**
     * Union of ContoursLevelsPostParams and ContoursQuantilesPostParams
     * @typedef {ContoursLevelsPostParams | ContoursQuantilesPostParams} ContoursOptParams
     */
     
    
     /** 
      * How is structured the parameter sent to the contours lines calculator.
      * @typedef {Object} ContoursPostParams
      * @property {string} relFITSFilePath - the path to the FITS file of interest
      * @property {string} optParams - JSON representation of a {@link ContoursOptParams}
     */

    /**
     * Promise. What has to be done to obtain the contours lines given their levels|quantiles.
     * @param {ContoursPostParams} postParams 
     * @returns {LineStringFeature[]} 
     */
    getContoursPromise(postParams) {
        ContoursFactoryGUI.enter(this.getContoursPromise.name);
        let serverApi = new ServerApi();
        var p = new Promise(function (resolve, reject) {
            DOMAccessor.showLoaderAction(true);
            serverApi.getContours(postParams, (resp)=>{
                if (resp["status"] == false) {
                    reject(`Error 2 : Something went wrong during the generation of the contours. The message was '${resp["message"]}'`);
                }
                else {
                    resolve(resp["result"]["features"]);
                }                
            })
            })
        ContoursFactoryGUI.exit();
        return p;
    }

    /**
     * Obtain the contours lines corresponding to the choosen levels or quantiles and trigger their drawings performed by the connected instance of {@link ContoursFactory}.
     * @param {string} relFITSFilePath - The path to the FITS file of interest.
     */
    queryYAFITSContours(relFITSFilePath) {
        ContoursFactoryGUI.enter(this.queryYAFITSContours.name);
        DOMAccessor.showLoaderAction(true);
        // Actually the returned value is reduced to a single integer.
        let iFREQ = this.viewer.getSliceRange(); 
        let postParams;
        var self = this;
        // Build the parameters to accompany the POST, depends on the choosen contours calculation method.
        switch (this.contoursMethod) {
            case "levels":
                if (this.hasLevelsDefined()) {
                    let levels = this.parseTextOfLevels();
                    if (levels) {
                        postParams = { 'relFITSFilePath': relFITSFilePath, 'optParams': JSON.stringify({ 'iFREQ': iFREQ, 'levels': levels }, 0, 4) }
                    }
                }
                else {
                    alert("No levels defined");
                }
                break;

            case "quantiles":
                if (this.hasQuantilesDefined()) {
                    let quantiles = this.parseTextOfQuantiles();
                    if (quantiles) {
                        postParams = { 'relFITSFilePath': relFITSFilePath, 'optParams': JSON.stringify({ 'iFREQ': iFREQ, 'quantiles': quantiles }, 0, 4) }
                    }
                }
                else {
                    alert("No quantiles defined");
                }
                break;

            default:
                alert("No contours method defined");
        }
        
        // Trigger the contours calculation promise. On positive return draw the contours.
        if (postParams) {
            var p = this.getContoursPromise(postParams);
            p.then (
                result => {
                    self.contoursFactory.importYAFITSContours(result);
                    $(`#${self.modalContoursFormId}`).modal("hide");
                },
                error => {
                    alert(`Error 1 : Something went wrong during the generation of the contours. The message was "${error}"`);
                }
            )
        }else{
            DOMAccessor.showLoaderAction(false);
        }
        ContoursFactoryGUI.exit();
    }

    drawYAFITSContoursByLevels(levels, relFITSFilePath) {
        this.levels.val(levels.join(","));
        this.contoursMethod = "levels";
        this.queryYAFITSContours(relFITSFilePath);
    }

    setDefaultValue(levels){
        this.levels.val(levels.join(","));
        this.contoursMethod = "levels";
        this.defaultValue = levels.join(",");
    }

}; // End of class ContoursFactoryGUI


export {
    ContoursFactory, ContoursFactoryGUI
}