/*
Moves the current line or selection up or down

action.setup:
    - direction (string): 'up' or 'down'
*/

var utils = loadLibrary('tea-utils');

action.titleWithContext = function(context, outError) {
	if (typeof action.setup.direction === 'undefined') {
		return null;
	}
	
	var range = context.selectedRanges[0];
	if (range.length > 0) {
		// Something is selected; check to see if the selection spans multiple lines
		if (context.lineStorage.lineNumberForIndex(range.location) !== context.lineStorage.lineNumberForIndex(range.location + range.length)) {
			return '@multiple';
		}
	}
	return null;
};

action.performWithContext = function(context, outError) {
	// Make sure that there's a direction parameter passed
	if (typeof action.setup.direction === 'undefined') {
		console.log('move_line requires a direction parameter ("up" or "down" are valid)');
		return false;
	}
	
	var direction = action.setup.direction.toLowerCase(),
		// Grab our current selected range
		range = context.selectedRanges[0],
		// Check to see if the selection spans multiple lines
		moveRange, currentLine, multipleLines = false;
	if (range.length > 0) {
		// Grab the range for the lines our selection spans
		moveRange = context.lineStorage.lineRangeForRange(range);
		// Figure out if we are spanning multiple lines
		var nextStartIndex = context.lineStorage.lineStartIndexGreaterThanIndex(moveRange.location);
		if (nextStartIndex < (moveRange.location + moveRange.length)) {
			multipleLines = true;
			if (direction === 'up') {
				currentLine = context.lineStorage.lineNumberForIndex(moveRange.location);
			} else {
				// We have to remove one index from the final number because it either includes a linebreak, or else is at the very end of the document (and you can't select the last line of the document unless there is at least one character there)
				currentLine = context.lineStorage.lineNumberForIndex(moveRange.location + moveRange.length - 1);
			}
		}
	} else {
		moveRange = context.lineStorage.lineRangeForIndex(range.location);
	}
	
	if (!currentLine) {
		// We only don't have a current line if it's a singleton; set to the line for the first index as a result
		currentLine = context.lineStorage.lineNumberForIndex(moveRange.location);
	}
	
	// Grab our target line number
	var swapLine = (direction === 'up' ? currentLine - 1 : currentLine + 1);
	
	// Process the request!
	if (swapLine >= 1 && swapLine <= context.lineStorage.numberOfLines) {
		// Grab the range of the target
		var swapRange = context.lineStorage.lineRangeForLineNumber(swapLine),
			// Grab our text
			moveText = context.string.substringWithRange(moveRange),
			swapText = context.string.substringWithRange(swapRange);
		
		if (swapLine == context.lineStorage.numberOfLines || (direction === 'up' && moveRange.location + moveRange.length === context.string.length)) {
			// Swap linebreaks to make sure that the last line of the document doesn't end up appending the selection instead of swapping with it
			var moveBreak = moveText.replace(/^[\s\S]*?([\n\r]*)$/, '$1'),
				swapBreak = swapText.replace(/^[\s\S]*?([\n\r]*)$/, '$1');
			moveText = moveText.replace(/^([\s\S]*?)[\n\r]*$/, '$1' + swapBreak);
			swapText = swapText.replace(/^([\s\S]*?)[\n\r]*$/, '$1' + moveBreak);
		}
		
		// Initialize our text recipe and perform our replacements
		var recipe = new CETextRecipe(),
			// The order of the replacements matters depending on if you are going up or down
			selection;
		if (direction === 'up') {
			recipe.replaceRange(swapRange, moveText);
			recipe.replaceRange(moveRange, swapText);
			// Create our target selected range
			selection = new Range(swapRange.location, moveText.length);
		} else {
			recipe.replaceRange(moveRange, swapText);
			recipe.replaceRange(swapRange, moveText);
			// Create target selected range
			selection = new Range(moveRange.location + swapText.length, moveText.length);
		}
	} else {
		// The target line is outside the document bounds, so we will be inserting new lines
		var moveText = context.string.substringWithRange(moveRange),
			moveBreak = moveText.replace(/^[\s\S]*?([\n\r]*)$/, '$1'),
			// Construct new line based on indent level of existing line
			swapText = moveText.replace(/^([ \t]*)(?:\S[\s\S]*)?[\n\r]*$/, '$1') + (direction === 'down' ? context.textPreferences.lineEndingString : moveBreak);
		
		// Initialize our recipe, and do our insertions
		var recipe = new CETextRecipe(),
			selection;
		if (direction === 'up') {
			if (moveBreak === '') {
				moveText += context.textPreferences.lineEndingString;
			}
			if (moveText.length !== moveRange.length) {
				recipe.replaceRange(moveRange, moveText);
			}
			recipe.insertAtIndex(moveRange.location + moveRange.length, swapText);
			selection = new Range(0, moveText.length);
		} else {
			if (moveBreak !== '') {
				moveText = moveText.replace(/^([\s\S]*?)[\n\r]*$/, '$1');
			}
			if (moveText.length !== moveRange.length) {
				recipe.replaceRange(moveRange, moveText);
			}
			recipe.insertAtIndex(moveRange.location, swapText);
			selection = new Range(moveRange.location + swapText.length, moveText.length);
		}
	}
	
	// Add our undo name
	if (multipleLines) {
		recipe.undoActionName = 'Move Selected Lines ' + utils.toSimpleTitleCase(direction);
	} else if (action.setup.undoName) {
	    recipe.undoActionName = action.setup.undoName;
	}
	// Apply our recipe!
	context.applyTextRecipe(recipe);
	// Move our selection along with the text
	context.selectedRanges = [selection];
	// Signal success
	return true;
};