/**
 * JavaScript for pastebin.it
 * @author Daniel15 <dan.cx>
 * @date $Date: 2010-07-24 14:45:50 +0930 (Sat, 24 Jul 2010) $
 * @version $Revision: 89 $
 */
 
// Since we *know* we have JavaScript, we can add the class for it :)
$(document.body)
	.addClass('has-js')
	.removeClass('no-js');

// ---- Begin extensions of natives ----
Element.implement(
{
	setInputsDisabled: function(state)
	{
		this.getElements('input').each(function(el) { el.disabled = state; });
	}
});
// ---- End extensions of natives ----

/**
 * List of all languages we can highlight
 */
var Langs = new Hash(
{
	'raw': 
	{
		js: ['parseraw'],
		css: ['rawcolors'],
		parser: 'RawParser'
	},
	
	'javascript': 
	{
		js: ['tokenizejavascript', 'parsejavascript'],
		css: ['jscolors'],
		parser: 'JSParser'
	},
	
	'html': 
	{
		js: ['parsexml'],
		css: ['xmlcolors'],
		parser: 'XMLParser'
	},
	
	'css': 
	{
		js: ['parsecss'],
		css: ['csscolors'],
		parser: 'CSSParser'
	},
	
	'php': 
	{
		js: [
			'parsexml', 'parsecss', 'tokenizejavascript', 'parsejavascript',
			'tokenizephp', 'parsephp', 'parsephphtmlmixed'
			],
		css: ['phpcolors', 'jscolors', 'xmlcolors', 'csscolors'],
		parser: 'PHPHTMLMixedParser'
	},
	
	'sql': 
	{
		js: ['parsesql'],
		css: ['sqlcolors'],
		parser: 'SqlParser'
	},
	
	'python': 
	{
		js: ['parsepython'],
		css: ['pythoncolors'],
		parser: 'PythonParser'
	},
	
	'cs': 
	{
		js: ['tokenizecs', 'parsecs'],
		css: ['dotnetcolors'],
		parser: 'CsParser'
	},
	
	'aspcs':
	{
	
		js: [
			'parsexml', 'parsecss', 'tokenizejavascript', 'parsejavascript',
			'tokenizecs', 'parsecs', 'parseasphtmlmixed'
			],
		css: ['dotnetcolors', 'jscolors', 'xmlcolors', 'csscolors'],
		parser: 'AspHTMLMixedParser'
	},
	
	// TODO: Change this to a proper C++ parser once one is found / created
	// http://groups.google.com/group/codemirror/browse_thread/thread/635f7d6bfe2aa105
	'cpp':
	{
		js: ['tokenizecs', 'parsecs'],
		css: ['dotnetcolors'],
		parser: 'CsParser'
	}
});

/**
 * Stuff for paste pages
 */
var Paste = {}

/**
 * Stuff for the "create paste" page
 */
