/**
 * グローバル関数群
 */
 
// m～nのランダムな数を返す
function $rand(n, m) {
	m = m || 0;
	return Math.floor(Math.random() * (n - m) + m);
}

// firebugかalertでデバッグ
function $test(s) {
	if(typeof(console) != 'undefined' && console.log) console.log(s);
	else alert(s);
}

// evalデバッグ
function $debug(exp) {
	try {
		eval(exp);
	} catch(e) {
		if(typeof(console) != 'undefined' && console.log) console.log(e);
		else alert(e);
	}
}
/**
 * 形態素用DB
 */
function Database() {
	this.init.apply(this);
}

Database.prototype = {

	init: function() {
		this.keys = {};
		this.list = [];
	},

	// 添え字付き連想配列を作る
	add: function(key, value) {
		if(typeof this.keys[key] != 'undefined') {
			this.list[this.keys[key]].push(value);
		} else {
			this.keys[key] = this.list.length;
			this.list.push([value]);
			this.list[this.list.length - 1].value = value;
		}
	},
	
	// keyに続く形態素を返す
	get: function(key) {
		var list = this.list[this.keys[key]] || [];
		return list[$rand(list.length)];
	},
	
	// randomな形態素を返す
	rand: function() {
//		var list = this.list[$rand(this.list.length)];
//		return list[$rand(list.length)];
		return this.list[$rand(this.list.length)].value;
	},
	
	// keyに続く形態素リストを返す
	gets: function(key) {
		return this.list[this.keys[key]];
	},
	
	// randomな形態素リストを返す
	rands: function() {
		return this.list[$rand(this.list.length)];
	},
	
	//名詞を返す
	noun: function() {
		var result = this.rand();
		while(result.search(/[一-龠々〆ヵヶ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[ａ-ｚＡ-Ｚ０-９]+/) == -1) {
			result = this.rand();
		}
		return result;
	}
};

/**
 * 作詞をするクラス
 */
function SongWriter() {
	this.init.apply(this, arguments);
}


SongWriter.prototype = {

	init: function(s, mode) {
		this.db = new Database();
		if(s) this.store(this.analyze(s, mode));
	},

	// 簡易形態素解析
	// 形態素解析もどきを改良してみた - エブログ（http://ablog.seesaa.net/article/24578324.html）をほぼそのまま流用
	analyze: function(s, mode) {

		var pattern = /[一-龠々〆ヵヶ]+|[ぁ-んー]+|[ァ-ヴー]+|[a-zA-Z0-9' ]+|[ａ-ｚＡ-Ｚ０-９]+|[，,、]+|[。．.！!？?]+|[(（「『“]+|[)）」』”]+|['"]+|[ 　]+/g;

		if(!mode) {
			return s.match(pattern);
		} else if(mode == 'strict') {
			s = s.replace(/(でなければ|について|ならば|までを|までの|くらい|なのか|として|とは|なら|から|まで|して|だけ|より|ほど|など|って|では|は|で|を|の|が|に|へ|と|て)/g, '$1|');
			var a = s.split('|');
			var result = [];
			for(var i = 0; i < a.length; i++) {
				var a2 = a[i].match(pattern);
				if(a2) {
					for(var j = 0; j < a2.length; j++) {
						result.push(a2[j]);
					}
				}
			}
			return result;
		} else if(mode == 'noun') {
			return s.match(/[一-龠々〆ヵヶ]+|[ァ-ヴー]+|[a-zA-Z0-9]+|[ａ-ｚＡ-Ｚ０-９]+/g);
		}
	},

	// 形態素をDBに保存する
	store: function(a) {
		if(a) {
			for(var i = 0; i < a.length - 1; i++) {
				this.db.add(a[i], a[i + 1]);
			}
		}
	},

	//簡易マルコフ連鎖で文生成
	generate: function(s, limit, array) {
	
		array = array || false;
		
		var ends = /[。．.！!？?]/;
		var end = '。';
		
		var i = 0;
		// 文が終わり記号だけにならないように
		if(!s || s.search(ends) != - 1) s = this.db.noun();
		var result = [s];
		
		while(s.search(ends) == -1) {
		
			var data = this.db.get(s);
			
			var s2;
			if(typeof data != 'undefined') {
				s2 = data;
			} else {
				// DBになければ、ランダムで選ぶか文を終わらせる
				switch($rand(2)) {
					case 0:
						s2 = this.db.rand()
						break;
					case 1:
						s2 = end;
						break;
				}
			}
			result.push(s2);
			s = s2;
			
			if(limit && ++i > limit) break;			
		}
		result = !array ? this.close(result.join('')) : result;
		return result;
	},
	
	// 括弧を閉じる
	close: function(s) {
	
		var opens  = ['(', '（', '「', '『', '“', '"'];
		var closes = [')', '）', '」', '』', '”', '"'];

		// 先に閉じ括弧を削除
		for(var i = s.length - 1; i >= 0; i--) {
			var c = s.charAt(i);
			for(var j = 0; j < closes.length; j++) {
				if(c == closes[j])
					s = s.substring(0, i) + s.substring(i + 1, s.length);
			}
		}

		// 閉じ括弧を補完	
		for(var i = s.length - 1; i >= 0; i--) {
			var c = s.charAt(i);
			for(var j = 0; j < opens.length; j++) {
				if(c == opens[j])	s += closes[j];
			}
		}
		
		return s;
	},

	// メロを作成
	makeMelo: function(rhyme, mode) {
		var lines = $rand(6, 4);
//		var words = $rand(7, 3);
//		var words = 10;
		var words = $rand(10, 7);
		var melo = [this.generate(null, words)];
		for(var i = 1; i < lines; i++) {
			if(!rhyme) {
				melo.push(this.generate(null, words));
			} else {
				var contexts = this.analyze(melo[$rand(melo.length)], mode);
				var context = contexts[$rand(contexts.length)];
				melo.push(this.generate(context, words));
			}
		}
		return melo;
	},
	
	// サビを作成
	makeSabi: function(titles, mode) {
		var lines = $rand(5, 2);
//		var words = $rand(6, 2);
//		var words = 10;
		var words = $rand(10, 7);
		var sabi = [this.generate(titles[$rand(titles.length)], words)];
		var context;
		for(var i = 1; i < lines; i++) {
			switch($rand(2)) {
				case 0:
					var contexts = this.analyze(sabi[$rand(sabi.length)], mode);
					context = contexts[$rand(contexts.length)];
					break;
				case 1:
					context = titles[$rand(titles.length)];
					break;
			}
			sabi.push(this.generate(context, words));
		}
		return sabi;
		
	
	},
	
	// 歌詞を整形
	format: function(a) {
	
		var newline = '\n';
		var newline = '<br />';
		var delimiter = '　';
		// 文\s文\sで改行する（[^<]は<br で改行されないため）
		var block = /([^　\s<]+?[　\s]+[^　\s<]+?[　\s]+)/g;
		var result = '';
		
		return this.space(a.join(newline).replace(/[，,、。．.(（「『“)）」』”"]+/g, delimiter).replace(block, '$1' + newline)) + newline;
	},

	// スペースを圧縮
	space: function(s) {
		return s.replace(/　+/g, '　').replace(/\s+/g, ' ')
	},

	// 作詞する
	write: function(title, titleId, lyricsId) {
	
		var titles;
		if(!title) {
			title = this.generate(null, $rand(8, 4));
//			title = this.generate(null, 8);
			title = this.space(title);
			titles =  this.analyze(title, 'noun');
		} else {	
			titles = this.analyze(title, 'strict');
		}

		var max = $rand(5, 2);
		for(var i = 0, melos = []; i < max; melos.push(this.makeMelo(true, 'noun')), ++i);
		max = $rand(4, 2);
		for(var i = 0, sabis = []; i < max; sabis.push(this.makeSabi(titles, 'strict')), ++i);

		var lyrics = [];
		var newline = '<br />';
		while(melos.length != 0 || sabis.length != 0) {

			var phrase = $rand(2);
		
			if(melos.length == 0) phrase = 1;
			if(sabis.length == 0) phrase = 0;

			switch(phrase) {
				case 0:
					lyrics.push(this.format(melos.shift()) + newline);
					break;
				case 1:
					lyrics.push(this.format(sabis.shift()) + newline);
					break;
			}
		
		}
		
		if(typeof titleId == 'string') titleId = document.getElementById(titleId);
		if(typeof lyricsId == 'string') titleId = document.getElementById(lyricsId);

		titleId.innerHTML = title;
		lyricsId.innerHTML = lyrics.join('');
	}
};

