/*****************************************************************************
 *
 * edit.js - Some NagVis edit code
 *
 * Copyright (c) 2004-2016 NagVis Project (Contact: info@nagvis.org)
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *****************************************************************************/

function getTargetRaw(event) {
    return event.target ? event.target : event.srcElement;
}

function getTargetByClass(event, className) {
    let target = getTargetRaw(event);
    while (target && !has_class(target, className)) target = target.parentNode;
    return target;
}

function toggleMapObjectLock(event, object_id) {
    g_view.toggleObjectLock(object_id);
    return preventDefaultEvents(event);
}

// Toggles the mode of all map objects: editable or not
function toggleAllMapObjectsLock(event) {
    const lock = g_view.hasUnlocked();

    for (const object_id in g_view.objects) g_view.toggleObjectLock(object_id, lock);

    if (!lock) storeUserOption("unlocked-" + oPageProperties.map_name, "*");
    else storeUserOption("unlocked-" + oPageProperties.map_name, "");
    return preventDefaultEvents(event);
}

let draggingEnabled = true;
let draggingObject = null;
let dragObjectOffset = null;
const dragObjectPos = null;
let dragObjectStartPos = null;
const dragObjectChilds = {};
const dragStopHandlers = {};
const dragMoveHandlers = {};
const dragObjects = {};

let g_resize_obj = null; //This gets a value as soon as a resize start

/** Object resizing **/

function g_resize_object() {
    this.el = null; //pointer to the object
    this.dir = ""; //type of current resize (n, s, e, w, ne, nw, se, sw)
    this.grabx = null; //Some useful values
    this.graby = null;
    this.width = null;
    this.height = null;
    this.left = null;
    this.top = null;
}

// Find out what kind of resize! Return a string inlcluding the directions
function getDirection(event, el) {
    let dir;
    dir = "";

    // Handle IE and other browsers
    const xPos = event.offsetX ? event.offsetX : event.layerX ? event.layerX : 0;
    const yPos = event.offsetY ? event.offsetY : event.layerY ? event.layerY : 0;

    // The distance from the edge in pixels
    const offset = 8;

    if (yPos < offset) {
        dir += "n";
    } else if (yPos > el.offsetHeight - offset) {
        dir += "s";
    }

    if (xPos < offset) {
        dir += "w";
    } else if (xPos > el.offsetWidth - offset) {
        dir += "e";
    }

    return dir;
}

function resizeMouseDown(event) {
    event = event || window.event;
    const target = getTargetByClass(event, "resizeable");

    if (!target || target.id == "") return true;

    const dir = getDirection(event, target);
    if (dir == "") return true;

    // Disable dragging while resizing
    draggingEnabled = false;
    draggingObject = null;

    g_resize_obj = new g_resize_object();

    g_resize_obj.el = target;
    g_resize_obj.dir = dir;

    g_resize_obj.grabx = event.clientX;
    g_resize_obj.graby = event.clientY;
    g_resize_obj.width = pxToInt(target.style.width);
    g_resize_obj.height = pxToInt(target.style.height);
    g_resize_obj.left = pxToInt(target.style.left);
    g_resize_obj.top = pxToInt(target.style.top);

    return preventDefaultEvents(event);
}

function resizeMouseUp(event) {
    event = event || window.event;
    if (g_resize_obj === null) return true;

    // Re-enable dragging
    draggingEnabled = true;
    draggingObject = null;

    const dom_obj = g_resize_obj.el;

    const objId = dom_obj.id.split("-")[0];
    let objX = rmZoomFactor(pxToInt(dom_obj.style.left), true);
    let objY = rmZoomFactor(pxToInt(dom_obj.style.top), true);
    const objW = rmZoomFactor(parseInt(dom_obj.style.width));
    const objH = rmZoomFactor(parseInt(dom_obj.style.height));

    // (worldmap) X and Y are center coordinates, not the top/left corner
    const objMarginTop = rmZoomFactor(pxToInt(dom_obj.style.marginTop), true);
    const objMarginLeft = rmZoomFactor(pxToInt(dom_obj.style.marginLeft), true);
    if (objMarginLeft && objMarginTop) {
        objX = Math.round(objX + objMarginLeft + objW / 2);
        objY = Math.round(objY + objMarginTop + objH / 2);
    }

    const parts = g_view.unproject(objX, objY);

    saveObjectAttr(objId, {
        x: parts[0],
        y: parts[1],
        w: objW,
        h: objH
    });

    g_resize_obj = null;

    return preventDefaultEvents(event);
}

