/**
 * File:    cjtk.js
 *
 * This library is free software. License under the GNU Lesser General
 * Public License as published by the Free Software Foundation(LGPL).
 *
 * @link http://blog.yappo.jp/yappo/archives/000461.html
 * @author Kazuhiro Osawa < ko at yappo.ne.jp >
 * @version 0.0.3
 */

function Cjtk(config){
	this.init(config);
}

// Pluginのロード
Cjtk.functions = new Array();
Cjtk.register_function = function(name, func){
	Cjtk.functions[name] = func;
};

Cjtk.prototype.init = function(config){

	var default_config = {
		type: 'text/javascript',
		charset: 'utf-8',
		separator: ';',
		foreach: false,
		ajax: false,
		start_tag: '[%',
		end_tag: '%]',
		macro_attribute: 'class',
		rowid_attribute: 'title',
		params: '',
		process_callback: function(){}
	};

	for (var key in config) {
		this[key] = config[key];
	}

	for (var key in default_config) {
		if (!this[key]) { this[key] = default_config[key]; }
	}

	// 特定のclassが付いている要素を集める
	this.classes = this.getElementsByClass(document, this.classname);
	if (this.foreach && this.classes.length) {
		this.classes = new Array( this.classes[0] );
	}

	var l = this.start_tag.replace(/([\[\]\(\)])/g, '\\$1');
	var r = this.end_tag.replace(/([\[\]\(\)])/g, '\\$1');

	this.macrotag_regx = new RegExp(l + '(.*?)' + r, 'g');
};

Cjtk.prototype.closure = function(method){
	var _this = this;
	return function(){
		_this[method]();
	};
};

Cjtk.prototype.getElementsByTagName = function(obj, searchTag){
	if (obj.all) {
		if (searchTag == '*') {
			return obj.all;
		} else {
			var all = obj.all;
			var allElements = new Array();
			for (var i = 0;i < all.length;i++) {
				var r = new RegExp(searchTag.replace('*', '.*'));
				if (all[i].tagName.match(r)) {
					allElements.push(all[i]);
				}
			}
			return allElements;
		}
	} else if (obj.getElementsByTagName) {
		return obj.getElementsByTagName(searchTag);
	} else {
		return new Array();
	}
};

Cjtk.prototype.getElementsByClass = function(obj, searchClass){
	var Elements = new Array();
	var allElements = this.getElementsByTagName(obj, '*');
	for (var i = 0; i < allElements.length; i++) {
		if (this.getAttribute(allElements[i], 'class') == searchClass) {
			Elements.push(allElements[i]);
		}
	}
	return Elements;
};

Cjtk.prototype.process = function(){
	if (!this.classes.length) { return; }
	if (this.foreach) {
		this._process_foreach();
	} else {
		this._process();
	}
};

Cjtk.prototype._process_foreach = function(){
	this.content = {
		obj: this.classes[0].cloneNode(true),
		display: this.classes[0].style.display ? this.classes[0].style.display : ''
	};
	this.content.obj.style.display = 'none';
	this._jsonp_load(new Array());
};

Cjtk.prototype._process = function(){
	this.content = new Array();
	var list     = new Array();
	for (var i = 0;i < this.classes.length;i++) {
		var id = this.getAttribute(this.classes[i], this.rowid_attribute);
		if (!this.content[id]) {
			this.content[id] = new Array();
			list.push(id);
		}
		var content = {
			obj: this.classes[i],
			display: this.classes[i].style.display ? this.classes[i].style.display : ''
		};
		this.content[id].push(content);
		this.classes[i].style.display = 'none';
		this.removeAttribute(this. classes[i], this.rowid_attribute);
	}
	this._jsonp_load(list);
};

Cjtk.prototype._jsonp_load = function(list){
	if (this.params) {
		this.callback(this.params);
	} else if (this.ajax) {
		this._jsonp_load_ajax(list);
	} else {
		this._jsonp_load_script(list);
	}
};

Cjtk.prototype._jsonp_load_script = function(config){
	var s = document.createElement('script');
	s.type    = this.type;
	s.charset = this.charset;
	s.src     = this.url + config.join(this.separator);
	document.body.appendChild(s);
};

Cjtk.prototype._jsonp_load_ajax = function(config){
};

Cjtk.prototype.callback = function(json){
	for (var key in json) {
		this.json = json[key];
		if (this.foreach) {
			this._callback_foreach(key);
		} else {
			this._callback(key);
		}
	}

	if (this.foreach) {
		// 元のテンプレートを消す
		this.classes[0].parentNode.removeChild(this.classes[0]);
	}

	this.process_callback(this);
};

