/**
 * @fileOverview Polygon editor implementation.
 */

/**
 * Poly editor constructor.
 *
 * @class Polygon editor. Note that HPolyEdit is not included in the standard
 * hmap.*.js lib but must be included separately.
 *
 * @property {String} mainPageURL URL will be prefix with this string when URL
 *                    manipulation is enabled and map is opened in a frame.
 *
 * @constructor
 */
function HPolyEdit(parent, hmap) {
    this.parent = parent;
    this.hmap = hmap;
    this.polyLayer = hmap.getPolyLayer();

    // create poly edit window
    this.initWindow(parent);

    // configure map to not interfere with poly edit clicks
    this.hmap.disableDoubleClickZoom();

    // line to edit
    this.setPolyline(new HPolyline());
    this.vertexMarkers = new HPolyEditMarkers(this);

    // draw mode
    this.cmdDraw();

    this.isRemove = false;
    this.isHelpVisible = false;
    this.isPointsVisible = false;
    this.isDraggingMarker = false;
    this.pauseUrlManipulation = false;  // temp suspend update of query string if true
    this.urlManipulationEnabled = false;

    // parse query string for mainPageURL (used for URL generation within frame)
    
    var values = HMapHelpers.parseUrl(document.location.search);
    if (values.mainPageURL) {
        this.mainPageURL = decodeURIComponent(values.mainPageURL);
    }

    // init map event listeners
    var me = this;

    this.listeners = [];    // put them in an array for easy cleanup

    this.listeners.push( HEvent.addListener(this.hmap, HMap.EVENT_MAP_CLICK, function(rt90point) { me.mapClick(rt90point); }) );
    this.listeners.push( HEvent.addListener(this.hmap, HMap.EVENT_MAP_DBLCLICK, function(rt90point) { me.mapDblClick(rt90point); }) );
    this.listeners.push( HEvent.addListener(this.hmap, HMap.EVENT_MAP_MOUSE_MOVE, function(rt90point, point) { me.mapMouseMove(rt90point, point); }) );
    this.listeners.push( HEvent.addListener(this.hmap, HMap.EVENT_MAP_ZOOM, function() { me.polylineChanged(); }) );
    this.listeners.push( HEvent.addListener(this.hmap, HMap.EVENT_MAP_MOVE_END, function() { me.polylineChanged(); }) );

    var mainStyle = new HPolyStyle();
    mainStyle.strokeColor = "64, 64, 255";
    mainStyle.strokeOpacity = 0.8;
    mainStyle.fillColor = "128, 128, 255";
    mainStyle.fillOpacity = 0.8;
    mainStyle.weight = 3;

    var volatileStyle = new HPolyStyle();
    volatileStyle.strokeColor = "255, 0, 255";
    volatileStyle.strokeOpacity = 0.8;
    volatileStyle.weight = 3;

    this.polyLayer.setStyle(this.polyLayer.getCtxMain(), mainStyle);
    this.polyLayer.setStyle(this.polyLayer.getCtxVolatile(), volatileStyle);
}

/**
 * Event triggered when the poly editor window is about to close.
 *
 * @param {HPolyline} polyline The current polyline that is edited.
 * @event
 */
HPolyEdit.EVENT_POLYEDIT_CLOSE = "polyeditclose";

/**
 * Event triggered when a vertex is added using the editor.
 *
 * @param {HPointRT90} rt90point Vertex added.
 * @event
 */
HPolyEdit.EVENT_POLYEDIT_ADD_VERTEX = "polyeditadd";

/**
 * Event triggered when a vertex is removed using the editor.
 *
 * @param {HPointRT90} rt90point Vertex removed.
 * @event
 */
HPolyEdit.EVENT_POLYEDIT_REMOVE_VERTEX = "polyeditremove";

/**
 * Event triggered when current polyline of editor is cleared.
 *
 * @event
 */
HPolyEdit.EVENT_POLYEDIT_CLEAR = "polyeditremove";

/**
 * Event triggered when a polyline is set to the poly edit object
 * using setPolyline().
 *
 * @param {HPolyline} polyline The polyline set to edit.
 * @event
 */
HPolyEdit.EVENT_POLYEDIT_SET_POLYLINE = "polyeditsetpolyline";

/**
 * Element ID used for vertex list entry delete button.
 * 
 * @private
 */
HPolyEdit.VERTEX_LIST_DELETE_BUTTON_ID = "polyeditvertexdel";

/**
 * Create and init polygon edit window and add it to parent element.
 *
 * @param {Element} parent Element to add window to.
 * @private
 */