Paste.Create =
{
	/* Constants */
	/**
	 * Maximum number of files allowed in a paste 
	 */
	MAX_FILES: 4,
	
	/* Variables */
	/**
	 * Number of files that are currently in the paste 
	 */
	files: 0,
	
	/* Methods */
	
	/**
	 * Initialise the create paste page
	 */
	init: function()
	{
		// Attach our event handlers
		$('add_file').addEvent('click', Paste.Create.add_file);
		$('switch_view').addEvent('click', Paste.Create.switch_view);
		$('fullscreen').addEvent('click', Paste.Create.fullscreen);
		$('exit_fullscreen').addEvent('click', Paste.Create.exit_fullscreen);
		$$('h1')[0].addEvent('dblclick', Paste.Create.expand_header);
		window.addEvent('resize', Paste.Create.reset_sizes);
		
		// Get all the language dropdowns
		dropdowns = $$('select[id$=language_id]')
		dropdowns.addEvent('change', Paste.Create.select_lang);
		
		// Hide all the files by default
		var sections = $$('form#paste section');
		sections.setStyle('display', 'none');
		// File toolbar buttons
		sections.each(function(section)
		{
			// Fullscreen button
			section.getElements('img.fullscreen').addEvent('click', Paste.Create.fullscreen_file.bind(section));
		});
		
		Paste.Create.init_resizers();
		// If we have a captcha, initialise it
		Paste.Create.init_captcha();
		
		// Initialise the editors - Called via a timer to stop some odd bug with loading CodeMirror
		// TODO: Figure out the bug
		Paste.Create.init_editors.delay(10);
		(function()
		{
			// Do we have a hash? We might have to go fullscreen
			if (window.location.hash != '')
			{
				if (window.location.hash == '#fullscreen')
					Paste.Create.fullscreen();
				else if (window.location.hash.substring(0, 12) == '#fullscreen-')
					Paste.Create.fullscreen_file.bind($('file_' + window.location.hash.substring(12)))();			
			}
		}).delay(15);
	},
	
	// Initialise the text editors
	init_editors: function()
	{
		// Check how many files actually currently contain stuff, and show them
		$$('form#paste section').each(function(section)
		{
			// Does this section have some text>
			if (section.getElement('textarea').value != '')
				Paste.Create.add_file();
		});
		// Make sure we have at least one file
		if (Paste.Create.files == 0)
			Paste.Create.add_file();
	},
	
	init_resizers: function()
	{
		var sections = $$('form#paste section');
		// Remove any existing resizers
		$$('.resizer').dispose();
		
		// Are we creating horizontal or vertical resizers?
		var resizerClass;
		var modifiers;
		if ($('paste').hasClass('horiz'))
		{
			resizerClass = 'resizer-horiz';
			modifiers = {x: 'width', y: null};
		}
		else
		{
			resizerClass = 'resizer-vert';
			modifiers = {x: null, y: 'height'};
		}
		
		// Create the resizers. Exclude the first file
		var resizers = new Array();
		sections.slice(1).each(function(file, id)
		{
			resizers.push(new Element('div', 
			{
				'class': 'resizer ' + resizerClass
			}).inject(file, 'top'));
		});
		
		
		// Attach the resizers to the textboxes
		// Each file attaches to the resizer to the left of it (to make it smaller), and the resizer
		// to the right of it (to make it bigger).
		// File 0 -- [Resizer 0] -- File 1 -- [Resizer 1] -- File 2 -- [Resizer 2] -- ...
		sections.each(function(section, id)
		{
			// Left resizer -  Only if it's not the first file
			if (id != 0)
			{
				var drag = new Drag.Paste(section,
				{
					handle: resizers[id - 1],
					modifiers: modifiers,
					invert: true
				});
				
				section.store('resizer-left', drag);
			}
			
			// Right resizer - Only if it's not the last file
			if (id != sections.length - 1)
			{
				var drag = new Drag.Paste(section,
				{
					handle: resizers[id],
					modifiers: modifiers
				});
				
				section.store('resizer-right', drag);
			}
			
		});
	},
	
	/**
	 * Initialise a captcha form, if we have one
	 */
	init_captcha: function()
	{
		var form = $('captcha');
		if (!form)
			return;
			
		// Add the handler to handle people cancelling the captcha
		$('ignorecaptcha').addEvent('click', Paste.Create.hide_flash);
		
		$('removecaptcha').addEvent('click', Paste.Create.process_captcha);
	},
	
	/**
	 * Hide the flashdata thingo
	 */
	hide_flash: function()
	{
		$('flash').set('tween', {duration: 250}).fade('out');
		return false;
	},
	
	/** 
	 * Process a CAPTCHA
	 */
	process_captcha: function()
	{
		var form = $('captcha');
		
		var myRequest = new Request.JSON(
		{
			method: 'post',
			// Eww, Tobias :(
			url: window.location.href,
			data: form.toQueryString() + "&removecaptcha=yesplz",
			onSuccess: function(response)
			{
				form.setInputsDisabled(false);
				form.getElement('img.loading').setStyle('visibility', 'hidden');
				
				// Was it successful?
				if (response.status == true)
				{
					Paste.Create.hide_flash();
					return;
				}
				
				$('captcha_img').src = '/images/spinner.gif';
				$('captcha_img').src = '/captcha/' + Math.round(Math.random() * 10000000000);
				alert('Incorrect code entered, please try again.');
			}
		}).send();
		
		// Disable the buttons, show the loading spinner
		form.setInputsDisabled(true);
		form.getElement('img.loading').setStyle('visibility', 'visible');		
		
		return false;
	},
	
	/**
	 * Called when the browser window is resized. Reset all the files back to default widths
	 */
	reset_sizes: function()
	{
		$$('form#paste section').setStyles(
		{
			width: '',
			height: ''
		});
	},
	
	/**
	 * Add a file to the current paste
	 */
	add_file: function()
	{
		// Check if we've hit the maximum
		if (Paste.Create.files == Paste.Create.MAX_FILES)
		{
			//alert('Sorry, a maximum of ' + Paste.Create.MAX_FILES + ' files are allowed per paste.');
			return false;
		}

		//$('paste').set('class', 'files-' + ++Paste.Create.files);
		$('paste').set('class', $('paste').get('class').replace(/files-[0-9]/, 'files-' + ++Paste.Create.files));
			
		// Show the new paste
		$('file_' + (Paste.Create.files - 1)).setStyle('display', '');
		// Load the editor for this textarea by doing a change event on the language dropdown
		$('paste_files_file_' + (Paste.Create.files - 1) + '_language_id').fireEvent('change');			
		// Set the sizes back to defaults
		Paste.Create.reset_sizes();
		// If we have more than one file, we can display the "switch view" button
		if (Paste.Create.files > 1)
			$('switch_view').setStyle('display', 'block');
		
		return false;
	},
	
	/**
	 * Event handler for when the user chooses a language for a file
	 */
	select_lang: function()
	{
		// Get the file ID from this dropdown's ID
		var file_id = this.id.split('_')[3];
		var textarea = $('paste_files_file_' + file_id + '_code');
		
		// Does this textarea already have a highlighter?
		var editor = textarea.retrieve('editor');
		if (editor && editor.wrapping)
		{
			textarea.value = editor.getCode();
			// TODO: This is UUUUU-GLY!
			$(editor.wrapping).dispose();
			this.store('editor', null);
			textarea.setStyle('display', 'block');
		}
		
		// Handle raw (no highlighting). We're done :D
		if (this.value == '')
		{
			this.store('editor', null);
			return;
		}
		
		// Data for this language
		var lang = Langs.get(this.value);
		if (lang == null)
		{
			alert('Error: Language ' + this.value + ' not found!');
			throw 'Language ' + this.value + ' not found!';
		}
		
		// Add the default CSS file to the list of files to load.
		lang.css.push('default');
		// Load an editor for this language.
		var editor = CodeMirror.fromTextArea(textarea,
		{
			parserfile: lang.js.map(function(file) { return file + '.js' }),
			stylesheet: lang.css.map(function(file) { return '/css/vendor/codemirror/' + file + '.css' }),
			path: '/js/lib/vendor/codemirror/',
			autoMatchParens: false,
			lineNumbers: true,
			tabMode: "shift",
			// Ticket 24: Remove undo functionality
			indentUnit: 0,
		});
		
		textarea.store('editor', editor);
	},
	
	/**
	 * Switch between horizontal and vertical view
	 */
	switch_view: function()
	{
		Paste.Create.reset_sizes();
		
		var icon = this.getElement('img');
		var text = this.getElement('label');
		
		// Is it horizontal now?
		if ($('paste').toggleClass('horiz').toggleClass('vert').hasClass('horiz'))
		{
			icon.set('src', '/images/icons/application_tile_vertical.png');
			text.set('html', 'Switch to vertical view');
		}
		else
		{
			icon.set('src', '/images/icons/application_tile_horizontal.png');
			text.set('html', 'Switch to horizontal view');
		}
		
		Paste.Create.init_resizers();
	},
	
	/**
	 * Make a file fullscreeen
	 */
	fullscreen_file: function()
	{
		$(document.body).addClass('fullscreen-file');
		this.addClass('fullscreen');
		window.location.hash = '#fullscreen-' + this.id.substring(5);	
	},
	
	/**
	 * Make the whole paste fullscreen
	 */
	fullscreen: function()
	{
		$(document.body).addClass('fullscreen');
		window.location.hash = '#fullscreen';
	},
	
	/**
	 * Exit fullscreen mode 
	 */
	exit_fullscreen: function()
	{
		$(document.body).removeClass('fullscreen-file').removeClass('fullscreen');
		$$('form#paste section').removeClass('fullscreen');
		window.location.hash = '';
	},
	
	/**
	 * This method does not exist.
	 */
	expand_header: function()
	{
		var title = document.getElement('h1');		
		
		var frequency1 = 0.3;
		var frequency2 = 0.3;
		var frequency3 = 0.3;
		var phase1 = 0;
		var phase2 = 2;
		var phase3 = 4;
		var center = 128;
		var width = 127;
		var offset = 0;
		
		(function()
		{
			var title_html = '';
			var title_text = title.get('text');
			var colour;
			
			for (var i = 0; i < title_text.length; i++)
			{
				var red = Math.round(Math.sin(frequency1 * i + phase1 + offset) * width + center);
				var green = Math.round(Math.sin(frequency2 * i + phase2 + offset) * width + center);
				var blue = Math.round(Math.sin(frequency3 * i + phase3 + offset) * width + center);
				colour = 'rgb(' + red + ',' + green + ',' + blue + ')';
				title_html += '<span style="color: ' + colour + '; text-shadow: 0px 0px 20px ' + colour + '">' + title_text.charAt(i) + '</span>';
			}
			
			title.set('html', title_html);
			//$(document.body).setStyle('backgroundColor', colour);
			offset += 0.2;
		}).periodical(50);
	}
};

