/* Constants */
var HB_FAIL_MAX       = 64;  // Max polling rate on server error
var HB_NO_NEW_MAX     = 32;  // Max polling rate on no new messages
var HB_BASE_POLL      = 1;   // Base polling rate (sec)
var HB_NO_NEW_MULTI   = 2;   // Multiplier for every request with no new posts
var HB_FAIL_MULTI     = 2;   // Multiplier for every failed request
var HB_TIMEOUT_RETRY  = 60;  // Min. seconds to wait for new poll if one is already running - disabled!
var INIT_MAX_TRIES    = 3;   // Max. number of tries to connect to server before giving up
var GOOD_MSG_STAY     = 10;  // How long to keep good message displayed for (sec)
var BAD_MSG_STAY      = 60;  // How long to display bad message (sec)
var MAX_SCROLL_DIST   = 200; // Max. distance between bottom of user's screen and bottom of content
                             // div for auto-scrolling to be enabled
var USER_SCROLL_DELAY = 300; // Max time to wait after a user scroll event before we start
                             // auto-scrolling again (ms)
var MAX_HISTORY       = 30; // Maximum number of (merged) posts to store
var PING_RATE         = 300; // Activity ping rate, in seconds
var SZ_DEFAULT_TITLE  = "Slipbox Z"; // Initial title for page
var SZ_VERSION        = 2008090801;

/* Global variables - will be namespaced into objects later */
var init_tries = 0;
var poll_running = false;
var poll_last_check = 0;
var poll_last_activity_ping = 0;
var poll_last_id = 0;
var poll_heartbeat_multi = HB_BASE_POLL;
var poll_heartbeat_max = HB_NO_NEW_MAX;
var poll_heartbeat_reset = false;
var poll_error = 0;
var poll_func_ptr;
var poll_request_obj;
var in_auto_scroll_zone = false;
var user_scroll_handled = false; // If the user manually scrolls, we need to recalculate
var user_info = null; // stores all info on current user
var current_posts = $H({}); // post block starters only ( => 1)
var current_posts_inc_merged = $H({}); // all posts ( => post)
var merge_link = {}; // maps a block starter to 'child' posts (for deletion)
var user_list = $A([]);
var raw_topic = '';
var search_in_progress = false;
var pm_last_post; // Post merging
var handled_query_string = false;  // Only handle query string once per page load
var ignore_topic_link_click = 0;   // Stop topic link clicks from triggering an edit box
var ignore_close_window_event = 0; // Stop file upload from triggering a user part message
var ignore_scroll_event = 0;       // Ignore onScroll events caused by scripts
var post_max_size = 4096;          // Maximum post size (bytes)
var new_message_count = 0;         // New message count (for minimised/other tab)
var swfu = 0;                      // SWF Uploader object
var cookie_jar;
var no_images = 0;                 // Disable inline [img] and uploaded images, turning them into links

/* Make a generic server request with the arguments in args. The callback is called with a
Javascript data structure containing the results of the request. On failure, invokes the
callback with the magic string 'fail' */
function makeRequest(service, args, callback, async) {
    if(!args) args = {};
    args.service = service;
    if(!async) async = true;
    return new Ajax.Request('server.php', {
        method: 'post',
        asynchronous: true,
        onSuccess: function(t) {
            if(t.responseText) {
                data = t.responseText.evalJSON();
                if(callback) callback(data);
            }
            else callback('fail');
        },
        onFailure: function(t) {
            if(callback) callback('fail');
        },
        onException: function(req, ex) {
            str = "HORRIBLE REQUEST EXCEPTION!!\n\n";
            str += "Name: " + ex.name + "\n";
            str += "Message: " + ex.message + "\n";
            str += "Full string: " + ex + "\n\n";
            if(ex.stack) {
                str += "File: " + ex.fileName + " Line: " + ex.lineNumber + "\n";
                str += "Stack trace:\n\n" + ex.stack;
            }
            alert(str);
            return true;
        },
        parameters: $H(args).toQueryString()
    });
}

/* Make a synchronous request. Not recommended except for very special circumstances (e.g. onUnload) */
/* Currently has no effect - all requests are async for now */
function makeSyncRequest(service, args, callback) {
    makeRequest(service, args, callback, false);
}

/* Alert on error */
function trap_error(error, url, line) {
    alert("Error!\nURL: " + url + "\nLine: " + line + "\n\n" + error);
    return true;
}

/* First get the last 50 posts, current topic and user */
function init() {
    window.onerror = trap_error;
    document.title = SZ_DEFAULT_TITLE;
    gradientText.set($('slipboxTitleGradient'));
    handleQueryStringParams();
    getInitialPosts();
    document.onmousemove = clearNewPostCount;
    document.onkeydown = clearNewPostCount;
    window.onunload = closeWindow;
    createSwfUploader();
    createCalendars();
    cookie_jar = new CookieJar({ expires: 31536000 });
    updateHelpText();
    loadPanelSettings();
    showSidebar();
}

window.onload = init;

