/*
 * This module can be executed standalone in node, to build CSS files, inlining imports, and 
 * pre-processing extensions for faster run-time execution. This module is also
 * used by the AMD build process.
 */
var operatorMatch = {
	'{': '}',
	'[': ']',
	'(': ')'
};
var nextId = 1;

document = {
	createElement: function(){
		return {style:
			{// TODO: may want to import static has features to determine if some of these exist
			}
		};
	},
	getElementsByTagName: function(){
		return [];
	},
	addEventListener: function(){}
};
navigator = {
	userAgent: "build"
};

var pseudoDefine = function(id, deps, factory){
		parse = factory({
				isBuild: true,
				isTagSupported: function(){
					return true;					
				}
		});
	};
var requiredModules = [], base64Module;

if(typeof define == 'undefined'){
	var fs = require('fs'),
		pathModule = require('path'),
		parse;
	define = pseudoDefine;
	require('./core/parser');
}else{
	define(['build/fs', 'build/fileUtils', './build/base64'], function(fsModule, fileUtils, base64){
		fs = fsModule;
		base64Module = base64;
		// must create our own path module for Rhino :/
		pathModule = {
			resolve: function(base, target){
				return fileUtils.compactPath(base.replace(/[^\/]+$/, '') + target);
			},
			dirname: function(path){
				return path.replace(/[\/\\][^\/\\]*$/, '');
			},
			relative: function(basePath, path){
				basePath = this.dirname(basePath);
				var baseParts = basePath.split(/[\/\\]/);
				var parts = path.split(/[\/\\]/);
				var l = parts.length;
				for (var i = 0; i < l; i++){
					if (parts[i] !== baseParts[i]){
						break;
					}
				}
				parts.splice(0, i);
				for (; i < baseParts.length; i++){
					parts.unshift('..');
				}
				return parts.join('/');
			},
			join: function(base, target){
				return fileUtils.compactPath((base[base.length - 1] == '/' ? base : (base + '/')) + target);
			}
		};
		return function(xstyleText){
			var define = pseudoDefine;
			var vm;
			try {
				require(['dojo/node!vm']);
				vm = require('dojo/node!vm');
			} catch (e) {
			}
			vm ?
				vm.runInNewContext(xstyleText, {
					define: define,
					console: console
				}, 'xstyle/core/parser.js') :
				eval(xstyleText);
			return processCss;
		};
	});
}
function main(source, target){
	var imported = {};
	var basePath = source.replace(/[^\/]*$/, '');
	var cssText = fs.readFileSync(source).toString("utf-8");
	var processed = processCss(cssText, basePath, basePath);
	var output = processed.standardCss;
	if(processed.xstyleCss){
		output += 'x-xstyle{content:"' + 
				processed.xstyleCss.replace(/["\\\n\r]/g, '\\$&') + 
					'";}';
	}
	if(target){
		fs.writeFileSync(target, output);
	}else{
		console.log(output);
	}
}
function minify(cssText){
	return cssText.
			replace(/\/\*([^\*]|\*[^\/])*\*\//g, ' ').
			replace(/\s*("(\\\\|[^\"])*"|'(\\\\|[^\'])*'|[;}{:])\s*/g,"$1").
			replace(/[^\x00-\x7F]([0-9a-fA-F])?/g, function(character, hexComesNext){
				// escape non-ascii characters to be safe
				return '\\' + character.charCodeAt(0).toString(16) + (hexComesNext ? ' ' + hexComesNext : '');
			});
}
var mimeTypes = {
	eot: "application/vnd.ms-fontobject",
	woff: "application/font-woff",
	gif: "image/gif",
	jpg: "image/jpeg",
	jpeg: "image/jpeg",
	png: "image/png"	
}
function processCss(cssText, basePath, cssPath, inlineAllResources){
	function XRule(){
	}
	var ruleCount = 0;
	XRule.prototype = {
		newCall: function(name){
			var call = new Call(name);
			call.parent = this;
			return call;
		},
		childRules: 0,
		newRule: function(name){
			var rule = (this.rules || (this.rules = {}))[name] = new XRule();
			rule.ruleIndex = this.childRules++;
			rule.parent = this;
			return rule;
		},
		getDefinition: function(name, includeRules){
			var parentRule = this;
			do{
				var target = parentRule.definitions && parentRule.definitions[name]
					|| (includeRules && parentRule.rules && parentRule.rules[name]);
				parentRule = parentRule.parent;
			}while(!target && parentRule);
			if(target){
				target.name = name;
			}
			return target;
		},
		declareDefinition: function(name, value, conditional){
			// TODO: access staticHasFeatures to check conditional
			var valueString = {
				toString: function(){
					return value.toString(2);
				}
			};
			(this.xstyleCss = this.xstyleCss || []).push(name + '=', valueString, ';');
			var definitions = (this.definitions || (this.definitions = {}));
			definitions[name] = value || new XRule();
		},
		setValue: function(name, value){
			var target = this.getDefinition(name);
			var browserCss = this.browserCss = this.browserCss || [];
			if(!this.ruleStarted && !this.root){
				this.ruleStarted = true;
				browserCss.push(this.selector, '{');
			}
			if(!target){
				browserCss.push(name, ':', value, ';');
			}
			if(target || typeof value == 'object'){
				if(!this.xstyleStarted){
					this.xstyleStarted = true;
					this.xstyleCss = this.xstyleCss || [];
				}
				this.xstyleCss.push(name + ':' + value + ';');
			}
		},
		onArguments: function(){},
		setMediaSelector: function(selector){
			this.selector = '';
			browserCss.push(selector, '{');
			this.isMediaBlock = true;
		},
		toString: function(mode){
			var str = '';
			str += this.xstyleCss ? this.xstyleCss.join('') : '';
			for(var i in this.rules){
				var rule = this.rules[i];
				if(rule.ref && !rule.creating){
					str += rule.toString(1);
				}
			}
			if(!this.root && !this.isMediaBlock && this.xstyleStarted && (str || mode != 1)){
				str = ((mode == 2 && this.bases) || this.tagName || '') + '{' + this.ref + str.replace(/\s+/g, ' ') + '}';
			}
			return str;
		},
		onRule: function(){
			if(this.browserCss){
				this.browserCss.push('}');
				this.ref = '/' + ruleCount;
				ruleCount++;
				browserCss.push(this.browserCss.join(''));
			}
			if(this.isMediaBlock){
				browserCss.push('}');
			}
		},
		extend: function(derivative, fullExtension){
			var definitions = this.definitions;
			if(definitions && fullExtension){
				// TODO: need to mixin this in, if it already exists
				derivative.definitions = Object.create(definitions);
			}
			(derivative.bases = derivative.bases || []).push(this.name);
		}
	};
	// a class representing function calls
	function Call(value){
		// we store the caller and the arguments
		this.caller = value;
		this.args = [];
	}
	var CallPrototype = Call.prototype = new XRule;
	CallPrototype.declareProperty = CallPrototype.setValue = function(name, value){
		// handle these both as addition of arguments
		this.args.push(value);
	};
	CallPrototype.toString = function(){
		var operator = this.operator;
		return operator + this.args + operatorMatch[operator]; 
	};
	
	function insertRule(cssText){
		//browserCss.push(cssText);
	}
	function correctUrls(cssText, path){
		// correct all the URLs in the stylesheets
		// determine the directory path
		path = pathModule.dirname(path) + '/';
		//console.log("starting path", basePath , path);
		// compute the relative path from where we are to the base path where the stylesheet will go
		var relativePath = pathModule.relative(basePath, path);
		return cssText.replace(/url\s*\(\s*['"]?([^'"\)]*)['"]?\s*\)/g, function(t, url){
			if(url.match(/^data:/)){
				return url;
			}
			if(inlineAllResources || /#inline$/.test(url)){
				// we can inline the resource
				suffix = url.match(/\.(\w+)(#|\?|$)/);
				suffix = suffix && suffix[1];
				url = url.replace(/[\?#].*/,'');
				
				if(typeof java !== 'undefined'){
					// reading binary is hard in rhino
					var file = new java.io.File(pathModule.join(path, url));
					var length = file.length();
					var fis = new java.io.FileInputStream(file);
					var bytes = java.lang.reflect.Array.newInstance(java.lang.Byte.TYPE, length); 
					fis.read(bytes, 0, length);
					var jsBytes = new Array(length);
					for(var i = 0; i < bytes.length;i++){
						var singleByte = bytes[i];
						if(singleByte < 0){
							singleByte = 256 + singleByte;
						}
						jsBytes[i] = singleByte;
					}
					var moduleText =base64Module.encode(jsBytes);
				}else{
					// in node base64 encoding is easy
					var moduleText = fs.readFileSync(pathModule.join(path, url)).toString("base64");
				}
				return 'url("data:' + (mimeTypes[suffix] || 'application/octet-stream') + 
							';base64,' + moduleText + '")';
			}
			// or we adjust the URL
			var adjustedPath = pathModule.resolve(relativePath, url);
			return 'url("' + adjustedPath + '")';
		});
	};
	parse.getStyleSheet = function(importRule, sequence, styleSheet){
		var importValue = sequence[1];
		var path;
		try{
			if(importValue.operator === '('){
				// a url() call
				var firstArg = importValue.args[0];
				// extract  from a literal string, or just grab the raw text
				// note that the correctUrls should have already resolved this path
				path = firstArg.length? firstArg[0].value : firstArg.toString();
			}else{
				// assume it is a string
				path  = pathModule.resolve(styleSheet.href, importValue.value);
			}
		}catch(error){
			console.error('Can not parse import value of ', error, importValue, firstArg);
		}
		var localSource = '';
		try{
			localSource = fs.readFileSync(path).toString('utf-8');
		}catch(e){
			console.error(e.stack || e);
		}
		localSource = correctUrls(localSource, path);
		return {
			localSource: localSource,
			href: path || '.',
			insertRule: insertRule,
			cssRules: []
		};
	};
	var browserCss = [];//[correctUrls(cssText, basePath + "placeholder.css")];
	var rootRule = new XRule;
	rootRule.root = true;
	parse(rootRule, correctUrls(cssText, cssPath),
			{href: cssPath || '.', cssRules:[], insertRule: insertRule});
	rootRule.definitions = {
		Math:1,
		require:1,
		item: 1,
		'native': 1,
		prefixed: 1
	}
	function visit(parent){
		//browserCss.push(parent.selector + '{' + parent.cssText + '}'); 
		/*for(var i in parent.variables){
			if(!intrinsicVariables.hasOwnProperty(i)){
				xstyleCss.push(i,'=',parent.variables[i]);
			}
		}*/
	}
	visit(rootRule);
	return {
		standardCss: minify(browserCss.join('')),
		xstyleCss: rootRule.toString(),
		requiredModules: requiredModules
	};
}
if(typeof module != 'undefined' && require.main == module){
	main.apply(this, process.argv.slice(2));
}