/**
 * "List pastes" page
 */
Paste.List =
{
	/* Constants */
	
	/* Variables */
	// The languages we're loading
	langs: new Hash(),
	
	/* Methods */
	init: function()
	{	
		// Grab all the code blogs
		$$('pre').each(function(codeEl)
		{
			// Skip ones with no language
			if (codeEl.get('class') == '')
				return;
				
			// Data for this language
			var langName = codeEl.get('class').substring(5);
			
			// Add this language to our list of languages to load, and add this code block as something 
			// we have to highlight with it			
			if (!Paste.List.langs.has(langName))
			{
				// Make sure we actually have this language!
				var langData = Langs[langName];
				if (langData == null)
				{
					alert('Error: Language ' + langName + ' not found!');
					return;
				}
				
				Paste.List.langs[langName] = new Hash(
				{
					name: langName,
					// Code blocks that use this language
					code_blocks: [],
					// How many JS files are we waiting on?
					js_files: langData.js.length,
					// TODO: Should be called "files"?
					lang: langData
				});
			}
			
			Paste.List.langs[langName].code_blocks.push(codeEl);
		});
		
		// Load all the languages
		Paste.List.langs.each(function(stuff, langName)
		{
			// Load the JavaScript
			stuff.lang.js.each(function(file)
			{
				Asset.javascript('/js/lib/vendor/codemirror/' + file + '.js', 
				{
					onload: Paste.List.js_loaded.pass(stuff)
				});
			});
			
			// Load the CSS
			stuff.lang.css.each(function(file)
			{
				Asset.css('/css/vendor/codemirror/' + file + '.css');
			});
		});
	},
	
	/**
	 * Called when the JS file for a language has loaded
	 */
	js_loaded: function(lang)
	{
		if (--lang.js_files == 0)
		{
			// Highlight the code blocks for this language
			lang.code_blocks.each(function(codeEl)
			{
				var code = codeEl.get('text');
				codeEl.set('html', '');
				highlightText(code, codeEl, eval(lang.lang.parser));
			});
		}
	}
};