HPolyEdit.prototype.initWindow = function(parent) {

    // polygon editor container
    var div = document.createElement("div");

    var pos = this.hmap.globals.mapPosition;

    HMapHelpers.applyStyle([div], {
        position: "absolute",
        overflow: "hidden",
        fontFamily: "Verdana, Arial",
        fontSize: "10px",
        background: "#fff",
        border: "1px solid #333",
        width: "300px",
        //height: "100px",
        left: (pos.x + 2) + "px",
        top: (pos.y + 2) + "px",
        padding: "1px 3px 3px 3px",
        textAlign: "left",
        zIndex: 99999999
    });

    // drag handle
    var dragHandle = document.createElement("div");
    HMapHelpers.applyStyle([dragHandle], {
        position: "absolute",
        background: "#C3D4F0",
        width: "110%",
        height: "17px",
        left: "0px",
        top: "0px",
        cursor: "move",
        padding: 0,
        margin: 0
    });

    div.appendChild(dragHandle);

    // headline
    var headline = document.createElement("div");
    headline.innerHTML = "Polygoneditor";

    HMapHelpers.applyStyle([headline], {
        position: "relative",
        cursor: "move",
        fontFamily: "Arial",
        fontSize: "11px",
        fontWeight: "bold",
        paddingBottom: "2px",
        "float": "left"
    });

    div.appendChild(headline);

    var closeButton = document.createElement("div");
    closeButton.innerHTML = "X";

    HMapHelpers.applyStyle([closeButton], {
        position: "relative",
        "float": "right",
        cursor: "pointer",
        padding: "1px 3px 0 0"
    });

    div.appendChild(closeButton);

    // top separator
    var topSeparator = document.createElement("div");

    HMapHelpers.applyStyle([topSeparator], {
        position: "relative",
        clear: "both"
    });

    div.appendChild(topSeparator);

    // commands
    var commands = document.createElement("div");

    HMapHelpers.applyStyle([commands], {
        position: "relative",
        fontFamily: "Arial",
        fontSize: "11px",
        padding: "4px 0px 4px 0px"        
    });
    div.appendChild(commands);

    var divider = document.createElement("div");
    HMapHelpers.applyStyle([divider], {
        height: "0px",
        position: "relative",
        margin: "0px",
        padding: "0px",
        clear: "both"
    });
    //div.appendChild(divider);

    var helpArea = document.createElement("div");
    helpArea.id = "helparea";
    HMapHelpers.applyStyle([helpArea], {
        clear: "both",
        position: "relative",
        border: "1px solid #ccc",
        padding: "2px",
        margin: "3px 0px 0px 0px",
        display: "none"
        
    });
    helpArea.innerHTML = "V&auml;lj <b>Rita</b> och klicka sen p&aring; kartan. V&auml;lj <b>&Auml;ndra</b> f&ouml;r att redigera befintliga punkter: l&auml;gg till punkt moturs genom att dubbelklicka p&aring; befintlig punkt, flytta punkt genom att klicka och dra i den. F&ouml;r att ta bort, v&auml;lj <b>Ta bort</b> och klicka p&aring; punkten som skall raderas. V&auml;lj <b>&Aring;ngra</b> f&ouml;r att ta bort sista punkten. <b>Rensa</b> tar bort alla punkter.";
    div.appendChild(helpArea);

    var drawCmd = this.createCommand("Rita", "drawcmd");
    this.applyCmdSelect(drawCmd, true);
    commands.appendChild(drawCmd);

    var editCmd = this.createCommand("&Auml;ndra", "editcmd");
    HMapHelpers.applyStyle([editCmd], { marginRight: "4px" });
    this.applyCmdSelect(editCmd, false);
    commands.appendChild(editCmd);

    commands.appendChild(document.createTextNode("  "));

    var removeCmd = this.createCommand("Ta bort", "removecmd");
    HMapHelpers.applyStyle([removeCmd], { marginRight: "4px" });
    this.applyCmdSelect(removeCmd, false);
    commands.appendChild(removeCmd);

    commands.appendChild(document.createTextNode("  "));

    var undoCmd = this.createCommand("&Aring;ngra", "undocmd");
    HMapHelpers.applyStyle([undoCmd], { marginRight: "4px" });
    this.applyCmdSelect(undoCmd, false);
    commands.appendChild(undoCmd);

    commands.appendChild(document.createTextNode("  "));

    var clearCmd = this.createCommand("Rensa", "clearcmd");
    this.applyCmdSelect(clearCmd, false);
    commands.appendChild(clearCmd);

    var helpCmd = this.createCommand("?", "helpcmd");
    this.applyCmdSelect(helpCmd, false);
    commands.appendChild(helpCmd);
    HMapHelpers.applyStyle([helpCmd], {
        "float": "right"
    });

    var pointsCmd = this.createCommand("&#8595;", "pointscmd");
    this.applyCmdSelect(pointsCmd, false);
    commands.appendChild(pointsCmd);
    HMapHelpers.applyStyle([pointsCmd], {
        "float": "right",
        marginRight: "4px"
    });

    // points
    var points = document.createElement("div");
    points.id = "pointsarea";
    HMapHelpers.applyStyle([points], {
        clear: "both",
        position: "relative",
        overflowY: "scroll",
        width: "100%",
        height: "100",
        fontSize: "11px",
        padding: "2px",
        display: "none"
    });

    div.appendChild(points);

    parent.appendChild(div);

    var me = this;

    // attach events to listen to commands
    HEvent.addDomListener(drawCmd, "click", function(e) { me.cmdDraw(); return HEvent.stopEvent(e); });
    HEvent.addDomListener(editCmd, "click", function(e) { me.cmdEdit(); return HEvent.stopEvent(e); });
    HEvent.addDomListener(clearCmd, "click", function(e) { me.cmdClear(); return HEvent.stopEvent(e); });
    HEvent.addDomListener(undoCmd, "click", function(e) { me.cmdUndo(); return HEvent.stopEvent(e); });
    HEvent.addDomListener(removeCmd, "click", function(e) { me.cmdRemove(); return HEvent.stopEvent(e); });
    HEvent.addDomListener(pointsCmd, "click", function(e) { me.cmdPoints(); return HEvent.stopEvent(e); });
    HEvent.addDomListener(helpCmd, "click", function(e) { me.cmdHelp(); return HEvent.stopEvent(e); });

    // attach event to close button
    HEvent.addDomListener(closeButton, "click", function(e) { me.close(); });

    // attach mouse events to headline to allow dragging
    HEvent.addDomListener(headline, "mousedown", function(e) { me.startDrag(e); });
    HEvent.addDomListener(dragHandle, "mousedown", function(e) { me.startDrag(e); });
    HEvent.addDomListener(document, "mouseup", function(e) { me.stopDrag(e); });
    HEvent.addDomListener(document, "mousemove", function(e) { me.dragging(e); });

    // make global to object
    this.element = div;
    this.vertexList = points;
};

