//
//
//	Add something about how the location has changed, please set your ypath!
//	Or a FAQ item about it
//


// yPath

String.prototype.rep = function(find, replace) { return this.split(find).join(replace); };
String.prototype.trim = function() { return this.replace(/^\s+|\s+$/g, ''); };

var YShout = function() {
	var self = this;
	var args = arguments;
	$(function() { self.init.apply(self, args); });
}

var yShout;

YShout.prototype = {
	debug: true,

	// Will be extended by the actual theme
	theme: { 
		strings: {
			enterUsername: 'Enter your name',
			enterMessage: 'Enter a message',
			submit: 'Shout'
		},
	
		settings: {
			inputs: {
				// Show the username input box
				'username': true,
	
				// Show the message input box
				'message': false,
	
				// Show the URL input box
				'url': false
			}
		}
	},

	timers: {
		refresh: null,
	//	username: null,
		flood: null
	},

	// Information about YShout's current state
	state: {
		updating: false,
		initializing: true,
		posts: 0,
		username: null,
		flood: false
	},

	// The main initialize function
	init: function(options) {
		this.d('In init.')

		var self = this;
		yShout = this;
		
		// We're beginning to load new data
		this.state.updating = true;

		var dOptions = {
			yPath: '/yshout/',
			log: 1
		};

		// Apply user options
		this.options = jQuery.extend(dOptions, options);

		// Correct for missing trailing /
		if ((this.options.yPath.length > 0) && (this.options.yPath.charAt(this.options.yPath.length - 1) != '/'))
			this.options.yPath += '/';

		// Exit if there's no YShout div on the page
		if ($('#yshout').length == 0) return;

		this.d('Firing off the AJAX request.');

		// And finally send off the AJAX load request!
		this.ajax(this.load, {
			reqType: 'init',
			yPath: this.options.yPath + 'script/',
			log: this.options.log
		});

		this.d('Request sent.');
	},


	// Process information from the initial load, or a reload
	load: function(updates) {
		this.d('-');
		this.d('In load().');

		// TODO: This might not work yet
//		if (updates.yError) alert('There appears to be a problem: \n' + updates.yError + '\n\nIf you haven\'t already, try chmodding everything inside the YShout directory to 777.');

		var self = this;

		// If they are an admin, make sure the Javascript knows.
		if (updates.admin)
			this.state.admin = updates.admin;

		// Set settings
		this.settings = jQuery.extend(updates.settings, this.options.settings);

		// Wait for the theme JS to load before continuing
		this.loadTheme(function() {
			this.d('Creating form...');

			// Create the form and set up event handlers
			self.form.apply(self);

			// Start the refresh timer
			self.refresh.apply(self);

			// Set the username if one was provided (this means they've used YShout before)
			if (updates.username)
				;//self.nick(updates.username);

			// And process the updates (posts, userlist, etc.)
			if (updates) self.updates(updates);

			// TODO: Perhaps make this scroll the userlist too
			// Scroll to the bottom of the post list
			self.scroll();

			self.d('Calling theme loaded() event.');
			setTimeout(function() {
				self.theme.event.loaded.apply(self);
			}, 1);
			
			// We're done with the initial/re load!
			self.state.updating = false;
			self.state.initializing = false;
		//this.d('-');
		});


	},

	// Load the theme (CSS and JS)
	loadTheme: function(onComplete) {
		this.d('-');
		this.d('in loadTheme().');

		var self = this;

		// Hide the YShout div until the theme is loaded
		$('#yshout').css('display', 'none');

		// Create the stylesheet
		var stylesheet = this.options.yPath + 'themes/' + this.settings.theme + '/lib/css/style.css';
		$('<link id="ys-stylesheet" rel="stylesheet" type="text/css" href="' + stylesheet + '" />')
			.appendTo('head');

		this.d('Stylesheet created. Getting script...')

		// Download and execute the theme JS
		$.getScript(this.options.yPath + 'themes/' + this.settings.theme + '/lib/js/main.js', function() {
			$('#ys-stylesheet').ready(function() {
				self.d('CSS downloaded. Showing YShout...');
				// Show YShout; the theme is loaded.
				$('#yshout').css('display', 'block');

				onComplete.apply(self);
			});
		});
},

	// Check for new posts/users
	refresh: function() {
		var self = this;

		if (this.timers.refresh) clearInterval(this.timers.refresh)

		this.timers.refresh = setInterval(function() {
	//		self.d('Refreshing...');
			self.ajax(self.updates, { reqType: 'refresh' });
		}, this.settings.refreshRate);
	},

	// Set up the input form and its events
	form: function() {
		var self = this;
/* TODO: Re-add the settings.inverse -- well, settings */

		this.d('Creating HTML...');

		this.theme.html.create.apply(this);

		this.d('Hooking up events...');

		var keypress = function(e) { 
			var key = window.event ? e.keyCode : e.which; 
			if (key == 13 || key == 3) {
				switch (true) {
					case $(this).is('#ys-input-username'): self.username.apply(self, [this.value]); break;
					case $(this).is('#ys-input-message'): self.send.apply(self); break;
				}
				return false;
			}
		};

		$('#ys-input-submit').click(function() { self.send.apply(self); return false; });

		$('#ys-username-status').click(function() { $('#ys-input-username').focus(); });

		/*$('#ys-input-username').blur(function() {
			if ($(this).val() == '') return;
			self.nick.apply(self); 
		});*/

		$('#ys-input-message, #ys-input-username').keypress(keypress).one('focus', function() { if ($(this).is('.ys-before-focus')) $(this).removeClass('ys-before-focus').addClass('ys-after-focus').val(''); });

	},

	// Name = username, set = is it already set serverside? (This might not be their first visit)
	username: function(name, set) {
		this.d('Setting username...');
		
		var self = this;
		
		// If they have a name to use by default, just set the text box
		if (set) {
			this.d('Username already set.');
			$('#ys-input-username').removeClass('ys-before-focus').addClass('ys-after-focus').val(name)
			return;
		}
		
		this.theme.event.naming.apply(this);
		
		this.ajax([function(json) {
			self.error('');
			self.d('Username set.');

			self.state.username = name;

			// TODO: Also set the username on messages
			$('#ys-users .ys-self .ys-user-username').html(json.username);
			$('#ys-posts .ys-self-post .ys-post-username').html(json.username);
			
			self.theme.event.named.apply(self, [json]);

		}, self.theme.event.namingError], {
			reqType: 'username',
			username: $('#ys-input-username').val()
		});
	},

	// Pass an argument to set immediately. (This assumes the server knows already)
	nick: function(username) {
		var self = this;
		var n = $('#ys-input-username'), m = $('#ys-input-message');

		// Set just updates the username locally
		var set = function(name) {
			// TODO: What's this after-focus stuff doing here..?
			// Edit: Got it.
			
 
			
		};


		if (username) {	set(username); return; }
		// ^^^ TODO: Remove all the above.

		// If it's already set then just focus the message box
		if (n.val() == this.state.username) { m.focus(); return; }

		 
		 
		// Otherwise, ping the server to let it know / validate
		this.ajax([function(json) {
			self.error('');

			// TODO: Also set the username on messages
			set(json.username);
			$('#ys-users .ys-self .ys-user-username').html(json.username);
			m.attr('disabled', false).focus();
			self.d('Userame set.');

		}, function() {
			// There was an error. Figure out what it is.
			self.d('Error setting username.');

			// Invalid username?
			self.error ('You need to enter a valid username.');

			// ...Or is it already taken?
			var name = n.val();
			for (var i = 0, u = $('#ys-users')[0].users; i < u.length; i++)
				if (u[i].username == name)
					self.error(name + ' is already in use.');

			$('#ys-input-username, #ys-input-message').attr('disabled', false);

			// Set it back to what it was
			n.val(this.state.username);

			// And select the text.
			n[0].selectionStart = self.settings.usernameLength;
			n[0].selectionEnd = self.settings.usernameLength;

			// Undisable the message box.
		//	$('#ys-input-username, #ys-input-message').attr('disabled', false)
		}], {
			reqType: 'username',
			username: n.val()
		});

		// Disable the box while things are being set
		//$('#ys-input-username, #ys-input-message').attr('disabled', true)
		n.addClass('loading');

	},

	// Process updates from the server
	updates: function(updates) {
		if (!updates) return;

		this.d('In updates: ' + updates);

		if (updates.settings) this.settings = updates.settings;
		if (updates.posts) this.posts(updates.posts);
		if (updates.users) this.users(updates.users);
		if (updates.banned) this.banned();
		if (updates.flood) this.flood();
	},

	// This means flood control hsa been turned on
	flood: function() {
		var self = this;
		this.error('Be a nice person and stop flooding.');

		this.state.flood = true;

		$('#ys-input-username, #ys-input-message').attr('disabled', true);

		clearTimeout(this.timers.flood);

		this.timers.flood = setTimeout(function() {
			$('#ys-input-username, #ys-input-message').attr('disabled', false);
			self.state.flood = false;
			self.error('');

		// TODO: Make this the same time as the server is disabling for
		}, 5000);
	},

	// Process posts
	posts: function(p) {
		for (var i = 0; i < p.length; i++)
			this.post(p[i]);

		this.truncate();
		this.scroll();
	},


	// Todo later: 
	// Let the theme make the HTML. Then add the events and stuff from here still.
	post: function(post) {
		var self = this;

		this.state.posts++;
		var id = 'ys-post-' + this.state.posts;

		// Set up the post with information
		post.id = id;
		post.even = this.state.posts % 2 == 0;
		post.message = this.links(post.message);
		post.message = this.smileys(post.message);
		post.message = this.bbcode(post.message);


		// Get the HTML from the theme
		var html = this.theme.html.post(post);

		// TODO: Move this off to the theme too: this.theme.event.post(post) or something
		if (/* this.prefs.inverse*/ false) 
			$('#ys-posts').createPrepend(html);
		else
			$('#ys-posts').createAppend(html);

		this.ends();

		// Get the html element corresponding to the post and
		// Make it remember all of its details 
		var p = $('#' + id)[0];
		p.post = post;

		// TODO: Offload this to the theme / Highlight breaks in IE
		//if (!this.state.updating && !$.browser.ie)
		//	$('#' + id).Highlight(this.anim.speed, '#f2f2f2');

		// Add event handlers
		$('#' + id)
			.hover(function() {
				$(this).addClass('hover');
			}, function() {
				$(this).removeClass('hover');
			})
			.find('.ys-ban-link').click(function() {
				switch (this.state) {
					case 'ban':	self.ban.apply(self, [p]); break;
					case 'unban': self.unban.apply(self, [p]); break;
				}

				return false;
			}).end()
			.find('.ys-delete-link').click(	function() { self.del.apply(self, [p, this]); return false; });

		// Fire off theme event
		this.theme.event.post.apply(this, [$('#' + id)[0]]);

	},

	ban: function(p) {
		var self = this;
		var link = $('.ys-ban-link', p)[0];

		this.ajax([function(json) {
			if (json.self)
				self.banned.apply(self);
			else
				$('.ys-post[@ip=' + p.ip + ']')
					.addClass('ys-banned-post')
					.find('.ys-ban-link').html('Unban').each(function() { this.state = 'unban'; });

		}, function() {
			link.innerHTML = 'Ban';
			link.state = 'ban';
		}], {
			reqType: 'ban',
			ip: p.ip,
			username: p.post.username
		});

		link.innerHTML = 'Banning...';
		link.state = 'banning';
	},

	unban: function(p) {
		var self = this;
		var link = $('.ys-ban-link', p)[0];

		this.ajax([function(json) {
			$('.ys-post[@ip=' + p.ip + ']')
				.removeClass('ys-banned-post')
				.find('.ys-ban-link').html('Ban').each(function() { this.state = 'ban'; });
		}, function() {
			link.innerHTML = 'Unban';
			link.state = 'unban';
		}], {
			reqType: 'unban',
			ip: p.ip
		});

		link.innerHTML = 'Unbanning...';
		link.state = 'unbanning';
	},

	del: function(p, link) {
		var self = this;

		if (link.state == 'deleting') return;

		this.ajax([function(json) {
			if (json.posts) {
				self.state.updating = true;
				self.truncate(0);
				self.posts.apply(self, [json.posts]);
				self.state.updating = false;
			}
		}, function() {
			link.innerHTML = 'Delete';
			link.state = 'delete';
		}], {
			reqType: 'delete',
			uid: p.uid
		});

		link.innerHTML = 'Deleting...';
		link.state = 'deleting';

	},

	// Parse users in the userlist
	users: function(u) {
		$('#ys-users').html('')[0].users = u;
		for (var i = 0; i < u.length; i++)
			this.user(u[i]);
	},

	user: function(user) {
		this.d('Processing user: ' + user.username);

		$('#ys-users').createAppend(
			'li', { className: 'ys-user' + (user.self ? ' ys-self' : '')}, [
				'span', { className: 'ys-user-username' }, user.username
			]
		)[0].user = user;
	},

	scroll: function() {
		if (!/*this.prefs.inverse*/ false) {
			var postsDiv = $('#ys-posts')[0];
			postsDiv.scrollTop = postsDiv.scrollHeight;
		}
	},

	send: function() {
		if (!this.validate()) return;

		this.error('');

		var  postusername = $('#ys-input-username').val(), postMessage = $('#ys-input-message').val();

		this.ajax(this.updates, {
			reqType: 'post',
			username: postusername,
			message: postMessage
		});

		$('#ys-input-message').val('');
		
		this.theme.event.posting.apply(this);
		// TODO: Make this theme-dependent
	//	$('#ys-post-form').Highlight(this.anim.speed, '#e8e99d');
	},

	// Redo to shake or fade the needed input
	validate: function() {
		if ($('#ys-input-message').val().trim() == '') {
			this.error('You need to enter a message.')
			$('#ys-input-message').focus();
			return false;
		}

		if (!this.state.username) {
			this.error('You need to enter a username.')
			$('#ys-input-username').focus();
			return false;
		}

		return true;
	},

	banned: function() {
		var self = this;

		clearInterval(this.timers.refresh);

		$('#yshout').html('').addClass('ys-banned').createPrepend('div', { id: 'ys-banned' }, [
			'span', { }, 'You\'ve been banned! ',
			(this.state.admin ? [
				'a', { id: 'ys-unbanself-link', href: '#' }, 'Click here to unban yourself.'
			] : [ ])
		]);

		$('#ys-unbanself-link').click(function() {
			self.state.updating = true;
			self.ajax(function(json) {
				$('#yshout').removeClass('ys-banned');
				$('#ys-banned').remove();
				self.load.apply(self, [json]);
			}, {
				reqType: 'unbanself',
				log: self.options.log
			});

			$('#ys-unbanself-link').html('Unbanning...');
			return false;
		});
	},

	// Remove old messages
	truncate: function(n) {

		// If you want 0 posts left, just remove all of them
		if (n == 0) { $('#ys-posts').html(''); return; }


		var to = n ? n : this.settings.display_show;
		var posts = $('#ys-posts .ys-post').length;

		/* Do something for truncating */
		if (posts <= to) return;

		// Was this.settings.doTruncate instead of true
		if (true || this.state.updating) // TODO: Why updating?
			if (/*this.prefs.inverse*/ false)
				$('#ys-posts .ys-post:gt(' + (to - 1) + ')').remove();
			else
				$('#ys-posts .ys-post:lt(' + (posts - to) + ')').remove();
	},


	// TODO: Move this to the theme.
	// Update the .ys-first and .ys-last classes
	ends: function() {
		$('#ys-posts')
			.find('.ys-first').removeClass('ys-first').end()
			.find('.ys-last').removeClass('ys-last');

		$('#ys-posts .ys-post:first-child').addClass('ys-first');
		$('#ys-posts .ys-post:last-child').addClass('ys-last');
	},


	// This is a full reload, almost like the initial one
	reload: function(everything) {
		var self = this;
		this.state.updating = true;

		if (everything) {
			this.ajax(function(json) {
				$('#yshout').html('');
				clearInterval(this.timers.refresh);
				this.load(json);
			}, {
				reqType: 'init',
				yPath: this.options.yPath,
				log: this.options.log
			});
		} else {
			this.ajax(function(json) { this.truncate(0); this.updates(json); this.state.updating = false; }, {
				reqType: 'reload'
			});
		}
	},


	// TODO: Move this to the theme
	error: function(message) {
		return;
		
		// TODO: Redo.
		// A bit of a hacky workaround, but it'll do for now.
		if (!this.state.flood)
			if (message)
				$('#ys-form-error')
					.html('<em>Notice:</em> ' + message)
					.css('display', 'block');
			else
				$('#ys-form-error').css('display', 'none');

	},

	// Parse links
	links: function(s) { return s.replace(/((https|http|ftp|ed2k):\/\/[\S]+)/gi, '<a  href="$1" target="_blank">$1</a>'); },


	// Parse basic bbCode
	bbcode: function(s) {
		s = s.rep('[i]', '<i>');
		s = s.rep('[/i]', '</i>');
		s = s.rep('[I]', '<i>');
		s = s.rep('[/I]', '</i>');

		s = s.rep('[b]', '<b>');
		s = s.rep('[/b]', '</b>');
		s = s.rep('[B]', '<b>');
		s = s.rep('[/B]', '</b>');

		s = s.rep('[u]', '<u>');
		s = s.rep('[/u]', '</u>');
		s = s.rep('[U]', '<u>');
		s = s.rep('[/U]', '</u>');

		return s;
	},

	// Parse smileys
	smileys: function(s) {
		var yp = this.options.yPath;

		var smile = function(str, smiley, image) { return str.rep(smiley, '<img title="' + smiley + '" src="' + yp + 'script/lib/img/smileys/' + image + '" />'); };

		s = smile(s, ':twisted:',  'twisted.gif');
		s = smile(s, ':cry:',  'cry.gif');
		s = smile(s, ':\'(',  'cry.gif');
		s = smile(s, ':shock:',  'eek.gif');
		s = smile(s, ':evil:',  'evil.gif');
		s = smile(s, ':lol:',  'lol.gif');
		s = smile(s, ':mrgreen:',  'mrgreen.gif');
		s = smile(s, ':oops:',  'redface.gif');
		s = smile(s, ':roll:',  'rolleyes.gif');

		s = smile(s, ':?',  'confused.gif');
		s = smile(s, ':D',  'biggrin.gif');
		s = smile(s, '8)',  'cool.gif');
		s = smile(s, ':x',  'mad.gif');
		s = smile(s, ':|',  'neutral.gif');
		s = smile(s, ':P',  'razz.gif');
		s = smile(s, ':(',  'sad.gif');
		s = smile(s, ':)',  'smile.gif');
		s = smile(s, ':o',  'surprised.gif');
		s = smile(s, ';)',  'wink.gif');

		return s;
	},

	// Get a date string. TS = timestamp, Mini = xx/xx/xx
	date: function(ts, mini) {
		var t = new Date(ts * 1000);

		var Y = t.getFullYear();
		var d = t.getDate();
		var day = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'][t.getDay()];
		var mon = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'][t.getMonth()];

		if (mini) return t.getMonth() + '/' + t.getDate() + '/' + Y;
		return day + ' ' + mon + '. ' + d + ', ' + Y;
	},

	// Get the time in human-readable format
	time: function(ts) {
		var d = new Date(ts * 1000);
		var h = d.getHours(), m = d.getMinutes();

		if (this.settings.display_timestampFormat == 12) {
			h = (h > 12 ? h - 12 : h);
			if (h == 0) h = 12;
		}

		var pad = function(n) { return n > 9 ? n : '0' + n; };

		return /*pad(h)*/ h + ':' + pad(m);
	},

	// Parse the text into JSON
	json: function(parse) {
		this.d('In json: ' + parse);

		if (parse == '') return { };
		return eval('(' + parse + ')');
	},

	// Ajax Request
	ajax: function(callback, pars) {
		pars = jQuery.extend({
			reqFor: 'shout'
		}, pars);

		var self = this;

		// Fire off the request
		$.post(this.options.yPath + 'script/yshout.php', pars, function(parse) {
			if (parse) parse = self.json(parse);
			// If there's just one callback then call it
			if (typeof callback == 'function')
				if (parse)
					callback.apply(self, [parse]);
				else
					callback.apply(self);

			// If there are two, then call the first or second one depending on the
			// success of the request. This is a YShout success/fail thing, like if
			// the person you wanted to ban actually got successfully banned.
			else 
				if (parse.success)
					callback[0].apply(self, [parse]);
				else
					callback[1].apply(self, [parse]);
		});
	},

	// Debug function, right now it just calls the control panel's debug function
	d: function(title, message) {
		if (!this.debug) return;

		if (d) d(title, message);
		return message ? message : title;
	}

};