Cjtk.prototype._callback_foreach = function(key){
	var obj = this.content.obj.cloneNode(true);
	this._execute(obj);
	obj.style.display = this.content.display;
	this.classes[0].parentNode.insertBefore(obj, this.classes[0]);
};

Cjtk.prototype._callback = function(key){
	var obj = this.content[key];
	if (!obj) { return; }
	for (var i = 0;i < obj.length;i++) {
		this._execute(obj[i].obj);
		obj[i].obj.style.display = obj[i].display;
	}
};

Cjtk.prototype._execute = function(obj){
	this.lexical = new Array();
	this._execute_walk(obj);
};

// 子要素全てを処理する
Cjtk.prototype._execute_walk = function(obj){
	var children = obj.childNodes;
	for (var i = 0;i < children.length;i++) {
		// for, fore, if, unlessは子要素の位置が変るので調整する、_macro_processが移動量を教えてくれる
		i += this._execute_process(children[i]);		
	}
};

// 実際の変換処理
Cjtk.prototype._execute_process = function(obj){
	var skip = 0;
	var cname = '' + this.getAttribute(obj, this.macro_attribute);
	if (cname.match(this.macrotag_regx)) {
		this.removeAttribute(obj, this.macro_attribute);

		// マクロ処理
		var _this = this;
		var terms = new Array();

		// マクロ解析
		cname.replace(this.macrotag_regx, function(tag, param, isp, src){
			var ret = _this._compile_macro(param);
			if (ret.length) {
				terms.push(ret);
			}
		});

		if (terms.length) {
			// 1つずつしょり
			var newclass = '';
			for (var t = 0;t < terms.length;t++) {
				var ret = this._macro_process(obj, terms[t]);
				skip += ret.skip;
				if (ret.newclassname) { newclass = ret.newclassname; }
				if (ret.brk) {
					if (newclass) { this.setAttribute(obj, this.macro_attribute, newclass); }
					return skip;
				}
			}
			if (newclass) { this.setAttribute(obj, this.macro_attribute, newclass); }
		}
	} else if (cname) {
		var args = cname.split('.');
		if (this._get_json_value(new Array(args[0]))) {
			// jsonのデータをtagに展開する
			var value = this._get_json_value(args);
			var t = document.createTextNode(value);
			obj.appendChild(t);
		}
	}

	// 子ノード全てを再帰処理
	this._execute_walk(obj);
	return skip;
};

//マクロ構文解析を行う
Cjtk.prototype._compile_macro = function(macro){
	return this._compile_macro_term(macro);
};

//マクロを構文単位で取りだす
Cjtk.prototype._compile_macro_term = function(macro){
	var terms = new Array();

	if (!macro.match(/^\s*([^\s])/)) { return terms; }
	var first_word = RegExp.$1;
	
	var first_pos  = macro.indexOf(first_word);
	var term = macro.slice(first_pos);

	var str = '';
	var next = first_pos;

	// strings
	if (first_word == "'" || first_word == "\"") { //'"
		str = this._compile_macro_extract_strings(term, first_word);
		terms.push({ type: 'string', value: str });
		next += str.length + 2;
	} else
	// vars
	if (first_word == "$") {
		str = this._compile_macro_extract_vars(term);
		terms.push({ type: 'var', value: str });
		next += str.length + 1;
	} else
	// pipe
	if (first_word == "|") {
		terms.push({ type: 'pipe' });
		next++;
	} else
	// functions
	if (first_word.match(/\w/)) {
		str = this._compile_macro_extract_functions(term);
		terms.push({ type: 'function', value: str });
		next += str.length;
	} else {
		return terms;
	}

	var ret = this._compile_macro_term(macro.slice(next));
	return terms.concat(ret);
};

//クオートに囲まれた文字列を取りだす \'とかに対応
Cjtk.prototype._compile_macro_extract_strings = function(word, quote) {
	var word_tmp = word.slice(1, word.length);
	var str = '';
	while (1) {
		var next = word_tmp.indexOf(quote);
		if (next < 0) { return ''; }
		if (word_tmp.charAt(next - 1) == '\\') {
			str += word_tmp.slice(0, next - 1) + quote;
			word_tmp = word_tmp.slice(next + 1);
			continue;
		} else {
			str += word_tmp.slice(0, next);
			break;
		}
	}
	return str;
}

//変数名を取りだす
Cjtk.prototype._compile_macro_extract_vars = function(word) {
	var word_tmp = word.slice(1, word.length);
	if (!word_tmp.match(/^([^\s]+)/)) { return ''; }
	return RegExp.$1;
};