/**
 * Create command button DOM element.
 *
 * @param {String} text Text of command.
 * @param {String} id ID of element.
 * @returns {Element} DOM element.
 * @private
 */
HPolyEdit.prototype.createCommand = function(text, id) {
    var cmd = document.createElement("span");
    cmd.id = id;
    cmd.innerHTML = text;

    HMapHelpers.applyStyle([cmd], {
        paddingTop: "4px",
        border: "1px solid #ccc",
        "float": "left"
    });

    return cmd;
};

/**
 * Hide toolbar window.
 */
HPolyEdit.prototype.hideWindow = function() {
    HMapHelpers.applyStyle([this.element], {
        display: "none"
    });
};

/**
 * Show toolbar window.
 */
HPolyEdit.prototype.showWindow = function() {
    HMapHelpers.applyStyle([this.element], {
        display: "block"
    });
};

/**
 * Enable URL manipulation (disabled by default). Enable URL manipulation
 * immediately after HPolyEdit instance is created to parse the current
 * query string for point coordinates.
 */
HPolyEdit.prototype.enableUrlManipulation = function() {
    this.urlManipulationEnabled = true;
    this.initByUrl();
    this.generateUrl();
};

/**
 * Disables url manipulation.
 */
HPolyEdit.prototype.disableUrlManipulation = function() {
    this.urlManipulationEnabled = false;
};

/**
 * Returns true if url manipulation is enabled.
 *
 * @returns {Boolean} True if url manipulation is enabled.
 */
HPolyEdit.prototype.isUrlManipulationEnabled = function() {
    return this.urlManipulationEnabled;
};

/**
 * Start poly edit window dragging.
 *
 * @param {Event} e Mouse event.
 * @private
 */
HPolyEdit.prototype.startDrag = function(e) {
    var pos = HMapHelpers.getMousePosition(e);
    HEvent.stopEvent(e);

    this.dragStart = pos;
    this.dragWindowStart = new HPoint(this.element.offsetLeft, this.element.offsetTop);

    this.isDragging = true;
};

/**
 * Stop poly edit window dragging.
 *
 * @param {Event} e Mouse event.
 * @private
 */
HPolyEdit.prototype.stopDrag = function(e) {
    this.isDragging = false;
};

/**
 * Perform drag of poly edit window.
 *
 * @param {Event} e Mouse event.
 * @private
 */
HPolyEdit.prototype.dragging = function(e) {
    if (this.isDragging) {
        var pos = HMapHelpers.getMousePosition(e);

        this.element.style.left = this.dragWindowStart.x + (pos.x - this.dragStart.x) + "px";
        this.element.style.top = this.dragWindowStart.y + (pos.y - this.dragStart.y) + "px";

        HEvent.stopEvent(e);
    }
};

/**
 * Close poly edit window and remove current polyline from map.
 */
HPolyEdit.prototype.close = function() {
    HEvent.trigger(this, HPolyEdit.EVENT_POLYEDIT_CLOSE, this.polyline);

    HEvent.removeListeners(this.listeners);

    this.hmap.enableDoubleClickZoom();

    this.removePolyline();
    this.vertexMarkers.clear();

    HMapHelpers.clearElement(this.element);
    this.parent.removeChild(this.element);
};

/**
 * Set polyline object to edit.
 * 
 * @param {HPolyline} polyline Polyline object to edit.
 */
HPolyEdit.prototype.setPolyline = function(polyline) {
    // remove current
    this.removePolyline();

    // add new to map
    this.polyline = polyline;
    this.polyLayer.addPoly(this.polyline);

    HEvent.trigger(this, HPolyEdit.EVENT_POLYEDIT_SET_POLYLINE, this.polyline);

    // update vertex list window
    this.updateVertexList();
    this.polylineChanged();
};

