const LayerList = (() => {
    let layerList = document.getElementById("layers-menu");
    // let layerListEntry = layerList.firstElementChild;
    let layerListEntry = document.getElementById("default-layer-list-item");
    let renamingLayer = false;
    let dragStartLayer;
    Events.on("mousedown", layerList, openOptionsMenu);
    Events.on('click',"add-layer-button", addLayerClick, false);
    Events.on('click',"add-reference-button", addReferenceClick, false);
    Events.onCustom("switchedToAdvanced", showLayerList);
    Events.onCustom("switchedToBasic", hideLayerList);

    new Sortable(layerList, {
        animation: 100,
        filter: ".layer-button",
        draggable: ".layers-menu-entry",
        onStart: layerDragStart,
        onEnd: layerDragEnd
    });
    function showLayerList() {
        layerList.style.display = "inline-block";
        document.getElementById('layer-button').style.display = 'inline-block';
    }
    function hideLayerList() {
        if (EditorState.documentCreated()) {
            currFile.currentLayer.selectLayer();
            flatten(true);
        }
        layerList.style.display = "none";
        document.getElementById('layer-button').style.display = 'none';
    }
    function addReferenceClick(id, saveHistory = true, layerName) {
        addLayer(...arguments);
        currFile.layers[currFile.layers.length-1].selectLayer();
    }
    function addLayerClick(id, saveHistory = true, layerName) {
        addLayer(...arguments);
        currFile.layers[currFile.layers.length-1].selectLayer();
    }
    function addLayer(id, saveHistory = true, layerName) {
        let index = currFile.layers.length;
        // Creating a new canvas
        let newCanvas = document.createElement("canvas");
        // Setting up the new canvas
        currFile.canvasView.append(newCanvas);
        newCanvas.classList.add("drawingCanvas");
    
        if (!layerListEntry) return //console.warn('skipping adding layer because no document');
    
        // Clone the default layer
        let toAppend = layerListEntry.cloneNode(true);
        toAppend.style.display = "flex";
        //console.log('toAppend === ',toAppend);
        // Setting the default name for the layer
        const _layerName = layerName ?? "Layer " + currFile.layers.length;
        //console.log('_layerName === ',_layerName);
        toAppend.getElementsByTagName('p')[0].innerHTML = _layerName;
        // Removing the selected class
        toAppend.classList.remove("selected-layer");
        // Adding the layer to the list
        Layer.layerCount++;
    
        // Creating a layer object
        let newLayer = new Layer(currFile.canvasSize[0], currFile.canvasSize[1], newCanvas, toAppend);
        newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle;
        newLayer.context.willReadFrequently = true;
        newLayer.copyData(currFile.currentLayer);
    
        // currFile.layers.splice(index, 0, newLayer);
        currFile.layers.push(newLayer);
        
        // Insert it before the Add layer button
        layerList.insertBefore(toAppend, document.getElementById("add-layer-li"));
    
        if (id != null && typeof(id) == "string") {
            newLayer.setID(id);
        }
        // Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history
        if (saveHistory) {
            new HistoryState().AddLayer(newLayer, index);
            if(FileManager.cacheEnabled)FileManager.localStorageSave();
        }

        currFile.layers.forEach((layer, i) => {
            const _i = currFile.layers.length - i;
            layer.canvas.style.zIndex = (_i+1) * 10;
        })
    
        return newLayer;
    }
    /** Merges topLayer onto belowLayer
    * 
    * @param {*} belowLayer The layer on the bottom of the layer stack
    * @param {*} topLayer The layer on the top of the layer stack
    */
    function mergeLayers(belowLayer, topLayer) {
        // Copying the above content on the layerBelow
        let belowImageData = belowLayer.getImageData(0, 0, belowLayer.canvas.width, belowLayer.canvas.height);
        let toMergeImageData = topLayer.getImageData(0, 0, topLayer.canvas.width, topLayer.canvas.height);

        for (let i=0; i<belowImageData.data.length; i+=4) {
            let currentMovePixel = [
                toMergeImageData.data[i], toMergeImageData.data[i+1], 
                toMergeImageData.data[i+2], toMergeImageData.data[i+3]
            ];

            let currentUnderlyingPixel = [ //TODO: I'd be curious to know if this array slows this function down
                belowImageData.data[i], belowImageData.data[i+1], 
                belowImageData.data[i+2], belowImageData.data[i+3]
            ];

            if (Util.isPixelEmpty(currentMovePixel)) {
                if (!Util.isPixelEmpty(belowImageData)) {
                    toMergeImageData.data[i] = currentUnderlyingPixel[0];
                    toMergeImageData.data[i+1] = currentUnderlyingPixel[1];
                    toMergeImageData.data[i+2] = currentUnderlyingPixel[2];
                    toMergeImageData.data[i+3] = currentUnderlyingPixel[3];
                }
            }
        }

        // Putting the top data into the belowdata
        belowLayer.putImageData(toMergeImageData, 0, 0);
    }
    /** Sets the z indexes of the layers when the user drops the layer in the menu
     * 
     * @param {*} event 
     */
    function layerDragEnd(event) {
        Events.simulateMouseEvent(window, "mouseup");
        
        const tempLayerCache = currFile.layers.reduce((r,n,i) => {
            r[n.id] = n;
            return r;
        },{});

        let selectedId;
        const idArr = [...document.querySelectorAll(".layers-menu-entry")].map(elm => {
            if([...elm.classList].includes("selected-layer")) {
                selectedId = elm.id;
            }
            return elm.id;
        });
        let selectedIdx = idArr.indexOf(selectedId);
        idArr.forEach((id,i)=>{
            currFile.layers[i] = tempLayerCache[id];
            currFile.layers[i].isSelected = i===selectedIdx;
        });

        currFile.layers.forEach((layer, i) => {
            const _i = currFile.layers.length - i;
            layer.canvas.style.zIndex = (_i+1) * 10;
        });
        if(FileManager.cacheEnabled)FileManager.localStorageSave();

    }
    /** Saves the layer that is being moved when the dragging starts
     * 
     * @param {*} event 
     */
    function layerDragStart(event) {
        dragStartLayer = getLayerByID(layerList.children[event.oldIndex].id);
    }
    function getLayerByID(id) {
        let ret;
        for (let i=0; i<currFile.layers.length; i++) {
            if (currFile.layers[i].hasCanvas()) {
                if (currFile.layers[i].menuEntry.id == id) {
                    ret = currFile.layers[i];
                }
            }
        }
        return ret ?? null;
    }
    function getLayerByName(name) {
        for (let i=0; i<currFile.layers.length; i++) {
            if (currFile.layers[i].hasCanvas()) {
                if (currFile.layers[i].menuEntry.getElementsByTagName("p")[0].innerHTML == name) {
                    return currFile.layers[i];
                }
            }
        }

        return null;
    }
    function startRenamingLayer(event) {
        let p = currFile.currentLayer.menuEntry.getElementsByTagName("p")[0];
    
        currFile.currentLayer.oldLayerName = p.innerHTML;
    
        p.setAttribute("contenteditable", true);
        p.classList.add("layer-name-editable");
        p.focus();
        Events.simulateInput(65, true, false, false);
    
        renamingLayer = true;
    }
    function duplicateLayer(event, saveHistory = true) {
    
        let layerIndex = currFile.layers.indexOf(currFile.currentLayer);
    
        // Creating a new canvas
        let newCanvas = document.createElement("canvas");
        // Setting up the new canvas
        currFile.canvasView.append(newCanvas);
        newCanvas.classList.add("drawingCanvas");
    
        if (!layerListEntry) return //console.warn('skipping adding layer because no document');
    
        // Clone the default layer
        let toAppend = currFile.currentLayer.menuEntry.cloneNode(true);
        // Setting the default name for the layer
        toAppend.getElementsByTagName('p')[0].innerHTML += " copy";
        // Removing the selected class
        toAppend.classList.remove("selected-layer");
        // Adding the layer to the list
        Layer.layerCount++;
    
        // Creating a layer object
        let newLayer = new Layer(currFile.canvasSize[0], currFile.canvasSize[1], newCanvas, toAppend);
        newLayer.context.fillStyle = currFile.currentLayer.context.fillStyle;
        newLayer.copyData(currFile.currentLayer);
    
        currFile.layers.splice(layerIndex, 0, newLayer);
        
        // Insert it before the Add layer button
        layerList.insertBefore(toAppend, currFile.currentLayer.menuEntry);
    
        // Copy the layer content
        newLayer.context.putImageData(currFile.currentLayer.context.getImageData(
            0, 0, currFile.canvasSize[0], currFile.canvasSize[1]), 0, 0);
        newLayer.updateLayerPreview();

        LayerList.refreshZ();

        // Basically "if I'm not adding a layer because redo() is telling meto do so", then I can save the history
        if (saveHistory) {
            new HistoryState().DuplicateLayer(newLayer, currFile.currentLayer);
        }
    }
    function clearLayers() {
        
        currFile.layers.forEach(()=>deleteLayer());
        //console.log('currFile.layers.length === ',currFile.layers.length);
        for(let i = 0; i < currFile.layers.length;i++){
            const layer = currFile.layers[i];
            //console.log('i === ', i);
            //console.log('layer === ',layer);
        }
    }
    function deleteLayer(saveHistory = true) {
        //console.log('deleting layer: ', currFile.currentLayer.name, currFile.currentLayer);
        //console.trace();
        deleteLayerDirectly(currFile.currentLayer, saveHistory);
    
        // Closing the menu
        closeOptionsMenu();
    }
    function deleteLayerDirectly(layer, saveHistory = true) {
        let layerIndex = currFile.layers.indexOf(layer);
        let toDelete = currFile.layers[layerIndex];
        let previousSibling = toDelete.menuEntry.previousElementSibling;
        // Adding the ids to the unused ones
        Layer.unusedIDs.push(toDelete.id);

        if(layer.isSelected) {
            // Selecting the nearest layer
            const nearestLayer = (currFile.layers[layerIndex + 1] ?? currFile.layers[layerIndex - 1]);
            if(nearestLayer){
                nearestLayer.selectLayer();
                //console.log('changing to nearest layer');
            }
        }

        // Deleting canvas and entry
        toDelete.canvas.remove();
        toDelete.menuEntry.remove();

        // Removing the layer from the list
        currFile.layers.splice(layerIndex, 1);

        if (saveHistory) {
            new HistoryState().DeleteLayer(toDelete, previousSibling, layerIndex);
        }
    }
    function merge(saveHistory = true) {
        // Saving the layer that should be merged
        let toMerge = currFile.currentLayer;
        let toMergeIndex = currFile.layers.indexOf(toMerge);
        // Getting layer below
        let layerBelow = LayerList.getLayerByID(currFile.currentLayer.menuEntry.nextElementSibling.id);
    
        // If I have something to merge with
        if (layerBelow != null) {
            // Selecting that layer
            layerBelow.selectLayer();
    
            if (saveHistory) {
                new HistoryState().MergeLayer(toMergeIndex, toMerge,
                    layerBelow.context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]),
                    layerBelow);
            }
    
            LayerList.mergeLayers(currFile.currentLayer.context, toMerge.context);
    
            // Deleting the above layer
            toMerge.canvas.remove();
            toMerge.menuEntry.remove();
            currFile.layers.splice(toMergeIndex, 1);
    
            // Updating the layer preview
            currFile.currentLayer.updateLayerPreview();
        }
    }
    function flatten(onlyVisible) {
        if (!onlyVisible) {
            // Selecting the first layer
            let firstLayer = layerList.firstElementChild;
            let nToFlatten = layerList.childElementCount - 1;
            LayerList.getLayerByID(firstLayer.id).selectLayer();
    
            for (let i = 0; i < nToFlatten; i++) {
                merge();
            }
    
            new HistoryState().FlattenAll(nToFlatten);
        }
        else {
            // Getting all the visible layers
            let visibleLayers = [];
            let nToFlatten = 0;
    
            for (let i=0; i<currFile.layers.length; i++) {
                if (currFile.layers[i].hasCanvas() && currFile.layers[i].isVisible) {
                    visibleLayers.push(currFile.layers[i]);
                }
            }
    
            // Sorting them by z-index
            visibleLayers.sort((a, b) => (a.canvas.style.zIndex > b.canvas.style.zIndex) ? -1 : 1);
            // Selecting the last visible layer (the only one that won't get deleted)
            visibleLayers[visibleLayers.length - 1].selectLayer();
    
            // Merging all the layer but the last one
            for (let i=0; i<visibleLayers.length - 1; i++) {
                nToFlatten++;
                
                new HistoryState().FlattenTwoVisibles(
                    visibleLayers[i + 1].context.getImageData(0, 0, currFile.canvasSize[0], currFile.canvasSize[1]),
                    visibleLayers[i].menuEntry.nextElementSibling,
                    currFile.layers.indexOf(visibleLayers[i]),
                    visibleLayers[i], visibleLayers[i + 1]
                );
    
                LayerList.mergeLayers(visibleLayers[i + 1].context, visibleLayers[i].context);
    
                // Deleting the above layer
                visibleLayers[i].canvas.remove();
                visibleLayers[i].menuEntry.remove();
                currFile.layers.splice(currFile.layers.indexOf(visibleLayers[i]), 1);
            }
    
            new HistoryState().FlattenVisible(nToFlatten);
            // Updating the layer preview
            currFile.currentLayer.updateLayerPreview();
        }
    }
    function openOptionsMenu(event) {
        if (event.which == 3) {
            let selectedId;
            let target = event.target;

            while (target != null && target.classList != null && !target.classList.contains("layers-menu-entry")) {
                target = target.parentElement;
            }

            selectedId = target.id;

            Layer.layerOptions.style.visibility = "visible";
            Layer.layerOptions.style.top = "0";
            Layer.layerOptions.style.marginTop = "" + (event.clientY - 25) + "px";

            getLayerByID(selectedId).selectLayer(false);
        }
    }
    function closeOptionsMenu(event) {
        Layer.layerOptions.style.visibility = "hidden";
        currFile.currentLayer.rename();
        renamingLayer = false;
    }
    function getLayerListEntries() {
        return layerList;
    }
    function isRenamingLayer() {
        return renamingLayer;
    }
    function refreshZ() {
        try{
            let selectedZIndex = 0;
            let maxZ = 0;
            currFile.layers.forEach((layer, i) => {
                const _i = currFile.layers.length - i;
                let z = (_i+1) * 10;
                if(maxZ < z)maxZ = z;
                layer.canvas.style.zIndex = z;
                if(layer.isSelected){
                    selectedZIndex = z;
                }
            });
            currFile.checkerBoard.canvas.style.zIndex = 1;
            currFile.pixelGrid.canvas.style.zIndex = 2;
            currFile.TMPLayer.canvas.style.zIndex = selectedZIndex + 1;
            currFile.VFXLayer.canvas.style.zIndex = maxZ + 10;
        }catch(e){}
    }
    return {
        refreshZ,
        addLayer,
        mergeLayers,
        getLayerByID,
        getLayerByName,
        renameLayer: startRenamingLayer,
        duplicateLayer,
        clearLayers,
        deleteLayer,
        deleteLayerDirectly,
        merge,
        flatten,
        closeOptionsMenu,
        getLayerListEntries,
        isRenamingLayer
    }
})();