/**
 * A custom drag class used on the paste page
 */
Drag.Paste = new Class(
{
	Extends: Drag,
	options: 
	{
		//modifiers: {x: 'width', y: null},
		// Before we start, let's make the resizer cover the whole window. This is so it's above the iframes
		onBeforeStart: function() { this.handles.addClass('resizer-active'); },
		// And on complete, let's put it back to how it was
		onComplete: function() { this.handles.removeClass('resizer-active'); }
	}
});

/**
 * Stuff for every page
 */
var Page = 
{
	init: function()
	{
		Page.resize();
		window.addEvent('resize', Page.resize);
	},
	
	resize: function()
	{
		/* Yes, this is a terrible, ugly, horrible HACK. EWW. I hate even looking at it. However,
		 * it's here to work around a problem in Opera and sometimes Google Chrome - When on the 
		 * page, a vertical scrollbar randomly appears when it's not needed. Removing the heading 
		 * (or not rotating it) seems to fix it, but I can't see how to keep the heading rotated 
		 * AND only show a scrollbar when needed.
		 *
		 * Remove this once this problem is worked out.
		 */
		var page_height = $('wrap').getSize().y;
		var window_height = window.getSize().y
		// If page height is <= window height, we don't need scrollbars
		$(document.body).setStyle('overflow-y', page_height <= window_height ? 'hidden' : '');
	}
};
window.addEvent('domready', Page.init);