function resizeMouseMove(event) {
    event = event || window.event;
    const target = getTargetByClass(event, "resizeable");

    // First update the cursor. This needs to be done even
    // when not yet resizing to visualize that it is possible

    if (target) {
        let str = getDirection(event, target);

        // Fix the cursor
        if (str == "") str = "";
        else str += "-resize";
        target.style.cursor = str;
    }

    // The following code is only relevant when already resizing

    if (g_resize_obj === null) return true;

    const scale = g_resize_obj.el.dataset.theScale ? g_resize_obj.el.dataset.theScale : 1;

    const minWidth = 8 * scale, // The smallest width and height possible
        minHeight = 8 * scale;

    let grabOffsetX = event.clientX - g_resize_obj.grabx;
    let grabOffsetY = event.clientY - g_resize_obj.graby;

    if (g_resize_obj.dir.indexOf("e") != -1) {
        grabOffsetX = Math.max(grabOffsetX, -g_resize_obj.width * scale + minWidth);

        const newWidth = g_resize_obj.width + grabOffsetX / scale;
        const newLeft = g_resize_obj.left + grabOffsetX / 2;
        const newMarginLeft = -newWidth / 2;

        g_resize_obj.el.style.width = newWidth + "px";
        g_resize_obj.el.style.left = newLeft + "px";
        g_resize_obj.el.style.marginLeft = newMarginLeft + "px";
    }
    if (g_resize_obj.dir.indexOf("s") != -1) {
        grabOffsetY = Math.max(grabOffsetY, -g_resize_obj.height * scale + minHeight);

        const newHeight = g_resize_obj.height + grabOffsetY / scale;
        const newTop = g_resize_obj.top + grabOffsetY / 2;
        const newMarginTop = -newHeight / 2;

        g_resize_obj.el.style.height = newHeight + "px";
        g_resize_obj.el.style.top = newTop + "px";
        g_resize_obj.el.style.marginTop = newMarginTop + "px";
    }

    if (g_resize_obj.dir.indexOf("w") != -1) {
        grabOffsetX = Math.min(grabOffsetX, g_resize_obj.width * scale - minWidth);

        const newWidth = g_resize_obj.width - grabOffsetX / scale;
        const newLeft = g_resize_obj.left + grabOffsetX / 2;
        const newMarginLeft = -newWidth / 2;

        g_resize_obj.el.style.width = newWidth + "px";
        g_resize_obj.el.style.left = newLeft + "px";
        g_resize_obj.el.style.marginLeft = newMarginLeft + "px";
    }
    if (g_resize_obj.dir.indexOf("n") != -1) {
        grabOffsetY = Math.min(grabOffsetY, g_resize_obj.height * scale - minHeight);

        const newHeight = g_resize_obj.height - grabOffsetY / scale;
        const newTop = g_resize_obj.top + grabOffsetY / 2;
        const newMarginTop = -newHeight / 2;

        g_resize_obj.el.style.height = newHeight + "px";
        g_resize_obj.el.style.top = newTop + "px";
        g_resize_obj.el.style.marginTop = newMarginTop + "px";
    }

    return preventDefaultEvents(event);
}

function makeResizeable(trigger_obj) {
    add_class(trigger_obj, "resizeable");
    addEvent(trigger_obj, "mousedown", resizeMouseDown);
    addEvent(trigger_obj, "mouseup", resizeMouseUp);
}

function makeUnresizeable(trigger_obj) {
    // when locking the object while the cursor is a resize cursor,
    // it will stay as it is, when not removing them.
    trigger_obj.style.cursor = "";
    remove_class(trigger_obj, "resizeable");
    removeEvent(trigger_obj, "mousedown", resizeMouseDown);
    removeEvent(trigger_obj, "mouseup", resizeMouseUp);
}

/*** Handles the object dragging ***/

