/* global tinymce */
/*
 * TinyMCE Writing Improvement Tool Plugin
 * Author: Raphael Mudge (raffi@automattic.com)
 *
 * Updated for TinyMCE 4.0
 *
 * http://www.afterthedeadline.com
 *
 * Distributed under the LGPL
 *
 * Derived from:
 *	$Id: editor_plugin_src.js 425 2007-11-21 15:17:39Z spocke $
 *
 *	@author Moxiecode
 *	@copyright Copyright (C) 2004-2008, Moxiecode Systems AB, All rights reserved.
 *
 *	Moxiecode Spell Checker plugin released under the LGPL with TinyMCE
 */
tinymce.PluginManager.add( 'AtD', function( editor ) {
	var suggestionsMenu, started, atdCore, dom,
		each = tinymce.each;

	/* initializes the functions used by the AtD Core UI Module */
	function initAtDCore() {

		atdCore = new window.AtDCore();
		atdCore.map = each;
		atdCore._isTinyMCE = true;

		atdCore.getAttrib = function( node, key ) {
			return dom.getAttrib( node, key );
		};

		atdCore.findSpans = function( parent ) {
			if ( parent === undefined ) {
				return dom.select('span');
			} else {
				return dom.select( 'span', parent );
			}
		};

		atdCore.hasClass = function( node, className ) {
			return dom.hasClass( node, className );
		};

		atdCore.contents = function( node ) {
			return node.childNodes;
		};

		atdCore.replaceWith = function( old_node, new_node ) {
			return dom.replace( new_node, old_node );
		};

		atdCore.create = function( node_html ) {
			return dom.create( 'span', { 'class': 'mceItemHidden', 'data-mce-bogus': 1 }, node_html );
		};

		atdCore.removeParent = function( node ) {
			dom.remove( node, true );
			return node;
		};

		atdCore.remove = function( node ) {
			dom.remove( node );
		};

		atdCore.setIgnoreStrings( editor.getParam( 'atd_ignore_strings', [] ).join(',') );
		atdCore.showTypes( editor.getParam( 'atd_show_types', '' ) );
	}

	function getLang( key, defaultStr ) {
		return ( window.AtD_l10n_r0ar && window.AtD_l10n_r0ar[key] ) || defaultStr;
	}

	function isMarkedNode( node ) {
		return ( node.className && /\bhidden(GrammarError|SpellError|Suggestion)\b/.test( node.className ) );
	}

	function markMyWords( errors ) {
		return atdCore.markMyWords( atdCore.contents( editor.getBody() ), errors );
	}

	// If no more suggestions, finish.
	function checkIfFinished() {
		if ( ! editor.dom.select('span.hiddenSpellError, span.hiddenGrammarError, span.hiddenSuggestion').length ) {
			if ( suggestionsMenu ) {
				suggestionsMenu.hideMenu();
			}

			finish();
		}
	}

	function ignoreWord( target, word, all ) {
		var dom = editor.dom;

		if ( all ) {
			each( editor.dom.select( 'span.hiddenSpellError, span.hiddenGrammarError, span.hiddenSuggestion' ), function( node ) {
				var text = node.innerText || node.textContent;

				if ( text === word ) {
					dom.remove( node, true );
				}
			});
		} else {
			dom.remove( target, true );
		}

		checkIfFinished();
	}

	// Called when the user clicks "Finish" or when no more suggestions left.
	// Removes all remaining spans and fires custom event.
	function finish() {
		var node,
			dom = editor.dom,
			regex = new RegExp( 'mceItemHidden|hidden(((Grammar|Spell)Error)|Suggestion)' ),
			nodes = dom.select('span'),
			i = nodes.length;

		while ( i-- ) { // reversed
			node = nodes[i];

			if ( node.className && regex.test( node.className ) ) {
				dom.remove( node, true );
			}
		}

		// Rebuild the DOM so AtD core can find the text nodes
		editor.setContent( editor.getContent({ format: 'raw' }), { format: 'raw' } );

		started = false;
		editor.nodeChanged();
		editor.fire('SpellcheckEnd');
	}

	function sendRequest( file, data, success ) {
		var id = editor.getParam( 'atd_rpc_id', '12345678' ),
			url = editor.getParam( 'atd_rpc_url', '{backend}' );

		if ( url === '{backend}' || id === '12345678' ) {
			window.alert( 'Please specify: atd_rpc_url and atd_rpc_id' );
			return;
		}

		// create the nifty spinny thing that says "hizzo, I'm doing something fo realz"
		editor.setProgressState( true );

		tinymce.util.XHR.send({
			url: url + '/' + file,
			content_type: 'text/xml',
			type: 'POST',
			data: 'data=' + encodeURI( data ).replace( /&/g, '%26' ) + '&key=' + id,
			success: success,
			error: function( type, req, o ) {
				editor.setProgressState();
				window.alert( type + '\n' + req.status + '\nAt: ' + o.url );
			}
		});
	}

	function storeIgnoredStrings( /*text*/ ) {
		// Store in sessionStorage?
	}

	function setAlwaysIgnore( text ) {
		var url = editor.getParam( 'atd_ignore_rpc_url' );

		if ( ! url || url === '{backend}' ) {
			// Store ignored words for this session only
			storeIgnoredStrings( text );
		} else {
			// Plugin is configured to send ignore preferences to server, do that
			tinymce.util.XHR.send({
				url: url + encodeURIComponent( text ) + '&key=' + editor.getParam( 'atd_rpc_id', '12345678' ),
				content_type: 'text/xml',
				type: 'GET',
				error: function() {
					storeIgnoredStrings( text );
				}
			});
		}

		// Update atd_ignore_strings with the new value
		atdCore.setIgnoreStrings( text );
	}

	// Create the suggestions menu
	function showSuggestions( target ) {
		var pos, root, targetPos,
			items = [],
			text = target.innerText || target.textContent,
			errorDescription = atdCore.findSuggestion( target );

		if ( ! errorDescription ) {
			items.push({
				text: getLang( 'menu_title_no_suggestions', 'No suggestions' ),
				classes: 'atd-menu-title',
				disabled: true
			});
		} else {
			items.push({
				text: errorDescription.description,
				classes: 'atd-menu-title',
				disabled: true
			});

			if ( errorDescription.suggestions.length ) {
				items.push({ text: '-' }); // separator

				each( errorDescription.suggestions, function( suggestion ) {
					items.push({
						text: suggestion,
						onclick: function() {
							atdCore.applySuggestion( target, suggestion );
							checkIfFinished();
						}
					});
				});
			}
		}

		if ( errorDescription && errorDescription.moreinfo ) {
			items.push({ text: '-' }); // separator

			items.push({
				text: getLang( 'menu_option_explain', 'Explain...' ),
				onclick : function() {
					editor.windowManager.open({
						title: getLang( 'menu_option_explain', 'Explain...' ),
						url: errorDescription.moreinfo,
						width: 480,
						height: 380,
						inline: true
					});
				}
			});
		}

		items.push.apply( items, [
			{ text: '-' }, // separator

			{ text: getLang( 'menu_option_ignore_once', 'Ignore suggestion' ), onclick: function() {
				ignoreWord( target, text );
			}}
		]);

		if ( editor.getParam( 'atd_ignore_enable' ) ) {
			items.push({
				text: getLang( 'menu_option_ignore_always', 'Ignore always' ),
				onclick: function() {
					setAlwaysIgnore( text );
					ignoreWord( target, text, true );
				}
			});
		} else {
			items.push({
				text: getLang( 'menu_option_ignore_all', 'Ignore all' ),
				onclick: function() {
					ignoreWord( target, text, true );
				}
			});
		}

		// Render menu
		suggestionsMenu = new tinymce.ui.Menu({
			items: items,
			context: 'contextmenu',
			onautohide: function( event ) {
				if ( isMarkedNode( event.target ) ) {
					event.preventDefault();
				}
			},
			onhide: function() {
				suggestionsMenu.remove();
				suggestionsMenu = null;
			}
		});

		suggestionsMenu.renderTo( document.body );

		// Position menu
		pos = tinymce.DOM.getPos( editor.getContentAreaContainer() );
		targetPos = editor.dom.getPos( target );
		root = editor.dom.getRoot();

		// Adjust targetPos for scrolling in the editor
		if ( root.nodeName === 'BODY' ) {
			targetPos.x -= root.ownerDocument.documentElement.scrollLeft || root.scrollLeft;
			targetPos.y -= root.ownerDocument.documentElement.scrollTop || root.scrollTop;
		} else {
			targetPos.x -= root.scrollLeft;
			targetPos.y -= root.scrollTop;
		}

		pos.x += targetPos.x;
		pos.y += targetPos.y;

		suggestionsMenu.moveTo( pos.x, pos.y + target.offsetHeight );
	}

	// Init everything
	editor.on( 'init', function() {
		if ( typeof window.AtDCore === 'undefined' ) {
			return;
		}

		// Set dom and atdCore
		dom = editor.dom;
		initAtDCore();

		// add a command to request a document check and process the results.
		editor.addCommand( 'mceWritingImprovementTool', function( callback ) {
			var results,
				errorCount = 0;

			if ( typeof callback !== 'function' ) {
				callback = function(){};
			}

			// checks if a global var for click stats exists and increments it if it does...
			if ( typeof window.AtD_proofread_click_count !== 'undefined' ) {
				window.AtD_proofread_click_count++;
			}

			// remove the previous errors
			if ( started ) {
				finish();
				return;
			}

			// send request to our service
			sendRequest( 'checkDocument', editor.getContent({ format: 'raw' }), function( data, request ) {
				// turn off the spinning thingie
				editor.setProgressState();

				// if the server is not accepting requests, let the user know
				if ( request.status !== 200 || request.responseText.substr( 1, 4 ) === 'html' || ! request.responseXML ) {
					editor.windowManager.alert(
						getLang( 'message_server_error', 'There was a problem communicating with the Proofreading service. Try again in one minute.' ),
						callback(0)
					);

					return;
				}

				// check to see if things are broken first and foremost
				if ( request.responseXML.getElementsByTagName('message').item(0) !== null ) {
					editor.windowManager.alert(
						request.responseXML.getElementsByTagName('message').item(0).firstChild.data,
						callback(0)
					);

					return;
				}

				results = atdCore.processXML( request.responseXML );

				if ( results.count > 0 ) {
					errorCount = markMyWords( results.errors );
				}

				if ( ! errorCount ) {
					editor.windowManager.alert( getLang( 'message_no_errors_found', 'No writing errors were found.' ) );
				} else {
					started = true;
					editor.fire('SpellcheckStart');
				}

				callback( errorCount );
			});
		});

		if ( editor.settings.content_css !== false ) {
			// CSS for underlining suggestions
			dom.addStyle( '.hiddenSpellError{border-bottom:2px solid red;cursor:default;}' +
				'.hiddenGrammarError{border-bottom:2px solid green;cursor:default;}' +
				'.hiddenSuggestion{border-bottom:2px solid blue;cursor:default;}' );
		}

		// Menu z-index > DFW
		tinymce.DOM.addStyle( 'div.mce-floatpanel{z-index:150100 !important;}' );

		// Click on misspelled word
		editor.on( 'click', function( event ) {
			if ( isMarkedNode( event.target ) ) {
				event.preventDefault();
				editor.selection.select( event.target );
				// Create the suggestions menu
				showSuggestions( event.target );
			}
		});
	});

	editor.addMenuItem( 'spellchecker', {
		text: getLang( 'button_proofread_tooltip', 'Proofread Writing' ),
		context: 'tools',
		cmd: 'mceWritingImprovementTool',
		onPostRender: function() {
			var self = this;

			editor.on('SpellcheckStart SpellcheckEnd', function() {
				self.active( started );
			});
		}
	});

	editor.addButton( 'spellchecker', {
		tooltip: getLang( 'button_proofread_tooltip', 'Proofread Writing' ),
		cmd: 'mceWritingImprovementTool',
		onPostRender: function() {
			var self = this;

			editor.on( 'SpellcheckStart SpellcheckEnd', function() {
				self.active( started );
			});
		}
	});

	editor.on( 'remove', function() {
		if ( suggestionsMenu ) {
			suggestionsMenu.remove();
			suggestionsMenu = null;
		}
	});
});