/**
 * Get curent polyline that is edited.
 *
 * @returns {HPolyline} Current polyline object.
 */
HPolyEdit.prototype.getPolyline = function() {
    return this.polyline;
};

/**
 * Remove current polyline from map.
 */
HPolyEdit.prototype.removePolyline = function() {
    if (this.polyline) {
        // remove current from map
        this.polyLayer.removePoly(this.polyline);

        // clear all canvases
        this.polyLayer.clearAll();
    }
};

/**
 * Append vertex to vertex list.
 *
 * @param {HPointRT90} rt90point Vertex to add.
 * @private
 */
HPolyEdit.prototype.addToVertexList = function(rt90point) {
    this.vertexList.appendChild(this.createVertexListEntry(this.polyline.getVertexCount() - 1, rt90point));
    
    // scroll to bottom
    this.vertexList.scrollTop = this.vertexList.scrollHeight;
};

/**
 * Update vertex list by clearing and re-adding vertices.
 * @private
 */
HPolyEdit.prototype.updateVertexList = function() {
    HMapHelpers.clearElement(this.vertexList);
    
    for (var i = 0; i < this.polyline.getVertexCount(); i++) {
        this.vertexList.appendChild(this.createVertexListEntry(i, this.polyline.getVertex(i)));
    }
    
    // scroll to bottom
    this.vertexList.scrollTop = this.vertexList.scrollHeight;
};

/**
 * Create DOM event in vertex list.
 * 
 * @param {Number} index Vertex index.
 * @param {HPointRT90} rt90point Vertex RT90 location.
 * @returns {Element}
 * @private
 */
HPolyEdit.prototype.createVertexListEntry = function(index, rt90point) {
    var span = document.createElement("div");
    span.innerHTML = rt90point.toString();

    var me = this;
    HEvent.addDomListener(span, "mouseover", function(e) { me.vertexEntryMouseOver(e, index, span); });
    HEvent.addDomListener(span, "mouseout", function(e) { me.vertexEntryMouseOut(e, index, span); });

    return span;
};

/**
 * Set drawing state. If true, then user may draw new vertices, if false user
 * is in edit mode.
 *
 * @param {Boolean} isDrawing New drawing state.
 */
HPolyEdit.prototype.setDrawingState = function(isDrawing) {
    if (isDrawing && !this.isDrawing) {
        // enable drawing
        this.vertexMarkers.clear();
    
        this.polyline.setClosed(false);
        this.polyLayer.update();
    } else if (!isDrawing && this.isDrawing) {
        // enable editing
        this.vertexMarkers.createByPolyline(this.polyline);
    }

    this.polyLayer.clear(this.polyLayer.getCtxVolatile());

    this.isDrawing = isDrawing;

    var drawCmd = document.getElementById("drawcmd");
    var editCmd = document.getElementById("editcmd");

    this.applyCmdSelect(drawCmd, this.isDrawing);
    this.applyCmdSelect(editCmd, !this.isDrawing);
};

/**
 * Command button Draw.
 *
 * @private
 */
HPolyEdit.prototype.cmdDraw = function() {
    this.setDrawingState(true);
};

/**
 * Command button Edit.
 *
 * @private
 */
HPolyEdit.prototype.cmdEdit = function() {
    this.setDrawingState(false);
};

/**
 * Command button Remove.
 *
 * @private
 */
HPolyEdit.prototype.cmdRemove = function() {
    this.isRemove = !this.isRemove;

    var removeCmd = document.getElementById("removecmd");
    this.applyCmdSelect(removeCmd, this.isRemove);

    if (!this.isRemove) {
        this.setDrawingState(false);    
    }
};

/**
 * Command button Clear - Clear current polygon, redraw polyline and update
 * vertex list accordingly.
 *
 * @private
 */
HPolyEdit.prototype.cmdClear = function() {
    if (confirm("Alla punkter kommer att tas bort.")) {
        this.setDrawingState(false);

        HEvent.trigger(this, HPolyEdit.EVENT_POLYEDIT_CLEAR);

        // reset and update polyline on main context
        this.polyline.reset();
        this.polyLayer.update();
        this.polylineChanged();

        this.vertexMarkers.clear();

        // update vertex list window
        this.updateVertexList();
    }
};

/**
 * Command button Undo - Remove last vertex, redraw polyline and update vertex
 * list accordingly.
 *
 * @private
 */
HPolyEdit.prototype.cmdUndo = function() {
    if (this.polyline.getVertexCount() < 1) {
        return;
    }

    // remove last added vertex
    this.removeVertex(this.polyline.getVertexCount() - 1);

    // unclose if closed
    this.polyline.setClosed(false);

    if (!this.isDrawing) {
        this.vertexMarkers.remove(this.polyline.getVertexCount());
    }

    // redraw polyline
    this.polyLayer.update();

    // clear volatile context (if drawing)
    this.polyLayer.clear(this.polyLayer.getCtxVolatile());

    // update vertex list window
    this.updateVertexList();
};

/**
 * Command button: Help - toggle help view.
 *
 * @private
 */