function getButton(event) {
    if (event.which == null)
        /* IE case */
        return event.button < 2 ? "LEFT" : event.button == 4 ? "MIDDLE" : "RIGHT";
    else
        /* All others */
        return event.which < 2 ? "LEFT" : event.which == 2 ? "MIDDLE" : "RIGHT";
}

function makeUndragable(trigger_obj) {
    delete dragStopHandlers[trigger_obj.id];
    delete dragMoveHandlers[trigger_obj.id];
    delete dragObjects[trigger_obj.id];

    remove_class(trigger_obj, "dragger");

    removeEvent(trigger_obj, "mousedown", dragStart);
    removeEvent(document, "mouseup", dragStop);
}

function makeDragable(trigger_obj, obj, dragStopHandler, dragMoveHandler) {
    dragStopHandlers[trigger_obj.id] = dragStopHandler;
    dragMoveHandlers[trigger_obj.id] = dragMoveHandler;
    dragObjects[trigger_obj.id] = obj;

    add_class(trigger_obj, "dragger");

    addEvent(trigger_obj, "mousedown", dragStart);
    // The drag stop event is registered globally on the whole document to prevent
    // problems with too fast mouse movement which might lead to lag the dragging
    // object behind the mouse and make it impossible to stop dragging.
    addEvent(document, "mouseup", dragStop);
}

/**
 * This function is called once an object is picked for dragging
 */
function dragStart(event) {
    event = event || window.event;

    const target = getTargetByClass(event, "dragger");
    const button = getButton(event);

    // Skip calls when already dragging or other button than left mouse
    if (draggingObject !== null || button != "LEFT" || !target || !draggingEnabled) return true;

    contextHide();

    const parts = getEventMousePos(event),
        posx = parts[0],
        posy = parts[1];

    draggingObject = target;
    draggingObject.x = rmZoomFactor(pxToInt(draggingObject.style.left), true);
    draggingObject.y = rmZoomFactor(pxToInt(draggingObject.style.top), true);

    // Save relative offset of the mouse
    dragObjectOffset = [posx - draggingObject.x, posy - draggingObject.y];
    dragObjectStartPos = [draggingObject.x, draggingObject.y];

    // Save diff coords of relative objects
    const sLabelName = target.id.replace("box_", "rel_label_");
    const oLabel = document.getElementById(sLabelName);
    if (oLabel) {
        dragObjectChilds[sLabelName] = [oLabel.offsetLeft - draggingObject.x, oLabel.offsetTop - draggingObject.y];
    }
    return preventDefaultEvents(event);
}

/**
 * This function is called repeated while the object is being dragged
 */
function dragObject(event) {
    event = event || window.event;

    if (draggingObject === null || !draggingEnabled) return true;

    const parts = getEventMousePos(event),
        posx = parts[0],
        posy = parts[1],
        newLeft = posx - dragObjectOffset[0],
        newTop = posy - dragObjectOffset[1];

    // skip further handling when moving out of screen
    if (typeof posx === "undefined" || typeof posy === "undefined") return preventDefaultEvents(event);

    draggingObject.style.position = "absolute";
    draggingObject.style.left = addZoomFactor(newLeft) + "px";
    draggingObject.style.top = addZoomFactor(newTop) + "px";
    draggingObject.x = newLeft;
    draggingObject.y = newTop;

    // When this object has a relative coordinated label, then move this too
    moveRelativeObject(draggingObject.id, newTop, newLeft);

    // Is this object currently relative positioned?
    const idParts = draggingObject.id.split("-");
    const obj = g_view.objects[idParts[0]];
    let parents;
    if (obj.conf.view_type === "line") {
        const anchorId = idParts[2];
        parents = obj.getParentObjectIds(anchorId);
    } else {
        parents = obj.getParentObjectIds();
    }
    const isRel = Object.keys(parents).length > 0;

    // Unhighlight all other objects
    for (const i in g_view.objects) g_view.objects[i].highlight(false);

    // Highlight parents when relative
    for (const objectId in parents) g_view.objects[objectId].highlight(true);

    // With pressed CTRL key the icon should be docked
    // This means the object will be positioned relative to that object
    // This code only highlights that object. When the CTRL key is still pressed
    // when dropping the object the currently moved object will be positioned
    // relative to this object.
    let msg = null;
    if (event.ctrlKey) {
        // Find the nearest object to the current position and highlight it
        let o = getNearestObject(draggingObject, newLeft, newTop);
        if (o) {
            o.highlight(true);
            o = null;
        }

        if (!isRel) msg = "Hold CTRL till drop for relative positioning";
    }

    // Shift key makes the object absolute positioned when still held during dropping
    else if (event.shiftKey) {
        // Unhighlight all objects
        for (const a in g_view.objects) g_view.objects[a].highlight(false);

        if (isRel) msg = "Hold SHIFT till drop for absolute positioning";
    } else {
        if (isRel) msg = "Press SHIFT for absolute positioning";
        else msg = "Press CTRL for relative positioning";
    }

    if (msg !== null) displayStatusMessage(msg, "notice", true);

    // Call the dragging handler when one is set
    if (dragMoveHandlers[draggingObject.id])
        dragMoveHandlers[draggingObject.id](draggingObject, dragObjects[draggingObject.id], event);
    return preventDefaultEvents(event);
}

