/**
 * @file filterUtils.js Interface to ask information to OSD
 * @author Christophe Avenel
 * @see {@link filterUtils}
 */

/**
 * @namespace filterUtils
 * @property {Bool}   filterUtils._filtersUsed - 
 * @property {Object} filterUtils._filters - 
 * @property {Object} filterUtils._filterItems - 
 * @property {String} filterUtils._compositeMode - 
 */
 filterUtils = {
    // Choose between ["Brightness", "Exposure", "Hue", "Contrast", "Vibrance", "Noise", 
    //                 "Saturation","Gamma","Invert","Greyscale","Threshold","Erosion","Dilation",
    //                 "Colormap", "SplitChannel"]
    _filtersUsed: ["Saturation","Brightness","Contrast"],
    _filters: {
        "Color":{
            params:{
                type:"color",
                value:"100,100,100"
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        var valueRGB = value.split(",")
                        this.channels(
                            {red: valueRGB[0]-100, green: valueRGB[1]-100, blue: valueRGB[2]-100}
                        )
                        this.render(callback);
                    });
                }
            }
        },
        "Brightness":{
            params:{
                type:"range",
                min:-50,
                max:50,
                step:0.5,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.brightness(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Exposure":{
            params:{
                type:"range",
                min:-100,
                max:100,
                step:1,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.exposure(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Hue":{
            params:{
                type:"range",
                min:0,
                max:100,
                step:1,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.hue(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Contrast":{
            params:{
                type:"range",
                min:0,
                max:8,
                step:0.05,
                value:1
            },
            filterFunction: function (value) {
                if (value == 1) {  return function (context, callback) {callback();}}
                return OpenSeadragon.Filters.CONTRAST(value);
            }
        },
        "Vibrance":{
            params:{
                type:"range",
                min:-250,
                max:250,
                step:10,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.vibrance(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Noise":{
            params:{
                type:"range",
                min:0,
                max:100,
                step:10,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.noise(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Saturation":{
            params:{
                type:"range",
                min:-100,
                max:100,
                step:10,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.saturation(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Gamma":{
            params:{
                type:"range",
                min:0,
                max:2,
                step:0.1,
                value:1
            },
            filterFunction: function (value) {
                if (value == 1) {  return function (context, callback) {callback();}}
                return function(context, callback) {
                    Caman(context.canvas, function() {
                        this.gamma(value);
                        this.render(callback);
                    })
                }
            }
        },
        "Invert":{
            params:{
                type:"checkbox"
            },
            filterFunction: function () {
                return OpenSeadragon.Filters.INVERT();
            }
        },
        "Greyscale":{
            params:{
                type:"checkbox"
            },
            filterFunction: function () {
                return OpenSeadragon.Filters.GREYSCALE();
            }
        },
        "Threshold":{
            params:{
                type:"range",
                min:0,
                max:256,
                step:1,
                value:256
            },
            filterFunction: function (value) {
                if (value == 256) {  return function (context, callback) {callback();}}
                return OpenSeadragon.Filters.THRESHOLDING(value);
            }
        },
        "Erosion":{
            params:{
                type:"range",
                min:1,
                max:11,
                step:2,
                value:1
            },
            filterFunction: function (value) {
                if (value == 1) {  return function (context, callback) {callback();}}
                return OpenSeadragon.Filters.MORPHOLOGICAL_OPERATION(value, Math.min);
            }
        },
        "Dilation":{
            params:{
                type:"range",
                min:1,
                max:11,
                step:2,
                value:1
            },
            filterFunction: function (value) {
                if (value == 1) {  return function (context, callback) {callback();}}
                return OpenSeadragon.Filters.MORPHOLOGICAL_OPERATION(value, Math.max);
            }
        },
        "Colormap":{
            // Note: this filter will use the same colormaps as those for the markers
            params:{
                type:"select",
                options: ["None"].concat(dataUtils._d3LUTs.map(function(str) {
                    return str.replace("interpolate", "");
                })),
                value:"0"
            },
            filterFunction: function (value) {
                if (value == 0) { return function (context, callback) {callback();}}
                else {
                    let colorscaleName = dataUtils._d3LUTs[value - 1];
                    console.assert(colorscaleName != undefined);

                    let cmap = [];
                    for (let i = 0; i < 256; ++i) {
                        const color = d3[colorscaleName](i / 255.0);
                        const hexColor = glUtils._formatHex(color);  // D3 sometimes returns RGB strings
                        const r = Number("0x" + hexColor.substring(1,3));
                        const g = Number("0x" + hexColor.substring(3,5));
                        const b = Number("0x" + hexColor.substring(5,7));
                        cmap.push([r, g, b]);
                    }
                    return OpenSeadragon.Filters.COLORMAP(cmap, 128);
                }
            }
        },
        "SplitChannel":{
            params:{
                type:"select",
                options: ["None","R","G","B"],
                value:"None"
            },
            filterFunction: function (value) {
                if (value == 0) { return function (context, callback) {callback();}}
                else {
                    return function(context, callback) {
                        Caman(context.canvas, function() {
                            this.splitChannel(value);
                            this.render(callback);
                        });
                    }
                }
            }
        },
        "Min":{
            params:{
                type:"range",
                min:0,
                max:255,
                step:1,
                value:0
            },
            filterFunction: function (value) {
                if (value == 0) {  return function (context, callback) {callback();}}
                return RESCALE(value,filterUtils.getFilterParams('Max').value);
            }
        },
        "Max":{
            params:{
                type:"range",
                min:0,
                max:255,
                step:1,
                value:255
            },
            filterFunction: function (value) {
                if (value == 255) {  return function (context, callback) {callback();}}
                return RESCALE(filterUtils.getFilterParams('Min').value,value);
            }
        }
    },
    _filterItems:{},
    _lastFilters:{},
    _compositeMode:"source-over"
}
RESCALE = function(vmin,vmax) {
            if (vmin < 0 || vmax>255) {
                throw new Error('Contrast adjustment must be positive.');
            }
            var range = (vmax-vmin)/255
            var precomputedContrast = [];
            for (var i = 0; i < 256; i++) {
                var val = (i - vmin)/range;
                if (val<0){val=0};
                if (val>255){val=255};
                precomputedContrast[i] = val
            }
            return function(context, callback) {
                var imgData = context.getImageData(
                    0, 0, context.canvas.width, context.canvas.height);
                var pixels = imgData.data;
                for (var i = 0; i < pixels.length; i += 4) {
                    pixels[i] = precomputedContrast[pixels[i]];
                    pixels[i + 1] = precomputedContrast[pixels[i + 1]];
                    pixels[i + 2] = precomputedContrast[pixels[i + 2]];
                }
                context.putImageData(imgData, 0, 0);
                callback();
            };
        },
        
Caman.Filter.register("splitChannel", function (channelValue) {
    this.process("splitChannel", function (rgba) {
        let channel = null;
        if (channelValue == 1) {channel = rgba.r};
        if (channelValue == 2) {channel = rgba.g};
        if (channelValue == 3) {channel = rgba.b};
        rgba.r = channel;
        rgba.g = channel;
        rgba.b = channel;

        // Return the modified RGB values
        return rgba;
    });
});

/** 
 * Initialize list of filters
 *  */
 filterUtils.initFilters = function() {
    var settingsPanel = document.getElementById("filterSettings");
    if (!settingsPanel) return;
    for (var filter in filterUtils._filters) {
        selectParams = {
            eventListeners:{
                "change": function (e) {
                    filterName = e.srcElement.getAttribute("filter");
                    checked = e.srcElement.checked;
                    if (checked)
                        filterUtils._filtersUsed.push(filterName);
                    else 
                        filterUtils._filtersUsed = filterUtils._filtersUsed.filter(function(value, index, arr){ 
                            return value != filterName;
                        });
                    overlayUtils.addAllLayersSettings();
                    filterUtils.setRangesFromFilterItems();
                    filterUtils.getFilterItems();
                }
            },
            "class":"filterSelection",
            "checked":filterUtils._filtersUsed.filter(e => e === filter).length > 0,
            id:"filterCheck_" + filter,
            extraAttributes: {
                "filter": filter
            }
        }
        select = HTMLElementUtils.inputTypeCheckbox(selectParams);
        select.classList.add("form-check-input");
        settingsPanel.appendChild(select);
        var label = document.createElement("label");
        label.classList.add("form-check-label");
        label.setAttribute("for", "filterCheck_" + filter);
        label.innerHTML = "&nbsp;" + filter;
        var form = document.createElement("div");
        form.classList.add("form-check");
        form.appendChild(select);
        form.appendChild(label);
        settingsPanel.appendChild(form);
    }
    modeParams = {
        eventListeners:{
            "change": function (e) {
                compositeMode = e.srcElement.value;
                filterUtils._compositeMode = compositeMode;
                filterUtils.setCompositeOperation();
            }
        },
        id: "filterCompositeMode",
        options:[
            {text:"source-over", value:"source-over"},
            {text:"lighter", value:"lighter"},
            {text:"darken", value:"darken"},
            {text:"source-atop", value:"source-atop"},
            {text:"source-in", value:"source-in"},
            {text:"source-out", value:"source-out"},
            {text:"destination-over", value:"destination-over"},
            {text:"destination-atop", value:"destination-atop"},
            {text:"destination-in", value:"destination-in"},
            {text:"destination-out", value:"destination-out"},
            {text:"copy", value:"copy"},
            {text:"xor", value:"xor"},
            {text:"multiply", value:"multiply"},
            {text:"screen", value:"screen"},
            {text:"overlay", value:"overlay"},
            {text:"color-dodge", value:"color-dodge"},
            {text:"color-burn", value:"color-burn"},
            {text:"hard-light", value:"hard-light"},
            {text:"soft-light", value:"soft-light"},
            {text:"difference", value:"difference"},
            {text:"exclusion", value:"exclusion"},
            {text:"hue", value:"hue"},
            {text:"saturation", value:"saturation"},
            {text:"color", value:"color"},
            {text:"luminosity", value:"luminosity"}
        ]
    }
    var label = document.createElement("label");
    label.innerHTML = "Merging mode:&nbsp;";
    settingsPanel.appendChild(label);
    select = HTMLElementUtils.selectTypeDropDown(modeParams);
    select.classList.add("form-select", "form-select-sm");
    select.value = filterUtils._compositeMode;
    filterUtils.setCompositeOperation();
    settingsPanel.appendChild(select);
    filterUtils.getFilterItems();
}

/**
 * Get params for a given filter
 * @param {Number} filterName
 **/
filterUtils.getFilterParams = function(filterName) {
    filterParams = filterUtils._filters[filterName].params;
    filterParams.eventListeners = {
        "input": filterUtils.getFilterItems
    };
    filterParams["class"] = "filterInput";
    filterParams.filter = filterName;
    return filterParams;
}

/** 
 * Get function for a given filter 
 * @param {Number} filterName Index of an OSD tile source
 **/
filterUtils.getFilterFunction = function(filterName) {
    return filterUtils._filters[filterName].filterFunction;
}

/** 
 * apply all filters to all layers
 *  */
 filterUtils.applyFilterItems = function(calledItems) {
    var caman = Caman;
	caman.Store.put = function() {};

    var op = tmapp["object_prefix"];
    overlayUtils.waitLayersReady().then(() => {    
        if (JSON.stringify(filterUtils._lastFilters) == JSON.stringify(filterUtils._filterItems)) {
            return;
        }
        filters = [];
        for (const layer in filterUtils._filterItems) {
            processors = [];
            for(var filterIndex=0;filterIndex<filterUtils._filterItems[layer].length;filterIndex++) {
                processors.push(
                    filterUtils._filterItems[layer][filterIndex].filterFunction(filterUtils._filterItems[layer][filterIndex].value)
                );
            }
            filters.push({
                items: tmapp[op + "_viewer"].world.getItemAt(layer),
                processors: processors,
                toReset: JSON.stringify(filterUtils._lastFilters[layer]) != JSON.stringify(filterUtils._filterItems[layer])
            });
        };
        tmapp[op + "_viewer"].setFilterOptions({
            filters: filters,
            loadMode: "async"
        });
        for ( var i = 0; i < tmapp[op + "_viewer"].world._items.length; i++ ) {
            if (filterUtils._lastFilters[i]) {
                console.log(filters[i].toReset)
                if (! filters[i].toReset) {
                    continue;
                }
            }
            tmapp[op + "_viewer"].world._items[i].tilesMatrix={};
            tmapp[op + "_viewer"].world._items[i]._needsDraw = true;
        }
        filterUtils._lastFilters = {}
        Object.keys(filterUtils._filterItems).forEach(function(key, index) {
            filterUtils._lastFilters[key] = JSON.parse(JSON.stringify(filterUtils._filterItems[key]));
        });
    })
}

/** 
 * Set html ranges and checkboxes from filter items
 *  */
 filterUtils.setRangesFromFilterItems = function() {
    var op = tmapp["object_prefix"];
    for (const layer in filterUtils._filterItems) {
        for(var filterIndex=0;filterIndex<filterUtils._filterItems[layer].length;filterIndex++) {
            item = filterUtils._filterItems[layer][filterIndex];
            filterRange = document.querySelector('[filter="'+item.name+'"][layer="'+layer+'"]');
            if (filterRange) {
                if (filterRange.type == "range" || filterRange.type == "select-one")
                    filterRange.value = item.value;
                else if (filterRange.type == "checkbox")
                    filterRange.checked = item.value;
                if (filterRange.type == "color") {
                    function componentToHex(c) {
                        var hex = Math.floor(c*255/100).toString(16);
                        return hex.length == 1 ? "0" + hex : hex;
                    }
                    function rgbToHex(rgb) {
                        if (rgb == "0" || rgb == 0) {return "#FFFFFF";}
                        var array = rgb.split(',');
                        return "#" + componentToHex(array[0]) + componentToHex(array[1]) + componentToHex(array[2]);
                    }
                    console.log(item.value);
                    console.log("setRangesFromFilterItems", item.value, rgbToHex(item.value))
                    filterRange.value = rgbToHex(item.value);
                }
            }
        }
    };
}

/** 
 * Set html opacity and visibility from layers
 *  */
filterUtils.setOpacityFromLayers = async function() {
    var op = tmapp["object_prefix"];    
    tmapp.layers.forEach(function(layer, i) {
        if (tmapp[op + "_viewer"].world.getItemAt(i) != undefined) {
            let opacity = tmapp[op + "_viewer"].world.getItemAt(i).getOpacity();
            $("#opacity-layer-"+i).val((opacity != 0)?opacity:1);
            $("#visible-layer-"+i).prop('checked', opacity != 0);
        }
    });
}

/** 
 * Get filter functions and values from html ranges and checkboxes
 *  */
filterUtils.getFilterItems = function() {
    var op = tmapp["object_prefix"];
    
    overlayUtils.waitLayersReady().then(() => {    
        filterInputsRanges = document.getElementsByClassName("filterInput");
        items = {};
        for (i = 0; i < filterInputsRanges.length; i++) {
            var filterLayer = filterInputsRanges[i].getAttribute("layer");
            items[filterLayer] = []
        }
        for (i = 0; i < filterInputsRanges.length; i++) {
            var filterName = filterInputsRanges[i].getAttribute("filter");
            var filterLayer = filterInputsRanges[i].getAttribute("layer");
            var filterFunction = filterUtils.getFilterFunction(filterName);
            
            if (filterInputsRanges[i].type == "range" || filterInputsRanges[i].type == "select-one")
                inputValue = filterInputsRanges[i].value;
            else if (filterInputsRanges[i].type == "checkbox")
                inputValue = filterInputsRanges[i].checked;
            else if (filterInputsRanges[i].type == "color") {
                function hex2RGB(hex) {
                    const color = hex
                    const r = Math.floor(100*parseInt(color.substr(1,2), 16)/255)
                    const g = Math.floor(100*parseInt(color.substr(3,2), 16)/255)
                    const b = Math.floor(100*parseInt(color.substr(5,2), 16)/255)
                    console.log(hex, r+","+g+","+b);
                    return r+","+g+","+b
                }
                inputValue = hex2RGB(filterInputsRanges[i].value);
            }
            if (inputValue) {
                items[filterLayer].push(
                    {
                        filterFunction: filterFunction,
                        value: inputValue,
                        name: filterName
                    }
                );
            }
        }
        filterUtils._filterItems = items;
        filterUtils.applyFilterItems(items);
    })
}

filterUtils.setCompositeOperation = function() {
    var op = tmapp["object_prefix"];
    overlayUtils.waitLayersReady().then(() => {
        var filterCompositeMode = document.getElementById("filterCompositeMode");
        filterCompositeMode.value = filterUtils._compositeMode;
        tmapp[op + "_viewer"].compositeOperation = filterUtils._compositeMode;
        for (i = 0; i < tmapp[op + "_viewer"].world.getItemCount(); i++) {
            tmapp[op + "_viewer"].world.getItemAt(i).setCompositeOperation(filterUtils._compositeMode);
        }
    })
}

/** Create an HTML filter */
filterUtils.createHTMLFilter = function (params) {
    if (!params) {
        return null;
    }
    var type = params.type || null;
    if (type == "range") {
        filterInput = HTMLElementUtils.inputTypeRange(params);
    }
    else if (type == "checkbox") {
        filterInput = HTMLElementUtils.inputTypeCheckbox(params);
    }
    else if (type == "select") {
        filterInput = HTMLElementUtils.selectTypeDropDown(params);
    }
    else if (type == "color") {
        filterInput = HTMLElementUtils.inputTypeColor(params);
        console.log(params);
        if (params.value.includes(",")) {
            function componentToHex(c) {
                var hex = Math.floor(c*255/100).toString(16);
                return hex.length == 1 ? "0" + hex : hex;
            }
            function rgbToHex(rgb) {
                var array = rgb.split(',');
                return "#" + componentToHex(array[0]) + componentToHex(array[1]) + componentToHex(array[2]);
            }
            params.value = rgbToHex(params.value);
        }
    }
    if (params.value != undefined) {
        filterInput.setAttribute("value", params.value);
    }
    filterInput.setAttribute("layer", params.layer);
    filterInput.setAttribute("filter", params.filter);
    filterInput.setAttribute("id", "filterInput-" + params.filter + "-" + params.layer);
    if (type != "color") {
        filterInput.setAttribute("list", "filterDatalist-" + params.filter + "-" + params.layer);

        datalist = document.createElement("datalist");
        datalist.setAttribute("id", "filterDatalist-" + params.filter + "-" + params.layer);
        option = document.createElement("option");
        option.text = params.value;
        datalist.appendChild(option);

        filterInput.appendChild(datalist);
    }else {
        filterInput.setAttribute("list", "filterDatalist-" + params.filter + "-" + params.layer);

        datalist = document.createElement("datalist");
        datalist.setAttribute("id", "filterDatalist-" + params.filter + "-" + params.layer);
        const colors = ['#FFFFFF', '#FF0000', '#00FF00','#0000FF','#FF00FF','#FFFF00','#00FFFF',"#FF007F","#7F00FF","#007FFF","#00FF7F","#7FFF00","#FF7F00"];
        for (const element of colors) {
            option = document.createElement("option");
            option.text = element;
            datalist.appendChild(option);
        }
        

        filterInput.appendChild(datalist);
    }
    return filterInput;
}