HPolyEdit.prototype.cmdHelp = function() {
    var infoArea = document.getElementById("helparea");
    
    this.isHelpVisible = !this.isHelpVisible;

    if (this.isHelpVisible) {
        HMapHelpers.applyStyle([infoArea], {
            display: "block"
        });
    } else {
        HMapHelpers.applyStyle([infoArea], {
            display: "none"
        });
    }
};

/**
 * Command button: Points - toggle points view.
 *
 * @private
 */
HPolyEdit.prototype.cmdPoints = function() {
    var pointsArea = document.getElementById("pointsarea");

    this.isPointsVisible = !this.isPointsVisible;

    if (this.isPointsVisible) {
        HMapHelpers.applyStyle([pointsArea], {
            display: "block"
        });
    } else {
        HMapHelpers.applyStyle([pointsArea], {
            display: "none"
        });
    }
};

/**
 * Apply selected or unselected style to command button.
 *
 * @param {Element} elem Element to apply style to.
 * @param {Boolean} state State to apply (true for selected).
 *
 * @private
 */
HPolyEdit.prototype.applyCmdSelect = function(elem, state) {
    HMapHelpers.applyStyle([elem], {
        cursor: "pointer",
        padding: "2px 4px 2px 4px"
    });

    if (state) {
        HMapHelpers.applyStyle([elem], {
            borderTop: "1px solid #555",
            borderLeft: "1px solid #555",
            background: "#999",
            borderBottom: "1px solid #eee",
            borderRight: "1px solid #eee"
        });
    } else {
        HMapHelpers.applyStyle([elem], {
            borderTop: "1px solid #eee",
            borderLeft: "1px solid #eee",
            background: "#ddd",
            borderBottom: "1px solid #555",
            borderRight: "1px solid #555"
        });
    }
};

/**
 * Add vertex. Does not add the new vertex if same as previous.
 *
 * @param {HPointRT90} rt90point Vertex to add.
 * @returns {Boolean} True if vertex added.
 */
HPolyEdit.prototype.addVertex = function(rt90point) {
    if (!this.isDuplicateVertex(rt90point)) {
        this.polyline.addVertex(rt90point);
        this.polyLayer.update();
        this.polylineChanged();

        // vertex list window
        this.addToVertexList(rt90point);

        HEvent.trigger(this, HPolyEdit.EVENT_POLYEDIT_ADD_VERTEX, rt90point);

        return true;
    }

    return false;
};

/**
 * Insert vertex.
 *
 * @param {Number} index Index where to insert.
 * @param {HPointRT90} rt90point Vertex to insert.
 */
HPolyEdit.prototype.insertVertex = function(index, rt90point) {
    this.polyline.insertVertex(index, rt90point);
    this.polyLayer.update();
    this.polylineChanged();

    // vertex list window
    this.updateVertexList(rt90point);

    HEvent.trigger(this, HPolyEdit.EVENT_POLYEDIT_ADD_VERTEX, rt90point);
};

/**
 * Remove vertex.
 *
 * @param {Number} index Index of vertex to remove.
 */
HPolyEdit.prototype.removeVertex = function(index) {
    HEvent.trigger(this, HPolyEdit.EVENT_POLYEDIT_REMOVE_VERTEX, this.polyline.getVertex(index));
    this.polyline.deleteVertex(index);
    this.polylineChanged();
};

/**
 * Updates a vertex with new coordinates.
 * 
 * @param {Number} index Index of vertex to update.
 * @param {HPointRT90} rt90point New coordinates.
 */
HPolyEdit.prototype.updateVertex = function(index, rt90point) {
    var vertex = this.getPolyline().getVertex(index);

    vertex.north = rt90point.north;
    vertex.east = rt90point.east;

    this.polylineChanged();
};

/**
 * Checks to see if point is duplicate of previous point already added to polyline.
 *
 * @param {HPointRT90} rt90point Point to compare.
 * @returns {Boolean} True if point is equal to latest point in polyline.
 * @private
 */
HPolyEdit.prototype.isDuplicateVertex = function(rt90point) {
    return (this.polyline.getVertexCount() > 0 && this.polyline.getVertex(this.polyline.getVertexCount() - 1).equals(rt90point));
};

/**
 * Add vertex when map is clicked (triggered by event).
 *
 * @param {HPointRT90} rt90point Position of click.
 * @private
 */
HPolyEdit.prototype.mapClick = function(rt90point) {
    if (this.isDraggingMarker) {
        return;
    }

    this.pauseUrlManipulation = false;

    // make sure polyline is open
    this.polyline.setClosed(false);

    this.addVertex(rt90point);

    // clear temp line in volatile context
    this.polyLayer.clear(this.polyLayer.getCtxVolatile());

    if (!this.isDrawing) {
        this.cmdDraw();
    }
};

/**
 * Add vertex and close polygon when map is double clicked (triggered by event).
 *
 * @param {HPointRT90} rt90point Position of click.
 * @private
 */