/* Callback for initPosts - sets topic and adds post history, updates UI */
function receiveInitialPosts(data) {
    if(data == 'fail') {
        if(init_tries == INIT_MAX_TRIES) {
            // We've tried thrice. Looks like the server is down.
            alert('Could not connect to Slipbox server. Please try again later.');
        }
        else {
            // Try again
            makeRequest('initPosts', receiveInitialPosts);
            init_tries++;
        }
    }
    else {
        if(data.version > SZ_VERSION) {
            alert('The Slipzone has been updated. After clicking OK, please press Ctrl+R to load the changes into your browser.');
        }
    
        if(data.user) userLogin(data.user);
        else userLogout();
        
        /* Set up UI */
        updateTopic(data.topic);
        $('postFrame').show();
        $('topicFrame').show();
        if(data.user_list) userListUpdate(data.user_list);
        $('sidePanels').show();
        $('pnlUserList').show();
        $('searchForm').show();
        $('helpBox').show();
        /* show good/bad message, hide sidebar if needed, set no_images = 0 if needed (for addPosts)
        handleQueryStringParams();
        /* Set up message list */
        addPosts(data.posts, true);
        scrollToBottom();
        /* Set up new posts callback */
        poll_last_activity_ping = new Date();
        poll_func_ptr = setInterval(poll, 100);
        clearBadMessage();
    }
}

/* Triggers AJAX request for initPosts */
function getInitialPosts() {
    makeRequest('initPosts', {}, receiveInitialPosts);
}

/* Mark user inactive when he or she closes the window (if logged in) */
function closeWindow() {
    // We might ignore this event if we know the page will be reloaded, e.g. for file uploads
    if(user_info && !ignore_close_window_event) makeSyncRequest('closeWindow');
}

// COOKIE FUNCTIONS
var CookieJar = Class.create();

CookieJar.prototype = {

	/**
	 * Append before all cookie names to differntiate them.
	 */
	appendString: "__CJ_",

	/**
	 * Initializes the cookie jar with the options.
	 */
	initialize: function(options) {
		this.options = {
			expires: 3600,		// seconds (1 hr)
			path: '',			// cookie path
			domain: '',			// cookie domain
			secure: ''			// secure ?
		};
		Object.extend(this.options, options || {});

		if (this.options.expires != '') {
			var date = new Date();
			date = new Date(date.getTime() + (this.options.expires * 1000));
			this.options.expires = '; expires=' + date.toGMTString();
		}
		if (this.options.path != '') {
			this.options.path = '; path=' + escape(this.options.path);
		}
		if (this.options.domain != '') {
			this.options.domain = '; domain=' + escape(this.options.domain);
		}
		if (this.options.secure == 'secure') {
			this.options.secure = '; secure';
		} else {
			this.options.secure = '';
		}
	},

	/**
	 * Adds a name values pair.
	 */
	put: function(name, value) {
		name = this.appendString + name;
		cookie = this.options;
		var type = typeof value;
		switch(type) {
		  case 'undefined':
		  case 'function' :
		  case 'unknown'  : return false;
		  case 'boolean'  : 
		  case 'string'   : 
		  case 'number'   : value = String(value.toString());
		}
		var cookie_str = name + "=" + escape(Object.toJSON(value));
		try {
			document.cookie = cookie_str + cookie.expires + cookie.path + cookie.domain + cookie.secure;
		} catch (e) {
			return false;
		}
		return true;
	},

	/**
	 * Removes a particular cookie (name value pair) form the Cookie Jar.
	 */
	remove: function(name) {
		name = this.appendString + name;
		cookie = this.options;
		try {
			var date = new Date();
			date.setTime(date.getTime() - (3600 * 1000));
			var expires = '; expires=' + date.toGMTString();
			document.cookie = name + "=" + expires + cookie.path + cookie.domain + cookie.secure;
		} catch (e) {
			return false;
		}
		return true;
	},

	/**
	 * Return a particular cookie by name;
	 */
	get: function(name) {
		name = this.appendString + name;
		var cookies = document.cookie.match(name + '=(.*?)(;|$)');
		if (cookies) {
			return (unescape(cookies[1])).evalJSON();
		} else {
			return null;
		}
	},

	/**
	 * Empties the Cookie Jar. Deletes all the cookies.
	 */
	empty: function() {
		keys = this.getKeys();
		size = keys.size();
		for(i=0; i<size; i++) {
			this.remove(keys[i]);
		}
	},

	/**
	 * Returns all cookies as a single object
	 */
	getPack: function() {
		pack = {};
		keys = this.getKeys();

		size = keys.size();
		for(i=0; i<size; i++) {
			pack[keys[i]] = this.get(keys[i]);
		}
		return pack;
	},

	/**
	 * Returns all keys.
	 */
	getKeys: function() {
		keys = $A();
		keyRe= /[^=; ]+(?=\=)/g;
		str  = document.cookie;
		CJRe = new RegExp("^" + this.appendString);
		while((match = keyRe.exec(str)) != undefined) {
			if (CJRe.test(match[0].strip())) {
				keys.push(match[0].strip().gsub("^" + this.appendString,""));
			}
		}
		return keys;
	}
};