/*\
title: $:/core/modules/widgets/link.js
type: application/javascript
module-type: widget

Link widget

\*/
(function(){

/*jslint node: true, browser: true */
/*global $tw: false */
"use strict";

var Widget = require("$:/core/modules/widgets/widget.js").widget;

var LinkWidget = function(parseTreeNode,options) {
	this.initialise(parseTreeNode,options);
};

/*
Inherit from the base widget class
*/
LinkWidget.prototype = new Widget();

/*
Render this widget into the DOM
*/
LinkWidget.prototype.render = function(parent,nextSibling) {
	// Save the parent dom node
	this.parentDomNode = parent;
	// Compute our attributes
	this.computeAttributes();
	// Execute our logic
	this.execute();
	// Get the value of the tw-wikilinks configuration macro
	var wikiLinksMacro = this.getVariable("tw-wikilinks"),
		useWikiLinks = wikiLinksMacro ? !(wikiLinksMacro.trim() === "no") : true;
	// Render the link if required
	if(useWikiLinks) {
		this.renderLink(parent,nextSibling);
	} else {
		// Just insert the link text
		var domNode = this.document.createElement("span");
		parent.insertBefore(domNode,nextSibling);
		this.renderChildren(domNode,null);
		this.domNodes.push(domNode);
	}
};

/*
Render this widget into the DOM
*/
LinkWidget.prototype.renderLink = function(parent,nextSibling) {
	var self = this;
	// Create our element
	var domNode = this.document.createElement("a");
	// Assign classes
	var classes = ["tw-tiddlylink"];
	if(this.isShadow) {
		classes.push("tw-tiddlylink-shadow");
	}
	if(this.isMissing && !this.isShadow) {
		classes.push("tw-tiddlylink-missing");
	} else {
		if(!this.isMissing) {
			classes.push("tw-tiddlylink-resolves");
		}
	}
	domNode.setAttribute("class",classes.join(" "));
	// Set an href
	var wikiLinkTemplateMacro = this.getVariable("tw-wikilink-template"),
		wikiLinkTemplate = wikiLinkTemplateMacro ? wikiLinkTemplateMacro.trim() : "#$uri_encoded$",
		wikiLinkText = wikiLinkTemplate.replace("$uri_encoded$",encodeURIComponent(this.to));
	wikiLinkText = wikiLinkText.replace("$uri_doubleencoded$",encodeURIComponent(encodeURIComponent(this.to)));
	domNode.setAttribute("href",wikiLinkText);
	// Set the tooltip
	// HACK: Performance issues with re-parsing the tooltip prevent us defaulting the tooltip to "<$transclude field='tooltip'><$transclude field='title'/></$transclude>"
	var tooltipWikiText = this.tooltip || this.getVariable("tw-wikilink-tooltip");
	if(tooltipWikiText) {
		var tooltipText = this.wiki.renderText("text/plain","text/vnd.tiddlywiki",tooltipWikiText,{
				parseAsInline: true,
				variables: {
					currentTiddler: this.to
				},
				parentWidget: this
			});
		domNode.setAttribute("title",tooltipText);
	}
	// Add a click event handler
	$tw.utils.addEventListeners(domNode,[
		{name: "click", handlerObject: this, handlerMethod: "handleClickEvent"},
		{name: "dragstart", handlerObject: this, handlerMethod: "handleDragStartEvent"},
		{name: "dragend", handlerObject: this, handlerMethod: "handleDragEndEvent"}
	]);
	// Insert the link into the DOM and render any children
	parent.insertBefore(domNode,nextSibling);
	this.renderChildren(domNode,null);
	this.domNodes.push(domNode);
};

LinkWidget.prototype.handleClickEvent = function (event) {
	// Send the click on it's way as a navigate event
	var bounds = this.domNodes[0].getBoundingClientRect();
	this.dispatchEvent({
		type: "tw-navigate",
		navigateTo: this.to,
		navigateFromTitle: this.getVariable("storyTiddler"),
		navigateFromNode: this,
		navigateFromClientRect: { top: bounds.top, left: bounds.left, width: bounds.width, right: bounds.right, bottom: bounds.bottom, height: bounds.height
		},
		navigateSuppressNavigation: event.metaKey || event.ctrlKey || (event.button === 1)
	});
	event.preventDefault();
	event.stopPropagation();
	return false;
};

LinkWidget.prototype.handleDragStartEvent = function(event) {
	if(this.to) {
		// Set the dragging class on the element being dragged
		$tw.utils.addClass(event.target,"tw-tiddlylink-dragging");
		// Create the drag image elements
		this.dragImage = this.document.createElement("div");
		this.dragImage.className = "tw-tiddler-dragger";
		var inner = this.document.createElement("div");
		inner.className = "tw-tiddler-dragger-inner";
		inner.appendChild(this.document.createTextNode(this.to));
		this.dragImage.appendChild(inner);
		this.document.body.appendChild(this.dragImage);
		// Astoundingly, we need to cover the dragger up: http://www.kryogenix.org/code/browser/custom-drag-image.html
		var cover = this.document.createElement("div");
		cover.className = "tw-tiddler-dragger-cover";
		cover.style.left = (inner.offsetLeft - 16) + "px";
		cover.style.top = (inner.offsetTop - 16) + "px";
		cover.style.width = (inner.offsetWidth + 32) + "px";
		cover.style.height = (inner.offsetHeight + 32) + "px";
		this.dragImage.appendChild(cover);
		// Set the data transfer properties
		var dataTransfer = event.dataTransfer;
		// First the image
		dataTransfer.effectAllowed = "copy";
		if(dataTransfer.setDragImage) {
			dataTransfer.setDragImage(this.dragImage.firstChild,-16,-16);
		}
		// Then the data
		dataTransfer.clearData();
		var jsonData = this.wiki.getTiddlerAsJson(this.to),
			textData = this.wiki.getTiddlerText(this.to,"");
		// IE doesn't like these content types
		if(!$tw.browser.isIE) {
			dataTransfer.setData("text/vnd.tiddler",jsonData);
			dataTransfer.setData("text/plain",textData);
			dataTransfer.setData("text/x-moz-url","data:text/vnd.tiddler," + encodeURI(jsonData));
		}
		dataTransfer.setData("URL","data:text/vnd.tiddler," + encodeURI(jsonData));
		dataTransfer.setData("Text",textData);
		event.stopPropagation();
	} else {
		event.preventDefault();
	}
};

LinkWidget.prototype.handleDragEndEvent = function(event) {
	// Remove the dragging class on the element being dragged
	$tw.utils.removeClass(event.target,"tw-tiddlylink-dragging");
	// Delete the drag image element
	if(this.dragImage) {
		this.dragImage.parentNode.removeChild(this.dragImage);
	}
};

/*
Compute the internal state of the widget
*/
LinkWidget.prototype.execute = function() {
	// Get the target tiddler title
	this.to = this.getAttribute("to",this.getVariable("currentTiddler"));
	// Get the link title
	this.tooltip = this.getAttribute("tooltip");
	// Determine the link characteristics
	this.isMissing = !this.wiki.tiddlerExists(this.to);
	this.isShadow = this.wiki.isShadowTiddler(this.to);
	// Make the child widgets
	this.makeChildWidgets();
};

/*
Selectively refreshes the widget if needed. Returns true if the widget or any of its children needed re-rendering
*/
LinkWidget.prototype.refresh = function(changedTiddlers) {
	var changedAttributes = this.computeAttributes();
	if(changedAttributes.to || changedTiddlers[this.to] || changedAttributes.tooltip) {
		this.refreshSelf();
		return true;
	}
	return this.refreshChildren(changedTiddlers);
};

exports.link = LinkWidget;

})();