HPolyEdit.prototype.mapDblClick = function(rt90point) {
    if (this.isDrawing) {
        // close and draw as filled polygon
        this.polyline.setClosed(true);

        // add vertex just in case (single click event triggered when double click anyway)
        if (!this.addVertex(rt90point)) {
            // update with closed drawing
            this.polyLayer.update();
        }

        this.polyLayer.clear(this.polyLayer.getCtxVolatile());

        this.cmdEdit();
    }
};

/**
 * Draw temporary line when mouse is moved in drawing mode (triggered by event).
 *
 * @param {HPointRT90} rt90point Position of click.
 * @param {HPoint} point Container pixel position of click.
 * @private
 */
HPolyEdit.prototype.mapMouseMove = function(rt90point, point) {
    if (this.isDrawing && this.polyline.getVertexCount() > 0) {
        var ctx = this.polyLayer.getCtxVolatile();

        this.polyLayer.clear(ctx);

        this.polyLayer.drawLine(ctx, this.polyline.getVertex(this.polyline.getVertexCount() - 1), rt90point);
    }
};

/**
 * Event listener triggered when mouse over entry in vertex list window.
 * Indicates the list event and the vertex in the map.
 *
 * @param {Event} event Mouse over event.
 * @param {Number} index Vertex index.
 * @param {Element} element Element for vertex list entry.
 * @private
 */
HPolyEdit.prototype.vertexEntryMouseOver = function(event, index, element) {
    HMapHelpers.applyStyle([element], {
        background: "#0f0"
    });

    var rt90point = this.polyline.getVertex(index);

    this.polyLayer.clear(this.polyLayer.getCtxVolatile());
    this.drawPolyMarker(rt90point, "0, 255, 0");
};

/**
 * Event listener triggered when mouse moving out from entry in vertex list
 * window. Cleans up entry indication and volatile context where indication
 * was drawn on the map.
 *
 * @param {Event} event Mouse out event.
 * @param {Number} index Vertex index.
 * @param {Element} element Element for vertex list entry.
 * @private
 */
HPolyEdit.prototype.vertexEntryMouseOut = function(event, index, element) {
    HMapHelpers.applyStyle([element], {
        background: "#fff"
    });

    var ctx = this.polyLayer.getCtxVolatile();
    this.polyLayer.clear(ctx);
};

/**
 * Listens to drag end events of marker. Used for updating position of
 * corresponding vertex.
 *
 * @param {HPointRT90} rt90point Coordinate of marker.
 * @param {HMarker} marker Reference of actual marker.
 * @private
 */
HPolyEdit.prototype.markerDragEnd = function(rt90point, marker) {
    var index = this.vertexMarkers.indexOf(marker);

    this.updateVertex(index, rt90point);
    this.polyLayer.update();

    this.updateVertexList();

    // use timeout to prevent drag end to be interpreted as map click
    var me = this;
    setTimeout(function() { me.isDraggingMarker = false; }, 50)
};

/**
 * Listens to drag move events of marker.
 *
 * @param {HPointRT90} rt90point Coordinate of marker.
 * @param {HMarker} marker Reference of actual marker.
 * @private
 */
HPolyEdit.prototype.markerDragMove = function(rt90point, marker) {
    this.isDraggingMarker = true;

    var index = this.vertexMarkers.indexOf(marker);

    var prevVertex = null;
    var nextVertex = null;

    var polyline = this.getPolyline();
    var count = polyline.getVertexCount();

    // get previous  vertex
    if (index === 0 && count > 1 && polyline.isClosed()) {
        prevVertex = polyline.getVertex(count - 1);
    } else if (index > 0) {
        prevVertex = polyline.getVertex(index - 1);
    }

    // get next vertex
    if (index == count - 1 && index > 1 && polyline.isClosed()) {
        nextVertex = polyline.getVertex(0);
    } else {
        nextVertex = polyline.getVertex(index + 1);
    }

    // draw temp lines while dragging
    var ctx = this.polyLayer.getCtxVolatile();

    this.polyLayer.clear(ctx);

    if (prevVertex) {
        this.polyLayer.drawLine(ctx, prevVertex, rt90point);
    }

    if (nextVertex) {
        this.polyLayer.drawLine(ctx, nextVertex, rt90point);
    }
};

/**
 * Listens to marker click events. If in removal mode, a clicked marker
 * deletes the corresponding vertex.
 *
 * @param {HPointRT90} rt90point Coordinate of marker.
 * @param {HMarker} marker Reference of actual marker.
 * @private
 */
HPolyEdit.prototype.markerClick = function(rt90point, marker) {
    if (this.isRemove) {
        var index = this.vertexMarkers.indexOf(marker);

        this.removeVertex(index);
        this.vertexMarkers.remove(index);

        this.polyLayer.update();
        this.updateVertexList();

        // toggle to turn of remove cmd
        this.cmdRemove();
    }
};

/**
 * Listens to marker double click events. Used for adding a new vertex between
 * the clicked marker and the next counter-clockwise marker.
 *
 * @param {HPointRT90} rt90point Coordinate of marker.
 * @param {HMarker} marker Reference of actual marker.
 * @private
 */