/**
 * Is called to find the nearest object to the given position. This must
 * check if there is a direct or indirect reference to the current object
 * in order to prevent relative coordinate loops.
 */
function getNearestObject(draggingObject, x, y) {
    let nearest = null;
    let min = null;
    let dist;

    let obj;
    for (const i in g_view.objects) {
        obj = g_view.objects[i];

        // Skip own object
        if (draggingObject.id.split("-")[0] == obj.conf.object_id) continue;

        // FIXME: Also handle lines
        if (obj.conf.view_type !== "icon" || obj.conf.type == "line") continue;

        const objX = obj.parseCoord(obj.conf.x, "x");
        const objY = obj.parseCoord(obj.conf.y, "y");
        dist = Math.sqrt((objX - x) * (objX - x) + (objY - y) * (objY - y));
        if (min === null || dist < min) {
            // Got a nearer one. Ok. But does it have a reference to us?
            if (coordsReferTo(obj, draggingObject.id.split("-")[0])) {
                continue;
            }
            min = dist;
            nearest = obj;
        }
    }

    obj = null;
    min = null;
    dist = null;
    return nearest;
}

/**
 * This function checks wether or not the source object coords refert to
 * the target object directly or indirectly
 */
function coordsReferTo(obj, target_object_id) {
    if (obj.conf.object_id == target_object_id) {
        return true;
    }

    if (isRelativeCoord(obj.conf.x)) {
        const xParent = getMapObjByDomObjId(obj.getCoordParent(obj.conf.x, -1));
        if (coordsReferTo(xParent, target_object_id)) {
            return true;
        }
    }

    if (isRelativeCoord(obj.conf.y)) {
        const yParent = getMapObjByDomObjId(obj.getCoordParent(obj.conf.y, -1));
        if (coordsReferTo(yParent, target_object_id)) {
            return true;
        }
    }

    return false;
}

function moveRelativeObject(parentId, parentTop, parentLeft) {
    let sLabelName = parentId.replace("box_", "rel_label_");
    if (typeof dragObjectChilds[sLabelName] !== "undefined") {
        let oLabel = document.getElementById(sLabelName);
        if (oLabel) {
            oLabel.style.position = "absolute";
            oLabel.style.left = dragObjectChilds[sLabelName][0] + parentLeft + "px";
            oLabel.style.top = dragObjectChilds[sLabelName][1] + parentTop + "px";
            oLabel = null;
        }
    }
    sLabelName = null;
}

