/* -*- coding: utf-8-unix -*-
 * $Id: gengo.js,v 1.15 2005/11/08 13:26:31 noir Exp $
 * Copyright (C) 2005 Hiroyuki KUROSAKI <noir[at]st.rim.or.jp>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

// class Array

Array.prototype.clone = function() {
    return Array.apply(null, this);
};

if (!Array.prototype.map) {
    Array.prototype.map = function(f) {
	var ret = new Array(this.length);
	for (var i = 0; i < this.length; i++) {
	    ret[i] = f(this[i]);
	}
	return ret;
    };
}

if (!Array.prototype.forEach) {
    Array.prototype.forEach = function(f) {
	for (var i = 0; i < this.length; i++) {
	    f(this[i]);
	}
    };
}

Array.prototype.zip = function(other) {
    var ret = new Array(this.length < other.length
			? this.length
			: other.length);
    for (var i = 0; i < this.length && i < other.length; i++) {
	ret[i] = [this[i], other[i]];
    }
    return ret;
};

Array.prototype.fold = function(f, knil) {
    var ret = knil;
    for (var i = 0; i < this.length; i++) {
	ret = f(this[i], ret);
    }
    return ret;
};

// class Fader

Number.prototype.toHex = function(padding) {
    var s = this.toString(16);
    var requiredPadLength = padding - s.length;
    var zs = new Array();
    for (var i = 0; i < requiredPadLength; i++) {
	zs.push("0");
    }
    return zs.join("") + s;
};

var RGB = Class.create();

RGB.MAX_VALUE = 0xff;

RGB.addLimited = function(a, b, limit) {
    var min = (b > 0) ? 0 : limit;
    var max = (b > 0) ? limit: RGB.MAX_VALUE;
    var result = a + b;
    if (result < min) {
	result = min;
    }
    else if (result > max) {
	result = max;
    }
    return result;
};

RGB.prototype = {
    initialize: function(value) {
	this.value = null;
	if (typeof value == "string") {
	    this.value = value.match(/[0-9a-fA-F]{2}/g).map(function(x) {
		return parseInt("0x" + x);
	    });
	}
	else if (typeof value == "object" && value.constructor == Array) {
	    this.value = value.clone();
	}
	else {
	    this.value = [0, 0, 0];
	}
    },
    toString: function() {
	return "#" + this.value.map(function(x) { return x.toHex(2); }).join("");
    },
    clone: function() {
	return new RGB(this.value);
    },
    increment: function(n, limit) {
	if (typeof n == "number") {
            this.value = this.value.zip(limit.value).map(function(x) {
		return RGB.addLimited(x[0], n, x[1]);
	    });
	}
	else if (typeof n == "object" && n.constructor == Array){
	    this.value = this.value.zip(n).zip(limit.value).map(function(x) {
		return RGB.addLimited(x[0][0], x[0][1], x[1]);
	    });
	}
    },
    compare: function(f, other) {
	return this.value.zip(other.value).map(function(x) {
	    return f(x[0], x[1]);
	}).fold(function(e, prev) {
	    return e && prev;
	}, true);
    },
    equal: function(other) {
	return this.compare(function(x, y) { return x == y; }, other);
    },
    greaterThanOrEqual: function(other) {
	return this.compare(function(x, y) { return x >= y; }, other);
    }
}

var Fader = Class.create();
Fader.timerIds = new Array();
Fader.prototype = {
    initialize: function(elem, from, to, step, interval) {
	this.elem = $(elem);
	this.from = new RGB(from);
	this.to = new RGB(to);
	this.step = step;
	this.interval = interval;
    },
    start: function() {
	if (Fader.timerIds[this.elem.id]) {
	    clearInterval(Fader.timerIds[this.elem.id]);
	    delete Fader.timerIds[this.elem.id];
	}
	var color = this.from.clone();
	var timerId = null;
	Fader.timerIds[this.elem.id] = setInterval((function() {
	    if (color.greaterThanOrEqual(this.to)) {
		clearInterval(Fader.timerIds[this.elem.id]);
		delete Fader.timerIds[this.elem.id];
		this.elem.style.backgroundColor = this.to.toString();
	    }
	    else {
		color.increment(this.step, this.to);
		this.elem.style.backgroundColor = color.toString();
	    }
	}).bind(this), this.interval);
    }
};

// class SpinBox

var SpinBox = Class.create();
SpinBox.prototype = {
    initialize: function(input, upButton, downButton, init, min, max, step) {
	this.input = $(input);
	this.upButton = $(upButton);
	this.downButton = $(downButton);
	this.initValue = parseInt(init);
	if (isNaN(this.initValue)) {
	    this.initValue = 0;
	}
	this.minValue = parseInt(min);
	if (isNaN(this.minValue)) {
	    this.minValue = 0;
	}
	this.maxValue = parseInt(max);
	if (isNaN(this.maxValue)) {
	    this.maxValue = 0;
	}
	this.step = parseInt(step);
	if (isNaN(this.step)) {
	    this.step = 1;
	}
	this.upButton.onclick = this.up.bindAsEventListener(this);
	this.downButton.onclick =this.down.bindAsEventListener(this);
	this.input.value = this.initValue.toString();
    },
    addValue: function(x) {
	var v = parseInt(this.input.value);
	if (isNaN(v)) {
	    v = 0;
	}
	v = v + x;
	if (this.minValue <= v && v <= this.maxValue) {
	    this.input.value = v.toString();
	}
    },
    up: function(evt) {
	this.addValue(this.step);
	this.input.onkeyup(evt);
    },
    down: function(evt) {
	this.addValue(-this.step);
	this.input.onkeyup(evt);
    }
};

function View() {
    this.subject = null;
    this.faders = new Array();
}

View.prototype.item = function(id) {
    return $(id);
};

View.prototype.updateSuccess = function() {
    this.faders.forEach(function(fader) {
	fader.start();
    });
};

var messageView = new View();

messageView.update = function() {
    this.item("message").innerHTML = this.subject.message;
    if (this.subject.message != "") {
	this.updateSuccess();
    }
};

var japaneseYearView = new View();

japaneseYearView.init = function() {
    var sel = this.item("jEra");
    while (sel.hasChildNodes()) {
	sel.removeChild(sel.lastChild);
    }
    var op;
    this.subject.rows.forEach(function(row) {
	op = document.createElement("option");
	op.innerHTML = row[0];
	sel.appendChild(op);
    });
    sel.selectedIndex = sel.options.length - 1;
};

japaneseYearView.update = function() {
    if (this.subject.convDir == "wtoj") {
	this.item("jEra").selectedIndex = this.subject.jEraIndex;
	jYear = this.subject.jYear;
	this.item("jYear").value = jYear > 0 ? jYear : "";
	this.updateSuccess();
    }
};

var westernYearView = new View();

westernYearView.update = function() {
    if (this.subject.convDir == "jtow") {
	wYear = this.subject.wYear;
	this.item("wYear").value = wYear > 0 ? wYear : "";
	this.updateSuccess();
    }
};

var gengoTable = {
    message: "",
    rows : [],
    jEraIndex: 0,
    jYear: 0,
    wYear: 0,
    convDir: ""
};

gengoTable.setObservers = function(observers) {
    this.observers = observers.clone();

    this.observers.forEach((function(o) {
	o.subject = this;
    }).bind(this));
}

gengoTable.notifyObservers = function() {
    this.observers.forEach(function(o) {
	o.update();
    });
}

gengoTable.request = function() {
    var url = "backend.php";
    var params = "f=tbl";
    var myAjax = new Ajax.Request(url,
				  {method: "get",
				   parameters: params,
				   onSuccess: this.loadRows.bind(this)});
}

gengoTable.loadRows = function(req) {
    eval("this.rows=" + req.responseText);
    japaneseYearView.init();
};

gengoTable.jtow = function(jEraIndex, jYear) {
    this.convDir = "jtow";
    this.message = "";
    this.jEraIndex = jEraIndex;
    this.jYear = jYear;

    this.wYear = this.rows[jEraIndex][1] + jYear - 1;

    if (this.wYear > this.rows[jEraIndex][2]) {
	this.message = this.rows[jEraIndex][0] + jYear + "年は存在しません。";
    }

    this.notifyObservers();
    return this.wYear;
};

gengoTable.wtoj = function(wYear) {
    this.convDir = "wtoj";
    this.message = "";
    this.wYear = wYear;

    var matched = new Array();

    for (var i = 0; i < this.rows.length; i++) {
	if (this.rows[i][1] <= wYear && wYear <= this.rows[i][2]) {
	    matched.push(this.rows[i]);
	    if (matched.length == 1) {
		this.jEraIndex = i;
		this.jYear = wYear - this.rows[i][1] + 1;
	    }
	}
    }

    if (matched.length > 1) {
	var m2 = matched.clone();
	m2.shift();
	this.message = m2.fold(function(item, prevResult) {
	    return prevResult + item[0] + (wYear - item[1] + 1) + "年<br />";
	}, "");
    }
    else if (matched.length == 0) {
	this.message = "該当なし";
    }
    this.notifyObservers();
    return matched;
};

function jtow() {
    jEraIndex = $("jEra").selectedIndex;
    jYear = parseInt($F("jYear"));
    if (isNaN(jYear)) {
	jYear = 0;
    }
    gengoTable.jtow(jEraIndex, jYear);
}

function wtoj() {
    wYear = parseInt($F("wYear"));
    if (isNaN(wYear)) {
	wYear = 0;
    }
    gengoTable.wtoj(wYear);
}

function clearBackgroundColor() {
    this.style.backgroundColor = "#ffffff";
}

function setEventHandlers() {
    var eventTable = [["jEra", "onclick", jtow],
		      ["jEra", "onkeyup", jtow],
		      ["jEra", "onfocus", clearBackgroundColor],
		      ["jYear", "onkeyup", jtow],
		      ["jYear", "onfocus", clearBackgroundColor],
		      ["wYear", "onkeyup", wtoj],
		      ["wYear", "onfocus", clearBackgroundColor],
		      ["jtowButton", "onclick", jtow],
		      ["wtojButton", "onclick", wtoj]];

    eventTable.forEach(function(row) {
	$(row[0])[row[1]] = row[2];
    });
}

function setFaders() {
    var views = [ [ messageView, ["message"] ],
		  [ japaneseYearView, ["jEra", "jYear"] ],
		  [ westernYearView, ["wYear"] ]];

    views.forEach(function(view) {
	view[0].faders = view[1].map(function(elem) {
	    return new Fader(elem, "#ffff00", "#ffffff", 0x11, 300);
	});
    });
}

function setSpinBoxes() {
    new SpinBox("jYear", "jYearUp", "jYearDown",
		(new Date()).getFullYear() - 1989 + 1, 1, 100, 1);
    new SpinBox("wYear", "wYearUp", "wYearDown",
		(new Date()).getFullYear(), 600, 3000, 10);
}

window.onload = function() {
    gengoTable.setObservers([messageView, japaneseYearView, westernYearView]);
    gengoTable.request();
    setEventHandlers();
    setSpinBoxes();
    setFaders();
};