HPolyEdit.prototype.markerDblClick = function(rt90point, marker) {
    if (!this.isRemove) {
        var index = this.vertexMarkers.indexOf(marker);

        var nextVertex = null;
        var polyline = this.getPolyline();
        var count = polyline.getVertexCount();

        // get next vertex
        if (index == count - 1 && index > 1 && polyline.isClosed()) {
            nextVertex = polyline.getVertex(0);
        } else {
            nextVertex = polyline.getVertex(index + 1);
        }

        if (nextVertex != null) {
            var point = new HPointRT90(
                nextVertex.north + ((rt90point.north - nextVertex.north) / 2),
                nextVertex.east + ((rt90point.east - nextVertex.east) / 2)
            );

            this.insertVertex(index + 1, point);
            this.vertexMarkers.insert(index + 1, point);

            this.updateVertexList();
        }
    }
};

/**
 * Listens to event when mouse enters a marker. Used for drawing a circle
 * behind a marker in removal mode.
 *
 * @param {HPointRT90} rt90point Coordinate of marker.
 * @param {HMarker} marker Reference of actual marker.
 * @private
 */
HPolyEdit.prototype.markerMouseOver = function(rt90point, marker) {
    if (this.isRemove) {
        this.drawPolyMarker(rt90point, "255, 0, 0");
    }
};

/**
 * Listens to event when mouse is moving out from marker. Used for
 * clearing context of marker in removal mode.
 *
 * @param {HPointRT90} rt90point Coordinate of marker.
 * @param {HMarker} marker Reference of actual marker.
 * @private
 */
HPolyEdit.prototype.markerMouseOut = function(rt90point, marker) {
    if (this.isRemove) {
        this.polyLayer.clear(this.polyLayer.getCtxVolatile());
    }
};

/**
 * Draw outlined circle on volatile poly layer.
 *
 * @param {HPointRT90} rt90point RT90 coordinate where to draw mark.
 * @param {String} color Color in format "255, 196, 128".
 * @private
 */
HPolyEdit.prototype.drawPolyMarker = function(rt90point, color) {
    var ctx = this.polyLayer.getCtxVolatile();

    ctx.save();
    this.polyLayer.setStyle(ctx, {fillColor: "255, 255, 255", fillOpacity: 0.8});
    this.polyLayer.drawCircle(ctx, rt90point, 12, true);
    this.polyLayer.setStyle(ctx, {fillColor: color, fillOpacity: 0.8});
    this.polyLayer.drawCircle(ctx, rt90point, 10, true);
    ctx.restore();
};

/**
 * Generate and append parameters to the URL # in location.href.
 *
 * @private
 */
HPolyEdit.prototype.generateUrl = function() {
    var center = this.hmap.getCenter();

    var href = "#zoom=" +  (10 - this.hmap.getZoom()) +
               "&cx=" + center.east +
               "&cy=" + center.north;

    var points = [];

    var rt90point;
    for (var i = 0; i < this.polyline.getVertexCount(); i++) {
        rt90point = this.polyline.getVertex(i);
        points.push(rt90point.east + "," + rt90point.north);
    }
    
    if (points.length > 0) {
        href += "&points=" + points.join(";");
    }

    document.location.href = href;

    // if in a frame
    if (parent != window && this.mainPageURL) {
        parent.location = this.mainPageURL + href;
    }
};

/**
 * Parse points, cx, cy in query string.
 *
 * @private
 */
HPolyEdit.prototype.parsePoints = function() {
    if (document.location.hash == "") {
        return {};
    }

    var values = HMapHelpers.parseUrl(document.location.hash);

    // parse points
    var points = [];
    if (values.points) {
        var pointsArray = values.points.split(';');

        for (var i = 0; i < pointsArray.length; i++) {
            var point = pointsArray[i].split(',');
            if (!isNaN(parseInt(point[0])) && !isNaN(parseInt(point[1]))) {
                points.push(new HPointRT90(parseInt(point[1]), parseInt(point[0])));
            }
        }

        values.points = points;
    }

    // center
    if (values.cx && values.cy && !isNaN(parseInt(values.cx)) &&  !isNaN(parseInt(values.cy))) {
        values.center = new HPointRT90(parseInt(values.cy), parseInt(values.cx));
    }

    return values;
};

/**
 * Initiate polyline points by parameters after '#' in the URL.
 * @private
 */
HPolyEdit.prototype.initByUrl = function() {
    var values = this.parsePoints();

    if (values === null) {
        return;
    }

    this.pauseUrlManipulation = true;

    var polyline = null;
    if (values.points && values.points.length > 0) {
        polyline = new HPolyline();
        for (var i = 0; i < values.points.length; i++ ) {
            polyline.addVertex(values.points[i]);
        }
        polyline.setClosed(true);

        this.setPolyline(polyline);
    }

    var zoom = 1;
    if (!isNaN(parseInt(values.zoom))) {
        zoom = 10 - parseInt(values.zoom);
    }

    var center = new HPointRT90(7012924, 1553924);
    if (values.center) {
        center = values.center;
    }

    if ((!values.center || !values.zoom) && polyline) {
        var bounds = polyline.getBounds();

        if (!values.zoom) {
            zoom = this.hmap.getBoundsZoomLevel(bounds);
        }

        if (!values.center) {
            center = bounds.getCenter();
        }
    }

    if (polyline !== null) {
        this.setDrawingState(false);
        this.hmap.setCenter(center, zoom);
    }

    this.pauseUrlManipulation = false;
};