function dragStop(event) {
    if (
        draggingObject === null ||
        !draggingEnabled ||
        typeof draggingObject.y == "undefined" ||
        typeof draggingObject.x == "undefined"
    )
        return;

    hideStatusMessage();

    // When x or y are negative just return this and make no change
    if (draggingObject.y < 0 || draggingObject.x < 0) {
        draggingObject.style.left = dragObjectStartPos[0] + "px";
        draggingObject.style.top = dragObjectStartPos[1] + "px";
        draggingObject.x = dragObjectStartPos[0];
        draggingObject.y = dragObjectStartPos[1];

        moveRelativeObject(draggingObject.id, dragObjectStartPos[1], dragObjectStartPos[0]);

        // Call the dragging handler when one is set
        if (dragMoveHandlers[draggingObject.id])
            dragMoveHandlers[draggingObject.id](draggingObject, dragObjects[draggingObject.id], event);

        draggingObject = null;
        return;
    }

    // Skip when the object has not been moved
    if (draggingObject.y == dragObjectStartPos[1] && draggingObject.x == dragObjectStartPos[0]) {
        draggingObject = null;
        return;
    }

    let oParent = null;
    if (event.ctrlKey) {
        oParent = getNearestObject(draggingObject, draggingObject.x, draggingObject.y);
        if (oParent) oParent.highlight(false);
    }

    if (event.shiftKey) oParent = false;

    // Unhilight parent objects
    for (const objectId in getMapObjByDomObjId(draggingObject.id.split("-")[0]).getParentObjectIds())
        getMapObjByDomObjId(objectId).highlight(false);

    dragStopHandlers[draggingObject.id](draggingObject, dragObjects[draggingObject.id], oParent);

    oParent = null;
    draggingObject = null;
}

/**
 * Returns true when currently dragging an object
 */
function dragging() {
    return draggingObject !== null;
}

/** add object code **/

let addObjType = null,
    addViewType = null,
    addNumLeft = null,
    addAction = null,
    addX = [],
    addY = [],
    addFollow = false,
    addShape = null,
    cloneId = null;

function cloneObject(e, objId) {
    cloneId = objId;
    const obj = getMapObjByDomObjId(objId);

    let numClicks = 1;
    if (
        obj.conf.type == "textbox" ||
        obj.conf.type == "container" ||
        obj.conf.view_type == "line" ||
        obj.type == "line"
    )
        numClicks = 2;

    return addObject(e, obj.conf.type, obj.conf.view_type, numClicks, "clone");
}

/**
 * Is called once to start the object creation
 */
function addObject(event, objType, viewType, numLeft, action) {
    event = event || window.event;

    addObjType = objType;
    addViewType = viewType;
    addNumLeft = numLeft;
    addAction = action;

    add_class(document.body, "add");

    return preventDefaultEvents(event);
}

function getEventMousePos(event) {
    event = event || window.event;

    // Only accept "left" mouse clicks
    if (getButton(event) != "LEFT") return null;

    // Ignore clicks on the header menu
    if (event.target) {
        let target = event.target;
        while (target) {
            if (target.id && target.id == "header") {
                return false;
            }
            target = target.parentNode;
        }
    }

    let posx, posy;
    if (event.pageX || event.pageY) {
        posx = event.pageX;
        posy = event.pageY;
    } else if (event.clientX || event.clientY) {
        posx = event.clientX;
        posy = event.clientY;
    }

    // Substract height of header menu here
    posy -= getHeaderHeight();

    // Take the zoom into account. If the map is zoomed this function gathers
    // coordinates where the zoom factor is included. It has to be removed for
    // further processing.
    posx = rmZoomFactor(posx, true);
    posy = rmZoomFactor(posy, true);

    return [posx, posy];
}

function stopAdding() {
    remove_class(document.body, "add");
    ((addObjType = null),
        (addViewType = null),
        (addNumLeft = null),
        (addAction = null),
        (addX = []),
        (addY = []),
        (addFollow = false),
        (addShape = null));
}

