/**
 * Increases the level of header for the given line
 * 
 * setup:
 * - increment (number): accepts 1 or -1
 */

action.performWithContext = function(context, outError) {
	// Grab the active line range, and setup next line variables in case
	var curRange = context.selectedRanges[0],
		lineRange = context.lineStorage.lineRangeForIndex(curRange.location),
		nextLineStart = lineRange.length + lineRange.location + 1,
		nextLineRange = null,
		// Grab all syntax zones in the current line
		lineZones = context.syntaxTree.zonesInCharacterRange(lineRange),
		// Setup our variables for later
		headerZone = null,
		atxHeaderSelector = new SXSelector('header.atx'),
		atxPunctuationSelector = new SXSelector('header.atx > punctuation.definition'),
		punctuationZone, punctuationEndZone = null,
		setextHeaderSelector = new SXSelector('punctuation.setext'),
		setext = false,
		level = 0,
		dir = (typeof action.setup.increment !== 'undefined' ? parseInt(action.setup.increment, 10) : 1),
		// Recipe variables
		recipe = new CETextRecipe(),
		replaceRange = null,
		replaceText,
		// Selection tracking
		selectionBump = 0,
		// Loop variables
		i, count;
	// Look for an existing header zone
	if (lineZones !== null) {
		for (i = 0, count = lineZones.length; i < count; i++) {
			if (atxHeaderSelector.matches(lineZones[i])) {
				headerZone = lineZones[i];
				break;
			} else if (setextHeaderSelector.matches(lineZones[i])) {
				headerZone = lineZones[i];
				setext = true;
				// Adjust our line ranges, since our cursor is on the setext line
				nextLineRange = lineRange;
				lineRange = context.lineStorage.lineRangeForIndex(lineRange.location - 1);
				break;
			}
		}
	}
	// If we do not have a header, check the next line for an setext zone
	if (headerZone === null) {
		if (nextLineStart < context.string.length) {
			nextLineRange = context.lineStorage.lineRangeForIndex(nextLineStart);
		}
		if (nextLineRange !== null) {
			lineZones = context.syntaxTree.zonesInCharacterRange(nextLineRange);
			if (lineZones !== null) {
				for (i = 0, count = lineZones.length; i < count; i++) {
					if (setextHeaderSelector.matches(lineZones[i])) {
						headerZone = lineZones[i];
						setext = true;
						break;
					}
				}
			}
		}
	}
	// We handle things differently depending on whether we are working with an existing setext title
	if (setext) {
		// Determine what level of setext title we are working with
		if (headerZone.text.match(/^=+$/)) {
			level = 1;
		} else {
			level = 2;
		}
		if (dir === 1) {
			// If we are working with a level 1, replace it with a level 2
			if (level === 1) {
				replaceRange = headerZone.range;
				replaceText = headerZone.text.replace(/=/g, '-');
			} else {
				// Working with a level 2, so we need to delete the setext line and add a level 3 setext prefix to the previous line
				replaceRange = new Range(lineRange.location, lineRange.length + nextLineRange.length);
				replaceText = '### ' + context.substringWithRange(lineRange);
				selectionBump = 4;
			}
		} else {
			// If working with a level 1, remove the header completely
			if (level === 1) {
				replaceRange = new Range(lineRange.location, lineRange.length + nextLineRange.length);
				replaceText = context.substringWithRange(lineRange);
			} else {
				// Working with a level 2, so drop down to a level 1
				replaceRange = headerZone.range;
				replaceText = headerZone.text.replace(/-/g, '=');
			}
		}
	} else if (headerZone !== null) {
		// We are working with an atx header, so we need to determine what level it is; grab its punctuation zones
		punctuationZone = headerZone.childAtIndex(0);
		if (atxPunctuationSelector.matches(headerZone.childAtIndex(headerZone.childCount - 1))) {
			punctuationEndZone = headerZone.childAtIndex(headerZone.childCount - 1);
		}
		// Determine our level and setup our recipe
		level = punctuationZone.text.length;
		if (dir === 1) {
			replaceText = punctuationZone.text + '#';
			selectionBump = 1;
		} else {
			replaceText = punctuationZone.text.substr(0, punctuationZone.text.length - 1);
			selectionBump = -1;
		}
		// Remove the header tags completely if we are dropping to no header
		if (replaceText.length === 0) {
			selectionBump = -1 * headerZone.text.replace(/^(#+\s*)[\S\s]*?\s*#*[\s\n\r]*$/, '$1').length;
			replaceText = headerZone.text.replace(/^#+\s*([\S\s]*?)\s*#*([\s\n\r]*)$/, '$1$2');
			replaceRange = headerZone.range;
		} else {
			// Otherwise replace our zones with the new header level
			recipe.replaceRange(punctuationZone.range, replaceText);
			if (punctuationEndZone !== null) {
				recipe.replaceRange(punctuationEndZone.range, replaceText);
			}
		}
	} else {
		// We do not have any header at all
		if (dir === 1) {
			replaceRange = lineRange;
			replaceText = '# ' + context.substringWithRange(lineRange);
			selectionBump = 2;
		} else {
			return false;
		}
	}
	
	// Run our recipe!
	if (replaceRange !== null) {
		recipe.replaceRange(replaceRange, replaceText);
	}
	context.applyTextRecipe(recipe);
	
	// Determine our new selection
	if (selectionBump !== 0) {
		curRange = new Range(curRange.location + selectionBump, curRange.length);
	}
	
	context.selectedRanges = [curRange];
	
	// All done!
	return true;
};