//関数名を取りだす
Cjtk.prototype._compile_macro_extract_functions = function(word) {
	if (!word.match(/^([\w\.\_]+)/)) { return ''; }
	return RegExp.$1;
};

//マクロの処理を行う
Cjtk.prototype._macro_process = function(obj, term){
	var ret = {
		stdout: '',
		newclassname: '',
		skip: 0,
		brk: 0
	};

	var stdout = '';
	var func = '';
	var func_args = new Array();
	for (var i = 0;i < term.length;i++) {
		switch (term[i].type) {
		case 'string':
			if (!func) {
				stdout = term[i].value;
			} else {
				func_args.push(term[i].value);
			}
			break;
		case 'var':
			var tmp = this._get_json_value(term[i].value.split(/\./));
			if (!func) {
				stdout = tmp;
			} else {
				func_args.push(tmp);
			}
			break;
		case 'pipe':
		case 'function':
			if (func) {
				ret = this._macro_process_func(ret, obj, func, stdout, func_args);
				stdout = ret.stdout;
				func = '';
				func_args = new Array();
			}
			if (term[i].type == 'pipe') {
				if (i + 2 == term.length && term[i + 1].type == 'var') {
					// 最後が変数だったら変数に代入する
					var key = term[i + 1].value;
					if (!this.getLexical(key)) {
						this.setLexical(key, stdout);
						return ret;
					}
				}
				break;
			}

			func = term[i].value;
			break;
		};
	}
	if (func) {
		ret = this._macro_process_func(ret, obj, func, stdout, func_args);
	}

	return ret;
};

//マクロ処理
Cjtk.prototype._macro_process_func = function(ret, obj, func, stdin, args){

	if (!func.match(/\./)) { func = 'CORE.' + func; }
	var funcs = func.split(/\./);

	var r = Cjtk.functions[funcs[0]](this, obj, funcs[1], stdin, args);

	ret.stdout = r.stdout;
	if (r.newclassname) { ret.newclassname = r.newclassname; }
	if (r.brk) { ret.brk = r.brk; }
	ret.skip += r.skip;

	return ret;
};

// 子ノードを全て削除
Cjtk.prototype._removeChildren = function(node){
	while (node.childNodes.length) {
		node.removeChild(node.childNodes.item(0));
	}
};

// レキシカル変数
Cjtk.prototype.getLexical = function(name){
	return this.lexical[name];
};
Cjtk.prototype.setLexical = function(name, value){
	if (!this.getLexical(name)) {
		this.lexical[name] = new Array();
	}
	return this.lexical[name].unshift(value);
};
Cjtk.prototype.removeLexical = function(name){
	this.lexical[name].shift();
	if (this.lexical[name].length == 0) {
		delete this.lexical[name];
	}
};

// JSONで取得した変数取得、レキシカル変数があればそちらを優先
Cjtk.prototype._get_json_value = function(args){
	var ret;
	if (this.getLexical(args[0])) {
		var key = args.shift();
		ret = this.__get_json_value(this.getLexical(key)[0], args);
	} else {
		ret = this.__get_json_value(this.json, args);
	}
	if (!ret) { return ''; }
	return ret;
};
Cjtk.prototype.__get_json_value = function(json, args){
	if (args.length == 0) {
		return json;
	} else if (args.length == 1) {
		return json[args[0]];
	} else {
		var d = json;
		for (var i = 0;i < args.length;i++) {
			d = d[args[i]];
			if (!d) { return ''; }
		}
		return d;
	}
};

// DOMのAttribute操作
Cjtk.prototype._getAttribute = function(obj, attr){
	if (typeof(obj['getAttribute']) != 'function') { return ''; }
	return obj.getAttribute(attr);
};
Cjtk.prototype._setAttribute = function(obj, attr, value){
	if (typeof(obj['setAttribute']) != 'function') { return; }
	return obj.setAttribute(attr, value);
};
Cjtk.prototype._appendAttribute = function(obj, attr, value){
	return this.setAttribute(obj, attr, this.getAttribute(obj, attr) + value);
};
Cjtk.prototype._removeAttribute = function(obj, attr){
	if (typeof(obj['removeAttribute']) != 'function') { return; }
	return obj.removeAttribute(attr);
};