function addClick(e) {
    if (!adding()) return true;

    let pos = getEventMousePos(e);
    if (pos === false) {
        // abort adding when clicking on the header
        stopAdding();
        return false;
    }

    addX.push(pos[0] - getSidebarWidth());
    addY.push(pos[1]);
    addNumLeft -= 1;
    pos = null;

    // Draw a line to illustrate the progress of drawing the current line
    if (
        (addViewType === "line" || addObjType === "textbox" || addObjType === "container" || addObjType === "line") &&
        addShape === null
    ) {
        addShape = new jsGraphics("map");
        addShape.cnv.setAttribute("id", "drawing");

        addShape.setColor("#06B606");
        addShape.setStroke(1);

        addFollow = true;
    }

    if (addNumLeft > 0) return false;

    //
    // If this is reached all object coords have been collected
    //

    let w, h;
    if (addObjType == "textbox" || addObjType == "container") {
        w = addX.pop() - addX[0];
        h = addY.pop() - addY[0];
    }

    const parts = g_view.unproject(addX, addY);
    addX = parts[0];
    addY = parts[1];

    let sUrl = "";
    if (addAction == "add" || addAction == "clone")
        sUrl =
            oGeneralProperties.path_server +
            "?mod=Map&act=addModify" +
            "&show=" +
            oPageProperties.map_name +
            "&type=" +
            addObjType +
            "&x=" +
            addX.join(",") +
            "&y=" +
            addY.join(",");

    if (
        addObjType != "textbox" &&
        addObjType != "container" &&
        addObjType != "shape" &&
        addViewType != "icon" &&
        addViewType != ""
    )
        sUrl += "&view_type=" + addViewType;

    if (addAction == "clone" && cloneId !== null) sUrl += "&clone_id=" + cloneId;

    if (addObjType == "textbox" || addObjType == "container") sUrl += "&w=" + w + "&h=" + h;

    if (sUrl === "") return false;

    // remove the drawing area. Once reached this it is not needed anymore
    if (addShape !== null) {
        addShape.clear();
        document.getElementById("map").removeChild(addShape.cnv);
    }

    showFrontendDialog(sUrl, _("Create Object"));
    stopAdding();
    return false;
}

function adding() {
    return addNumLeft !== null;
}

function addFollowing(e) {
    if (!addFollow) return true;

    const pos = getEventMousePos(e);

    const start_x = addZoomFactor(addX[0]),
        start_y = addZoomFactor(addY[0]);

    const end_x = addZoomFactor(pos[0]),
        end_y = addZoomFactor(pos[1]);

    addShape.clear();

    if (addViewType === "line" || addObjType === "line")
        addShape.drawLine(start_x, start_y, end_x - getSidebarWidth(), end_y);
    else addShape.drawRect(start_x, start_y, end_x - getSidebarWidth() - start_x, end_y - start_y);

    addShape.paint();
}

function useGrid() {
    return oViewProperties.grid_show === 1;
}

/**
 * Parses a grind to make the alignment of the icons easier
 */
function gridParse() {
    // Only show when user configured to see a grid
    if (!useGrid()) return;

    // Create grid container and append to map
    let oGrid = document.getElementById("grid");
    if (!oGrid) {
        oGrid = document.createElement("div");
        oGrid.setAttribute("id", "grid");
        document.getElementById("map").appendChild(oGrid);
    } else {
        while (oGrid.firstChild) {
            oGrid.removeChild(oGrid.firstChild);
        }
    }

    // Add options: grid_show, grid_steps, grid_color
    const line = document.createElement("div");
    line.style.backgroundColor = oViewProperties.grid_color;

    const gridStep = addZoomFactor(oViewProperties.grid_steps);
    const gridYStart = 0;
    const gridXStart = 0;
    const gridYEnd = pageHeight() - getHeaderHeight();
    const gridXEnd = pageWidth();

    let vline = null;
    for (let gridX = gridStep; gridX < gridXEnd; gridX = gridX + gridStep) {
        vline = line.cloneNode();
        vline.className = "vertical";
        vline.style.left = gridX + "px";
        vline.style.top = gridYStart + "px";
        vline.style.bottom = 0;
        oGrid.appendChild(vline);
    }

    let hline;
    for (let gridY = gridStep; gridY < gridYEnd; gridY = gridY + gridStep) {
        hline = line.cloneNode();
        hline.className = "horizontal";
        hline.style.top = gridY + "px";
        hline.style.left = gridXStart + "px";
        hline.style.right = 0;
        oGrid.appendChild(hline);
    }

    addEvent(window, "resize", gridRedraw);
    addEvent(window, "scroll", gridRedraw);
}

function gridRemove() {
    const oGrid = document.getElementById("grid");
    if (oGrid) oGrid.parentNode.removeChild(oGrid);

    removeEvent(window, "resize", gridRedraw);
    removeEvent(window, "scroll", gridRedraw);
}

function gridRedraw() {
    gridRemove();
    gridParse();
}

/**
 * Toggle the grid state in the current view and sends
 * current setting to server component for persistance
 */
