/*
Attempts to locate the balanced delimiters around the cursor and select
their contents

action.setup:
	- direction (string): specifies which direction to balance in; 'in' or 'out' (default)
	- mode (string): what type of balancing to use; 'auto' (default), 'itemizer' (force itemizer-based balancing), 'string' (force string-based balancing; outward only), or 'comment' (force comment-based balancing; outward only)
*/

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

action.performWithContext = function(context, outError) {
	// Init useful variables
	var range, zone, newRange;
	// Check what our direction is
	var direction = 'out';
	if (action.setup.direction) {
		direction = action.setup.direction.toLowerCase();
	}
	// Check what our mode is
	var mode = 'auto';
	if (action.setup.mode) {
		mode = action.setup.mode.toLowerCase();
	}
	// If auto, check what mode we should be using based on the cursor location
	if (mode === 'auto') {
		range = context.selectedRanges[0];
		var stringTarget = new SXSelector('string, string *:not(definition.begin)');
		var commentTarget = new SXSelector('comment, comment *:not(definition.begin)');
		var regexTarget = new SXSelector('regex.literal, regex.literal *:not(definition.begin)');
		zone = utils.zoneAtIndex(context, range.location);
		if (stringTarget.matches(zone) && direction === 'out') {
			mode = 'string';
		} else if (commentTarget.matches(zone) && direction === 'out') {
			mode = 'comment';
		} else if (regexTarget.matches(zone) && direction === 'out') {
			mode = 'regex.literal';
		} else {
			mode = 'itemizer';
		}
	}
	
	// Do the actual balancing!
	if (mode === 'string' || mode === 'comment' || mode === 'regex.literal') {
		// FIXME: Add support for multiple ranges?
		if (!range) {
			range = context.selectedRanges[0];
		}
		zone = utils.zoneAtIndex(context, range.location);
		// Make sure that we aren't at the last character in the string (adjacent to the string's punctuation)
		var innerSelector = new SXSelector(mode + ' *');
		if (innerSelector.matches(zone)) {
			zone = zone.parent;
		}
		var zoneRange = zone.range;
		// Determine our new range by excluding the starting and ending punctuation
		// FIXME: add graceful error handling/degradation
		var startPuncZone = zone.childAtIndex(0);
		var startPuncRange = startPuncZone.range;
		var endPuncZone = zone.childAtIndex(zone.childCount - 1);
		var endPuncRange = endPuncZone.range;
		// Verify that the endPuncZone is in fact the end punctuation (might not be for single-line comments, for instance)
		var endPuncTest = new SXSelector('punctuation.definition.end'),
			regexFlagsTest = new SXSelector('regex.flags');
		if (zone.childCount > 1 && endPuncTest.matches(endPuncZone)) {
			// endPuncZone wasn't the same as the start zone, and it tests out as end punctuation
			newRange = new Range(zoneRange.location + startPuncRange.length, zoneRange.length - startPuncRange.length - endPuncRange.length);
		} else if (zone.childCount > 2 && regexFlagsTest.matches(endPuncZone)) {
			// Special handling for regex objects, which can have flags appended to them (like in Javascript)
			endPuncZone = zone.childAtIndex(zone.childCount - 2);
			if (endPuncTest.matches(endPuncZone)) {
				var diffFromEnd = zoneRange.location + zoneRange.length - endPuncZone.range.location;
				newRange = new Range(zoneRange.location + startPuncRange.length, zoneRange.length - startPuncRange.length - diffFromEnd);
			}
		} else {
			// endPuncZone needs to be ignored
			newRange = new Range(zoneRange.location + startPuncRange.length, zoneRange.length - startPuncRange.length);
		}
		// If we are working with a comment, strip any leading or trailing whitespace from the selection since the user probably doesn't want to work with them
		if (mode === 'comment') {
			// Grab our text and initial location/length
			var text = context.substringWithRange(newRange),
				startIndex = newRange.location,
				selectionLength = newRange.length;
			// Strip off leading whitespace and compare
			var stripped = text.replace(/^\s*([\s\S]*?)$/, '$1');
			if (stripped.length < text.length) {
				startIndex = startIndex + (text.length - stripped.length);
				selectionLength = selectionLength - (text.length - stripped.length);
			}
			// Do the same thing for the trailing whitespace
			stripped = text.replace(/^([\s\S]*?)\s*$/, '$1');
			if (stripped.length < text.length) {
				selectionLength = selectionLength - (text.length - stripped.length);
			}
			// Remake our selection range
			newRange = new Range(startIndex, selectionLength);
			
			// If we have already balanced this comment, select the whole comment
			if (newRange.location == range.location && newRange.length == range.length) {
				newRange = zoneRange;
			}
		}
		// We have already balanced this zone, so switch to itemizer balancing
		if (newRange.location == range.location && newRange.length == range.length) {
			mode = 'itemizer';
		} else {
			context.selectedRanges = [newRange];
			return true;
		}
	}
	
	// Because we might need to advance to zen or itemizer balancing if the string has already been balanced, this stuff happens afterward
	var ranges = context.selectedRanges;
	var targets = [];
	var count = ranges.length;
	var item, items;
	for (var i = 0; i < count; i++) {
		range = ranges[i];
		if (direction === 'in') {
			item = utils.itemForRange(context, range);
			// FIXME: would functionality would be better if it selected the next item rather than just dying?
			if (item === null) {
				// No item to balance to, so jump to next range
				continue;
			}
			newRange = item.range;
			if (newRange.location === range.location && newRange.length === range.length) {
				// Item is already selected
				items = item.childItems;
				if (items.length > 0) {
					newRange = items[0].range;
				}
			}
			// Add range to our selection targets
			targets.push(newRange);
		} else {
			// Direction defaults to outward
			item = utils.itemParentForRange(context, range);
			if (item === null) {
				continue;
			}
			// Check to see if we need to select the item's contents, or its entirety
			var innerRange = item.innerRange;
			if (innerRange === null || (innerRange.location == range.location && innerRange.length == range.length) || range.location < innerRange.location || range.location + range.length > innerRange.location + innerRange.length) {
				// innerRange already selected or is outside the scope of original range, so head for the whole thing
				targets.push(item.range);
			} else {
				// innerRange hasn't been selected, so go with that
				targets.push(innerRange);
			}
		}
	}
	
	// Set the selections and return
	if (targets.length > 0) {
		context.selectedRanges = targets;
		return true;
	} else {
		return false;
	}
};