if (navigator.userAgent.indexOf( "MSIE" ) > -1) {

	Cjtk.prototype.getAttribute = function(obj, attr){
		if (!typeof(obj['getAttribute'])) { return ''; }

		// className を取ろうとすると例外が起きる場合が有る
		try {
			return obj.getAttribute(this._classAttribute_changeName(attr));
		} catch (e) {
			return '';
		}
	};
	Cjtk.prototype.setAttribute = function(obj, attr, value){
		if (!typeof(obj['setAttribute'])) { return ''; }
		return obj.setAttribute(this._classAttribute_changeName(attr), value);
	};
	Cjtk.prototype.appendAttribute = Cjtk.prototype._appendAttribute;
	Cjtk.prototype.removeAttribute = function(obj, attr){
		if (!typeof(obj['removeAttribute'])) { return ''; }
		return obj.removeAttribute(this._classAttribute_changeName(attr));
	};
	Cjtk.prototype._classAttribute_changeName = function(attr){
		if (attr == 'class') {
			return 'className';
		} else {
			return attr;
		}
	};

} else {
	Cjtk.prototype.getAttribute    = Cjtk.prototype._getAttribute;
	Cjtk.prototype.setAttribute    = Cjtk.prototype._setAttribute;
	Cjtk.prototype.appendAttribute = Cjtk.prototype._appendAttribute;
	Cjtk.prototype.removeAttribute = Cjtk.prototype._removeAttribute;
}


// COREマクロ
Cjtk.register_function('CORE', function(context, obj, func, stdin, args){
	var ret = {
		stdout: '',
		newclassname: '',
		skip: 0,
		brk: false
	};
	var first_arg = stdin ? stdin : args.length ? args[0] : '';

	switch (func) {
	case 'nop':
		break;
	case 'inner':
		ret.stdout = 0;
		switch (args[0]) {
		case 'set':
			context._removeChildren(obj);
			ret.stdout = 1;
			break;
		};
		obj.appendChild(document.createTextNode(stdin));
		break;
	case 'attr':
		ret.stdout = 0;
		switch (args[0]) {
		case 'get':
			ret.stdout = context.getAttribute(obj, args[1]);
			break;
		case 'set':
			context.setAttribute(obj, args[1], stdin);
			ret.stdout = 1;
			break;
		case 'append':
			context.appendAttribute(obj, args[1], stdin);
			ret.stdout = 1;
			break;
		};
		break;
	case 'if':
	case 'unless':
		if ((stdin && stdin != '0' && func == 'if') || ((!stdin || stdin == '0') && func == 'unless')) {
			// 条件に一致
			if (!args[0]) {
				// 引数が特に無ければ、子ノードをobjの場所にコピーする
				var children = obj.childNodes;
				for (var i = 0;i < children.length;i++) {
					var node = children[i].cloneNode(true);
					obj.parentNode.insertBefore(node, obj);
				}
				obj.parentNode.removeChild(obj);
				ret.skip = -1;
			}
			ret.brk = false;
			ret.stdout = '1';
		} else {
			// 条件に一致しないのでノードを消す
			obj.parentNode.removeChild(obj);
			ret.skip = -1;
			ret.brk = true;
			ret.stdout = '0';
		}
		break;
	case 'for':
	case 'foreach':
		ret.stdout = 0;
		ret.skip = -1;
		var key   = args[0];
		var value = stdin;
		if (typeof value != 'object') { break; }

		var insertobj;
		if (args[1]) {
			// 2個目の引数が有れば現在のNodeをコピーして、子Nodeを消したものを利用
			insertobj = obj.cloneNode(false);
		}

		for (k in value) {
			context.setLexical(key, { key: k, value: value[k], k: k, v: value[k] });
			var children = obj.childNodes;
			for (var i = 0;i < children.length;i++) {
				var node = children[i].cloneNode(true);
				context._execute_process(node);
				if (args[1]) {
					insertobj.appendChild(node);
				} else {
					obj.parentNode.insertBefore(node, obj);
				}
				ret.skip++;
			}
			context.removeLexical(key);
			ret.stdout++;
		}

		if (args[1]) { obj.parentNode.insertBefore(insertobj, obj); }
		obj.parentNode.removeChild(obj);
		break;

	// フィルター 
	case 'cat':
		ret.stdout = stdin + args.join('');
		break;
	case 'lc':
		ret.stdout = typeof stdin == 'string' ? stdin.toLowerCase() : '';
		break;
	case 'uc':
		ret.stdout = typeof stdin == 'string' ? stdin.toUpperCase() : '';
		break;
	case 'replace':
		ret.stdout = typeof stdin == 'string' ? stdin.replace(args[0], args[1]) : '';
		break;
	case 'html':
		var str = stdin;
		if(typeof(str) == 'object') { str = str.toString(); }
		ret.stdout = str.replace(/&/g, '&amp;').replace(/\"/g, '&quot;').replace(/\'/g, '&#039;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
		break;

	case 'class':
	case context.macro_attribute:
		ret.newclassname = first_arg;
		break;
	};

	return ret;
});