function gridToggle() {
    // Toggle the grid state
    if (useGrid()) {
        oViewProperties.grid_show = 0;
        gridRemove();
    } else {
        oViewProperties.grid_show = 1;
        gridParse();
    }

    // Send current option to server for persistance
    call_ajax(
        oGeneralProperties.path_server +
            "?mod=Map&act=modifyObject&map=" +
            oPageProperties.map_name +
            "&type=global&id=0&grid_show=" +
            oViewProperties.grid_show
    );
}

/**
 * Alligns the current coordinates to the current grid
 */
function coordsToGrid(x, y) {
    x = "" + x;
    y = "" + y;
    if (x.indexOf(",") !== -1) {
        x = x.split(",");
        y = y.split(",");
        for (let i = 0; i < x.length; i++) {
            x[i] = x[i] - (x[i] % addZoomFactor(oViewProperties.grid_steps));
            y[i] = y[i] - (y[i] % addZoomFactor(oViewProperties.grid_steps));
        }
        return [x.join(","), y.join(",")];
    } else {
        x = +x;
        y = +y;
        const gridMoveX = x - (x % addZoomFactor(oViewProperties.grid_steps));
        const gridMoveY = y - (y % addZoomFactor(oViewProperties.grid_steps));
        return [gridMoveX, gridMoveY];
    }
}

function toggle_section(sec) {
    const sections = document.getElementsByClassName("section");
    for (let i = 0; i < sections.length; i++) {
        if (sections[i].id == "sec_" + sec) sections[i].style.display = "";
        else sections[i].style.display = "none";
    }

    // update the navigation
    const nav_items = document.getElementById("nav").childNodes;
    for (let i = 0; i < nav_items.length; i++) {
        if (nav_items[i].id == "nav_" + sec) add_class(nav_items[i], "active");
        else remove_class(nav_items[i], "active");
    }

    // update the helper field value
    document.getElementById("sec").value = sec;
}

// Toggles the state of an option (Showing inherited value or the current configured value)
function toggle_option(name) {
    let field = document.getElementById(name);
    let txt = document.getElementById("_txt_" + name);

    if (field && txt) {
        if (field.style.display === "none") {
            field.style.display = "";
            txt.style.display = "none";
        } else {
            field.style.display = "none";
            txt.style.display = "";
        }
        txt = null;
        field = null;
    }
}

function togglePicker(id, state) {
    let o = document.getElementById(id);
    if (jscolor.picker && jscolor.picker.owner == o.color) o.color.hidePicker();
    else o.color.showPicker();
    o = null;
}

function pickWindowSize(id, dimension) {
    let o = document.getElementById(id);
    if (dimension == "width") {
        o.value = pageWidth();
    } else {
        o.value = pageHeight() - getHeaderHeight();
    }
    o = null;
}

function updateUserRoles(bAdd) {
    const user_roles = document.getElementById("user_roles");
    const available = document.getElementById("roles_available");
    const selected = document.getElementById("roles_selected");
    let source, target;
    if (bAdd) {
        source = available;
        target = selected;
    } else {
        source = selected;
        target = available;
    }

    // Quit when no source selected
    if (source.selectedIndex === -1) return false;

    // Save strings
    const optName = source.options[source.selectedIndex].text;
    const optValue = source.options[source.selectedIndex].value;

    // Move from source to target
    source.options.remove(source.selectedIndex);
    target.options[target.options.length] = new Option(optName, optValue, false, false);

    // now update the internal helper field with the selected options
    const selected_values = [];
    for (let i = 0; i < selected.options.length; i++) {
        selected_values.push(selected.options[i].value);
    }
    user_roles.value = selected_values.join(",");
}

function addOption(form) {
    const field = form.num_options;
    field.value = parseInt(field.value) + 1;
    updateForm(form);
}

// Removes an element from the map
function removeMapObject(event, object_id) {
    event = event || window.event;
    g_view.removeObject(object_id);
    return preventDefaultEvents(event);
}

/************************************************
 * Register events
 *************************************************/

addEvent(document, "mousemove", function (event) {
    return resizeMouseMove(event) && dragObject(event) && addFollowing(event);
});

addEvent(document, "click", function (event) {
    return addClick(event);
});
