/*
	Packer version 3.0 (beta 7) - copyright 2004-2007, Dean Edwards
	http://www.opensource.org/licenses/mit-license
*/

eval(base2.namespace);

var IGNORE = RegGrp.IGNORE;
var REMOVE = "";
var SPACE = " ";
var WORDS = /\w+/g;

var Packer = Base.extend({
	minify: function(script) {
		script = script.replace(Packer.CONTINUE, "");
		script = Packer.data.exec(script);
		script = Packer.whitespace.exec(script);
		script = Packer.clean.exec(script);
		return script;
	},
	
	pack: function(script, base62, shrink) {
		script = this.minify(script + "\n");
		if (shrink) script = this._shrinkVariables(script);
		if (base62) script = this._base62Encode(script);	
		return script;
	},
	
	_base62Encode: function(script) {
		var words = new Words(script);
		var encode = function(word) {
			return words.fetch(word).encoded;
		};
		
		/* build the packed script */
		
		var p = this._escape(script.replace(WORDS, encode));		
		var a = Math.min(Math.max(words.count(), 2), 62);		
		var c = words.count();		
		var k = words;
		var e = Packer["ENCODE" + (a > 10 ? a > 36 ? 62 : 36 : 10)];
		var r = a > 10 ? "e(c)" : "c";
		
		// the whole thing
		return format(Packer.UNPACK, p,a,c,k,e,r);
	},
	
	_escape: function(script) {
		// single quotes wrap the final string so escape them
		// also escape new lines required by conditional comments
		return script.replace(/([\\'])/g, "\\$1").replace(/[\r\n]+/g, "\\n");
	},
	
	_shrinkVariables: function(script) {
		// Windows Scripting Host cannot do regexp.test() on global regexps.
		var global = function(regexp) {
			// This function creates a global version of the passed regexp.
			return new RegExp(regexp.source, "g");
		};
		
		var data = []; // encoded strings and regular expressions
		var store = function(string) {
			var replacement = "#" + data.length;
			data.push(string);
			return replacement;
		};
		
		// Base52 encoding (a-Z)
		var encode52 = function(c) {
			return (c < 52 ? '' : arguments.callee(parseInt(c / 52))) +
				((c = c % 52) > 25 ? String.fromCharCode(c + 39) : String.fromCharCode(c + 97));
		};
				
		// identify blocks, particularly identify function blocks (which define scope)
		var BLOCK = /(function\s*[\w$]*\s*\(\s*([^\)]*)\s*\)\s*)?(\{([^{}]*)\})/;
		var VAR_ = /var\s+/g;
		var VAR_NAME = /var\s+[\w$]{2,}/g; // > 1 char
		var COMMA = /\s*,\s*/;
		var blocks = []; // store program blocks (anything between braces {})
		// encoder for program blocks
		var encode = function(block, func, args) {
			if (func) { // the block is a function block
			
				// decode the function block (THIS IS THE IMPORTANT BIT)
				// We are retrieving all sub-blocks and will re-parse them in light
				// of newly shrunk variables
				block = decode(block);
				
				// create the list of variable and argument names 
				var vars = match(block, VAR_NAME).join(",").replace(VAR_, "");
				var ids = Array2.combine(args.split(COMMA).concat(vars.split(COMMA)));
				
				// process each identifier
				var count = 0, shortId;
				forEach (ids, function(id) {
					id = rescape(trim(id));
					if (id) {
						// find the next free short name (check everything in the current scope)
						do shortId = encode52(count++);
						while (new RegExp("[^\\w$.]" + shortId + "[^\\w$:]").test(block));
						// replace the long name with the short name
						var reg = new RegExp("([^\\w$.])" + id + "([^\\w$:])");
						while (reg.test(block)) block = block.replace(global(reg), "$1" + shortId + "$2");
						var reg = new RegExp("([^{,])" + id + ":", "g");
						block = block.replace(reg, "$1" + shortId + ":");
					}
				});
			}
			var replacement = "~" + blocks.length;
			blocks.push(block);
			return replacement;
		};
		
		// decoder for program blocks
		var ENCODED = /~(\d+)/;
		var decode = function(script) {
			while (ENCODED.test(script)) {
				script = script.replace(global(ENCODED), function(match, index) {
					return blocks[index];
				});
			}
			return script;
		};
		
		// encode strings and regular expressions
		script = Packer.data.exec(script, store);
		
		// remove closures (this is for base2 namespaces only)
		script = script.replace(/new function\(_\)\s*\{/g, "{;#;");
		
		// encode blocks, as we encode we replace variable and argument names
		while (BLOCK.test(script)) {
			script = script.replace(global(BLOCK), encode);
		}
		
		// put the blocks back
		script = decode(script);
		
		// put back the closure (for base2 namespaces only)
		script = script.replace(/\{;#;/g, "new function(_){");
		
		// put strings and regular expressions back
		script = script.replace(/#(\d+)/g, function(match, index) {		
			return data[index];
		});
		
		return script;
	}
}, {
	CONTINUE: /\\\r?\n/g,
	
	ENCODE10: "String",
	ENCODE36: "function(c){return c.toString(a)}",
	ENCODE62: "function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))}",
	
	UNPACK: "eval(function(p,a,c,k,e,r){e=%5;if(!''.replace(/^/,String)){while(c--)r[%6]=k[c]" +
	        "||%6;k=[function(e){return r[e]}];e=function(){return'\\\\w+'};c=1};while(c--)if(k[c])p=p." +
			"replace(new RegExp('\\\\b'+e(c)+'\\\\b','g'),k[c]);return p}('%1',%2,%3,'%4'.split('|'),0,{}))",
	
	init: function() {
		this.data = reduce(this.data, new RegGrp, function(data, replacement, expression) {
			data.store(this.javascript.exec(expression), replacement);
			return data;
		}, this);
		this.clean = this.data.union(this.clean);
		this.whitespace = this.data.union(this.whitespace);
	},
	
	clean: {
		"\\(\\s*;\\s*;\\s*\\)": "(;;)", // for (;;) loops
		"throw[^};]+[};]": IGNORE, // a safari 1.3 bug
		";+\\s*([};])": "$1"
	},
	
	data: {
		// strings
		"STRING1": IGNORE,
		'STRING2': IGNORE,
		"CONDITIONAL": IGNORE, // conditional comments
		"(COMMENT1)\\n\\s*(REGEXP)?": "\n$3",
		"(COMMENT2)\\s*(REGEXP)?": " $3",
		"([\\[(\\^=,{}:;&|!*?])\\s*(REGEXP)": "$1$2"
	},
	
	javascript: new RegGrp({
		COMMENT1:    /(\/\/|;;;)[^\n]*/.source,
		COMMENT2:    /\/\*[^*]*\*+([^\/][^*]*\*+)*\//.source,
		CONDITIONAL: /\/\*@|@\*\/|\/\/@[^\n]*\n/.source,
		REGEXP:      /\/(\\\/|[^*\/])(\\.|[^\/\n\\])*\//.source,
		STRING1:     /'(\\.|[^'\\])*'/.source,
		STRING2:     /"(\\.|[^"\\])*"/.source
	}),
	
	whitespace: {
		"(\\d)\\s+(\\.\\s*[a-z\\$_\\[(])": "$1 $2", // http://dean.edwards.name/weblog/2007/04/packer3/#comment84066
		"([+-])\\s+([+-])": "$1 $2", // c = a++ +b;
		"\\b\\s+\\$\\s+\\b": " $ ", // var $ in
		"\\$\\s+\\b": "$ ", // object$ in
		"\\b\\s+\\$": " $", // return $object
		"\\b\\s+\\b": SPACE,
		"\\s+": REMOVE
	}
});