/**
 * Listens to events and rebuilds location.href if urlManipulation is enabled.
 * @private
 */
HPolyEdit.prototype.polylineChanged = function() {
    if (this.urlManipulationEnabled && !this.pauseUrlManipulation) {
        this.generateUrl();
    }
};

/**
 * HPolyEditMarkers constructor.
 *
 * @class Used by HPolyEdit. Class that manages the HMarkers and their addition
 * and removal from the map for each vertex of the current polyline edited
 * in the Poly editor.
 *
 * @param {HPolyEdit} polyEdit Reference to current instace of the poly editor.
 * @constructor
 * @private
 */
function HPolyEditMarkers(polyEdit) {
    this.polyEdit = polyEdit;
    this.hmap = polyEdit.hmap;
    this.vertexMarkers = [];
}

/**
 * Icon used for vertex marker.
 */
HPolyEditMarkers.MARKER_ICON = {
    image: "http://www.hitta.se/images/hmap/polyedit-marker.png",
    anchor: new HPoint(7, 7),
    infoBoxAnchorTop: new HPoint(7, -1),
    infoBoxAnchorBottom: new HPoint(7, 14)
};

// overlay category used by markers
HPolyEditMarkers.MARKER_CATEGORY = 'polyeditmarker';

/**
 * Create markers for each vertex in given polyline.
 *
 * @param {HPolyline} polyline Polyline instance to create markers from.
 */
HPolyEditMarkers.prototype.createByPolyline = function(polyline) {
    for (var i = 0; i < polyline.getVertexCount(); i++) {
        this.add(polyline.getVertex(i));
    }
};

/**
 * Add marker new marker at given coordinate.
 *
 * @param {HPointRT90} rt90point Coordinate of marker to create.
 */
HPolyEditMarkers.prototype.add = function(rt90point) {
    this.insert(this.vertexMarkers.length, rt90point);
};

/**
 * Insert new marker in array at given index.
 *
 * @param {Number} index Index to insert marker at.
 * @param {HPointRT90} rt90point Coordinate of marker to create.
 */
HPolyEditMarkers.prototype.insert = function(index, rt90point) {
    var me = this;

    var vertexMarker = new HMarker(rt90point, HPolyEditMarkers.MARKER_ICON);
    vertexMarker.enableDragging();

    // marker listeners
    HEvent.addListener(vertexMarker, HMarker.EVENT_DRAG_END, function(p) { me.polyEdit.markerDragEnd(p, this); });
    HEvent.addListener(vertexMarker, HMarker.EVENT_DRAG_MOVE, function(p) { me.polyEdit.markerDragMove(p, this); });
    HEvent.addListener(vertexMarker, HMarker.EVENT_MOUSE_DBLCLICK, function(p) { me.polyEdit.markerDblClick(p, this); });
    HEvent.addListener(vertexMarker, HMarker.EVENT_MOUSE_CLICK, function(p) { me.polyEdit.markerClick(p, this); });
    HEvent.addListener(vertexMarker, HMarker.EVENT_MOUSE_OVER, function(p) { me.polyEdit.markerMouseOver(p, this); });
    HEvent.addListener(vertexMarker, HMarker.EVENT_MOUSE_OUT, function(p) { me.polyEdit.markerMouseOut(p, this); });

    this.hmap.addOverlay(vertexMarker, HPolyEditMarkers.MARKER_CATEGORY);

    this.vertexMarkers.splice(index, 0, vertexMarker);
};

/**
 * Remove marker at index.
 *
 * @param {Number} index Index of marker to remove.
 */
HPolyEditMarkers.prototype.remove = function(index) {
    this.hmap.removeOverlay(this.vertexMarkers[index]);

    // clear listeners
    HEvent.clearSource(this.vertexMarkers[index]);

    this.vertexMarkers.splice(index, 1);
};

/**
 * Clear all markers.
 */
HPolyEditMarkers.prototype.clear = function() {
    this.hmap.clearOverlaysByCategory(HPolyEditMarkers.MARKER_CATEGORY);

    this.vertexMarkers = [];
};

/**
 * Check if given marker is equal to first marker in array.
 *
 * @param {HMarker} marker Marker to compare with.
 * @returns {Boolean} True if markers are equal.
 */
HPolyEditMarkers.prototype.isFirst = function(marker) {
    return (this.vertexMarkers.length > 0 && this.vertexMarkers[0] === marker);
};

/**
 * Returns index of given marker in array.
 *
 * @param {HMarker} marker Marker to find.
 * @results {Number} Index of marker (or -1 if not found).
 */
HPolyEditMarkers.prototype.indexOf = function(marker) {
    return this.vertexMarkers.indexOf(marker);
};

