/**
 * MemberCommunity: Community on the webpage tool.
 * 
 * This is free software, you can redistribute it and modify it under
 * the terms of the GNU Lesser General Public License.
 * 
 * You can change anything other than this comment.
 * If you do so, not remove former author adn copyright description, but add your name to which.
 * 
 * @author KenjiNagao
 * @copyright Copyright (C) 2008 KenjiNagao
 */
// The very minor JavaScript utility.
//
// This library is under the term of GNU LGPL(Gnu Lesser General Public Lisence).
// You can modify and/or redistribute this program under the term of GNU LGPL.
// 
// You can put whole of this program - then must include this comment - into your own products.
//
// version - 0.0.1 (2008/07/18)
// copyright (c) 2008 Kenji Nagao

if (typeof(jp) == 'undefined') {
	/**
	 * @namespace
	 */
	var jp = {};
}

if (typeof(jp.ggaogg) == 'undefined') {
	/**
	 * @namespace
	 */
	jp.ggaogg = {};
}

if (typeof(jp.ggaogg.util) == 'undefined') {
	/**
	 * @namespace
	 */
	jp.ggaogg.util = {};
}

jp.ggaogg.util = {
	/**
	 * PHP like function.
	 * 
	 * Check the obj is not equals to "undefined" or not.
	 * @param {Object} obj
	 * 		Object to be checked if it was set.
	 * @return {bool}
	 * 		If obj is 'undefined', then return false, otherwise return true.
	 */
	isSet: function(obj) {
		if (typeof(obj) == 'undefined') {
			return false;
		} else {
			return true;
		}
	},
	
	/**
	 * Check obj is string.
	 * @param {Object} obj
	 * 			Object to check.
	 * @return {boolean}
	 * 			true:Object is string. false:Otherwise.
	 */
	isString: function(obj) {
		return (typeof(obj) == 'string');
	},
	
	/**
	 * Check obj is number.
	 * @param {Object} obj
	 * 			Object to check.
	 * @return {boolean}
	 * 			true:Object is number. false:Otherwise.
	 */
	isNumber: function(obj) {
		return (typeof(obj) == 'number');
	},
	
	/**
	 * Check obj is boolean.
	 * @param {Object} obj
	 * 			Object to check.
	 * @return {boolean}
	 * 			true:Object is boolean. false:Otherwise.
	 */
	isBoolean: function(obj) {
		return (typeof(obj) == 'boolean');
	},
	
	/**
	 * Check obj is function.
	 * @param {Object} obj
	 * 			Object to check.
	 * @return {boolean}
	 * 			true:Object is function. false:Otherwise.
	 */
	isFunction: function(obj) {
		return (typeof(obj) == 'function');
	},
	
	/**
	 * Check obj is object.
	 * @param {Object} obj
	 * 			Object to check.
	 * @return {boolean}
	 * 			true:Object is object. false:Otherwise.
	 */
	isObject: function(obj) {
		return (typeof(obj) == 'object');
	}
};

// the alias
jp.ggaogg.util.isset = jp.ggaogg.util.isSet;


/**
 * Create object that operates about cookie very easier.
 * @author KenjiNagao
 * @class operate about cookie.
 * @param {Object} options
 * 		JSON object it has zero or more of days, hours, minutes, seconds fields.
 */
jp.ggaogg.util.CookieOperator = function(options) {
	
	var expirePlus = 0;
	
	if (jp.ggaogg.util.isset(options)) {
		if (jp.ggaogg.util.isset(options.days)) {
			expirePlus += 1000 * 3600 * 24 * options.days;
		}
		if (jp.ggaogg.util.isset(options.hours)) {
			expirePlus += 1000 * 3600 * options.hours;
		}
		if (jp.ggaogg.util.isset(options.minutes)) {
			expirePlus += 1000 * 60 * options.minutes;
		}
		if (jp.ggaogg.util.isset(options.seconds)) {
			expirePlus += 1000 * options.seconds;
		}
	}
	
	var now = new Date();
	now.setTime(now.getTime() + expirePlus);
	this.expire = now.toGMTString();
}

/**
 * @static
 * @param {String} name
 * 		Name of cookie.
 * @param {Object} value
 * 		Value of cookie.
 * @param {String} expireGMTString
 * 		String of GMT for expire. Cannot be omitted.
 */
jp.ggaogg.util.CookieOperator.setCookie = function(name, value, expireGMTString){
	var saveString = name + '=' + encodeURIComponent(value) + ';expires=' + expireGMTString;
	document.cookie = saveString;
}


jp.ggaogg.util.CookieOperator.prototype = {
	
	/**
	 * Set cookie.
	 * @public
	 * @param {String} name
	 * 		Name of the cookie.
	 * @param {Object} value
	 * 		Value of the cookie.
	 * @return void
	 */
	setCookie: function(name, value) {
		jp.ggaogg.util.CookieOperator.setCookie(name, value, this.expire);
	},
	
	/**
	 * Get cookie.
	 * @public
	 * @param {String} name
	 * @return
	 * 		Return cookie for name, or false if the cookie for name was not set.
	 */
	getCookie: function(name) {
		var cookies = document.cookie.split('; ');
		
		for (i = 0 ; i < cookies.length ; i++) {
			var currCookie = cookies[i].split("=");
			var currName = currCookie[0];
			var currValue = currCookie[1];
			if (name == currName) {
				return decodeURIComponent(currValue);
			}
		}
		
		return false;
	},
	
	/**
	 * Remove cookie.
	 * @public
	 * @param {String} name
	 * 		Name of cookie going to remove.
	 * @return {bool}
	 * 		If cookie existed and successfully removed, return true.
	 * 		Otherwise, return false.
	 */
	removeCookie: function(name) {
		var value = this.getCookie(name);
		if (value != false) {
			jp.ggaogg.util.CookieOperator.setCookie(name, value, new Date().toGMTString());
			return true;
		} else {
			return false;
		}
	}
};


/**
 * Registory of objects.
 * 
 * @author KenjiNagao
 * @copyright Copyright (c) 2008 Kenji Nagao
 * @class Registory of objects.
 */
jp.ggaogg.util.ObjectRegistory = function() {
	
};
jp.ggaogg.util.ObjectRegistory.prototype = {
	idCount:0,
	container: new Array(),
	
	/**
	 * Register object to this instance.
	 * @param {Object} obj
	 * 		Any object.
	 * @return {int}
	 * 		ID for object used to get(id) and remove(id).
	 */
	register: function(obj) {
		this.container[this.container.length] = {"id": this.idCount, "obj": obj};
		return this.idCount++;
	},
	
	/**
	 * Get object by a unique key registered before.
	 * @param {int} id
	 * 		ID object attached to ObjectManager.
	 * @return {object|false}
	 * 		Object if there is registered object for id, or false if not registered.
	 */
	get: function(id) {
		for (var i = 0 ; i < this.container.length ; i++) {
			var currItem = this.container[i];
			if (id == currItem.id) {
				return currItem.obj;
			}
		}
		return false;
	},
	
	/**
	 * Remove object by a unique key.
	 * @param {int} id
	 * 		ID of object that was created at regist(obj) method.
	 */
	remove: function(id) {
		for (var i = 0 ; i < this.container.length ; i++) {
			var currItem = this.container[i];
			if (id == currItem.id) {
				this.container.splice(i, 1);
				return true;
			}
		}
		return false;
	}
};

var MemberCommunity = {};


MemberCommunity.registory = new jp.ggaogg.util.ObjectRegistory();

// --------- //
// Constants //
// --------- //
/**
 * @author KenjiNagao
 * @class Constants for MemberCommunity.
 */
MemberCommunity.Constants = {
	
	// network and connection settings //
	/*
	 * For ealier step test.
	 */
    requestURL: 'http://membercommunitytest/js/src/stub/server.php',
	/**
	 * Server URL string
	 */
	//requestURL: './php/src/Main.php',
	//requestURL: 'http://membercommunity.testserver1/_bin/MemberCommunity/php/src/Main.php',
	
	/**
	 * Request method type string.
	 */
    requestMethod: 'post',
	
	// data restriction
	
	/**
	 * Maximum length of name.
	 * If inputted value was larger than this, then ignore the submit.
	 */
	nameMaxLen: 18,
	/**
	 * Maximum length of comment.
	 * If inputted value was larger than this, then ignore the submit.
	 */
	commentMaxLen: 100,
	/**
	 * Maximum length of message.
	 * If inputted value was larger than this, then ignore the submit.
	 */
	messageMaxLen: 100,
	

	// appearance //
	/**
	 * String will be displayed at the top of this script.
	 */
	TITLE: 'MemberCommunity',
	/**
	 * Background-color of title.
	 */
	TITLE_BGCOLOR: '#FF8888',
	/**
	 * Width of area of this script.
	 */
	AREA_WIDTH: '200px',
	/**
	 * Width of area of members.
	 */
	MEMBERNAME_WIDTH: '190px',
	/**
	 * Describe which side does window will displayed. 'right', or 'left' for mouse pointer.
	 */
	WND_DISP_POSIX: 'right',
	/**
	 * Describe which side does window will displayed. 'above', or 'below' for mouse pointer.
	 */
	WND_DISP_POSIY: 'below'
};
MemberCommunity.constants = {};
MemberCommunity.constants.ClassNameConstants = {
	/** 
	 * class name for window's content
	 */
	WINDOW: 'memberCommunity_window'
};

MemberCommunity.constants.IdNameConstants = {
	/**
	 * id name for name input element in profile editor.
	 */
	ID_PROFILEEDITOR_NAME: 'memberCommunity_profileeditor_name',
	/**
	 * id name for color select element in profile editor.
	 */
	ID_PROFILEEDITOR_COLOR: 'memberCommunity_profileeditor_color',
	/**
	 * id name for bgColor select element in profile editor.
	 */
	ID_PROFILEEDITOR_BGCOLOR: 'memberCommunity_profileeditor_bgColor',
	/**
	 * id name for comment input element in profile editor.
	 */
	ID_PROFILEEDITOR_COMMENT: 'memberCommunity_profileeditor_comment'
};

MemberCommunity.constants.CookieNameConstants = {
	
	/**
	 * Cookie name for id property of viewer.
	 */
	ID: 'id',
	/**
	 * Cookie name for name property of viewer.
	 */
	NAME: 'name',
	/**
	 * Cookie name for color property of viewer.
	 */
	COLOR: 'color',
	/**
	 * Cookie name for bgColor property of viewer.
	 */
	BGCOLOR: 'bgColor',
	/**
	 * Cookie name for comment property of viewer.
	 */
	COMMENT: 'comment'
}

MemberCommunity.constants.LoggingConstants = {
	"MemberCommunity.controller.request.AbstractRequest": Log4js.Level.OFF,
	'MemberCommunity.controller.request.MembersRequest': Log4js.Level.OFF,
	'MemberCommunity.controller.request.JoinRequest': Log4js.Level.OFF,
	'MemberCommunity.controller.request.UpdateRequest': Log4js.Level.OFF,
	'MemberCommunity.controller.request.RefreshRequest': Log4js.Level.OFF,
	'MemberCommunity.controller.request.GetMessageListRequest': Log4js.Level.OFF,
	'MemberCommunity.controller.request.SendMessageRequest': Log4js.Level.OFF,
	'MemberCommunity.controller.request.RemoveMessageRequest': Log4js.Level.OFF,
	'MemberCommunity.model.message.MessageManager': Log4js.Level.OFF,
	'MemberCommunity.model.member.Members': Log4js.Level.OFF,
	'MemberCommunity.view.member.MemberTable': Log4js.Level.OFF,
	
	'MemberCommunity.window.Window': Log4js.Level.OFF,
	'MemberCommunity.window.SingletonWindow': Log4js.Level.OFF,
	
	'MemberCommunity.window.member.CommentWindow': Log4js.Level.OFF,
	'MemberCommunity.window.member.MenuWindow': Log4js.Level.OFF,
	'MemberCommunity.window.viewer.ShowMessageWindow': Log4js.Level.OFF,
	
	'MemberCommunity.window.viewer.MessageListWindow': Log4js.Level.OFF,
	'MemberCommunity.window.viewer.ProfileEditor': Log4js.Level.OFF,
	'MemberCommunity.window.viewer.ShowMessageWindow': Log4js.Level.OFF,
	
	'MemberCommunity.Main': Log4js.Level.OFF
};

MemberCommunity.constants.IntervalConstants = {
	
	membersRequest: 60 * 1000,
	
	refreshRequest: 30 * 1000,
	
	getMessageListRequest: 15 * 1000
}
// ------- //
// Globals //
// ------- //
/**
 * Contain variables that referenced by　various classes.
 * @class
 */
MemberCommunity.Globals = {
	/**
	 * Instance returned by setInterval() function.
	 * 
	 * This value is saved to kill intervals.
	 */
	membersRequestIntervalObj: undefined,
	refreshRequestIntervalObj: undefined,
	getMessageListRequestIntervalObj: undefined,
	
	/**
	 * Calculate x-location.
	 * 
	 * 
	 * @param {Object} posi
	 * 		Position of x.
	 * @param {Object} size
	 * 		Size of width.
	 */
	calcWndPosiX: function(posi, size) {
		if (MemberCommunity.Constants.WND_DISP_POSIX == "right") {
			return posi;
		} else {
			return posi - size;
		}
	},
	
	calcWndPosiY: function(posi, size) {
		if (MemberCommunity.Constants.WND_DISP_POSIY == "below") {
			return posi;
		} else {
			return posi - size;
		}
	}
};

/**
 * @class
 * @param {String} msg
 */
function InvalidArgumentException(msg) {
	this.name = "InvalidArgumentException";
	this.message = msg;
	this.description = this.name + ":message " + this.message;
}

InvalidArgumentException.prototype = new Error();

/**
 * @class
 * @param {Object} msg
 */
function IllegalStateException(msg) {
	this.name = 'IllegalStateException';
	this.message = msg;
	this.description = this.name + ':message ' + this.message;
}

IllegalStateException.prototype = new Error();

if (!MemberCommunity.model) {
	MemberCommunity.model = {};
}

if (!MemberCommunity.model.member) {
	MemberCommunity.model.member = {};
}

/**
 * @author KenjiNagao
 * @class Describe about person.
 * @param {int} id
 * 			Id of person.
 * @param {String} name
 * 			Name of person.
 * 			The length must be more than 0 characters and less than {@link MemberCommunity.Constants.nameMaxLen}.
 * @param {String} color
 * 			Color of person.
 * 			The length must be 7.
 * @param {String} bgColor
 * 			Background color of person.
 * 			The length must be 7.
 * @param {String} comment
 * 			Comment of person.
 * 			The length must be less than {@link MemberCommunity.Constants.commentMaxLen}, and not empty.
 * @throws InvalidArgumentException
 * 			One or more of arguments are invalid.
 */
MemberCommunity.model.member.Person = function(id, name, color, bgColor, comment, isOnline) {
		var util = jp.ggaogg.util;
		
		this.setId(id);
		this.setName(name);
		this.setColor(color);
		this.setBgColor(bgColor);
		this.setComment(comment);
		
		this.isOnline = isOnline;
};

MemberCommunity.model.member.Person.ID_DEFAULT = -1;

MemberCommunity.model.member.Person.unmarshal = function(personElement) {
				
	var name = String(personElement.getElementsByTagName('name').item(0).getFirstChild().getNodeValue());
	var color = String(personElement.getElementsByTagName('color').item(0).getFirstChild().getNodeValue());
	var bgColor = String(personElement.getElementsByTagName('bgcolor').item(0).getFirstChild().getNodeValue());
	var comment = String(personElement.getElementsByTagName('comment').item(0).getFirstChild().getNodeValue());
				
	var person = new MemberCommunity.model.member.Person(
		parseInt(personElement.getElementsByTagName('id').item(0).getFirstChild().getNodeValue()),
		name, color, bgColor, comment, true
	);
	
	return person;
}

MemberCommunity.model.member.Person.prototype = {
	
	util: jp.ggaogg.util,
	
	/**
	 * ID of person.
	 * @private
	 * @field int
	 * @default -1
	 */
	id: -1,
	/**
	 * Name of person.
	 * @private
	 * @field String
	 * @default 'あなた'
	 */
    name: 'あなた',
	/**
	 * Color of person.
	 * @private
	 * @field String
	 * @default '#000000'
	 */
    color: '#000000',
	/**
	 * Background color of person.
	 * @private
	 * @field String
	 * @default '#FFFFFF'
	 */
	bgColor: '#FFFFFF',
	/**
	 * Comment of person.
	 * @private
	 * @field String
	 * @default ''
	 */
    comment: '',
	/**
	 * Which online(true) or offline(false).
	 */
	isOnline: false,

	// ------ //
	// setter //
	// ------ //
	setId: function(id) {
		if (this.util.isSet(id)) {
			if (!this.util.isNumber(id)) {
				throw new InvalidArgumentException('invalid id');
			}
			this.id = id;
		}
	},
	
	setName: function(name) {
		if (this.util.isSet(name)) {
			if (!this.util.isString(name)) {
				throw new InvalidArgumentException('無効な名前です。');
			} else if (name.length > MemberCommunity.Constants.nameMaxLen) {
				throw new InvalidArgumentException('名前が長すぎます。およそ' + MemberCommunity.Constants.nameMaxLen + '文字以内です。');
			} else if (name.empty()) {
				throw new InvalidArgumentException('名前を記入して下さい。');
			}
			
			this.name = name;
		}
	},
	
	setColor: function(color) {
		if (this.util.isSet(color)) {
			if (!this.util.isString(color) || color.length != 7) {
				throw new InvalidArgumentException('無効な色です。');
			}
			this.color = color;
		}
	},
	
	setBgColor: function(bgColor) {
		if (this.util.isSet(bgColor)) {
			if (!this.util.isString(bgColor) || bgColor.length != 7) {
				throw new InvalidArgumentException('無効な背景色です。');
			}
			this.bgColor = bgColor;
		}
	},
	
	setComment: function(comment) {
		if (this.util.isSet(comment)) {
			if (!this.util.isString(comment)) {
				throw new InvalidArgumentException('無効なコメントです。');
			} else if (comment.length > MemberCommunity.Constants.commentMaxLen) {
				throw new InvalidArgumentException('コメントが長すぎます。およそ' + MemberCommunity.Constants.commentMaxLen + '文字以内までです。');
			} else if (comment.empty()) {
				throw new InvalidArgumentException('コメントを記入して下さい。');
			}
			this.comment = comment;
		}
	},
	
	setIsOnline: function(isOnline) {
		this.isOnline = isOnline;
	},
	
	// ------ //
	// getter //
	// ------ //
	/**
	 * Get id of person.
	 * @return int
	 * 			Positive id value.
	 */
	getId: function() {
		return this.id;
	},
	
	/**
	 * Get name of person.
	 * @return String
	 */
	getName: function() {
		return this.name;
	},
	
	/**
	 * Get color of person.
	 * @return String
	 */
	getColor: function() {
		return this.color;
	},
	
	/**
	 * Get background color of person.
	 * @return String
	 */
	getBgColor: function() {
		return this.bgColor;
	},
	
	/**
	 * Get comment of person.
	 * @return String
	 */
	getComment: function() {
		return this.comment;
	},
	
	/**
	 * Get whether online or not.
	 */
	getIsOnline: function() {
		return this.isOnline;
	}
};

if (!MemberCommunity.model.message) {
	MemberCommunity.model.message = {};
}

/**
 * @class
 * @param {int} id
 * 		Id of message.
 * @param {int} from
 * 		Id of member the message he was sent.
 * @param {int} time
 * 		GMT time in milli seconds.
 * @param {String} body
 * 		Message string.
 * @throws InvalidArgumentException
 * 		One or more of each parameters were undefined. 
 */
MemberCommunity.model.message.Message = function(id, from, time, body) {
	var util = jp.ggaogg.util;
	
	if (!util.isSet(id)) throw new InvalidArgumentException('id is not set.');
	if (!util.isSet(from)) throw new InvalidArgumentException('from is not set.');
	if (!util.isSet(time)) throw new InvalidArgumentException('time is not set.');
	if (!util.isSet(body)) throw new InvalidArgumentException('body is not set.');
	
	this.id = id;
	this.from = from;
	this.time = time;
	this.body = body;
}

MemberCommunity.model.message.Message.prototype = {
	
	getId: function() {
		return this.id;
	},
	
	getFrom: function() {
		return this.from;
	},
	
	getPostedTime: function() {
		return this.time;
	},
	
	getPostedLocaleTime: function() {
		return this.time + 9 * 60 * 60 * 1000;
	},
	
	getBody: function() {
		return this.body;
	}
};

/**
 * @class
 */
MemberCommunity.model.message.MessageManager = {
	
	logger: Log4js.getLogger('MemberCommunity.model.message.MessageManager'),
	messages: new Array(),
	
	/**
	 * Add members from XML string.
	 * @param {String} messagesXML
	 * 		Members XML string.
	 */
	addMessagesFromXML: function(messagesXML) {
		this.logger.trace('addMessagesFromXML() called.');
		
		var parser = new DOMImplementation();
		parser.preserveWhiteSpace = true;
		this.logger.trace('addMessagesFromXML() parse XML.');
		var messagesDOM = parser.loadXML(messagesXML);
		var messagesElement = messagesDOM.getDocumentElement();
		this.logger.trace('addMessagesFromXML() got documentElement.');
		var nlMessageElement = messagesElement.getElementsByTagName('message');
		
		for (var i = 0 ; i < nlMessageElement.getLength() ; i++) {
			this.logger.trace('addMessagesFromXML() create MemberCommunity.Message and add.');
			var currMessageElement = nlMessageElement.item(i);
			var message = new MemberCommunity.model.message.Message(
				currMessageElement.getElementsByTagName('id').item(0).getFirstChild().getNodeValue(),
				currMessageElement.getElementsByTagName('from').item(0).getFirstChild().getNodeValue(),
				parseInt(currMessageElement.getElementsByTagName('time').item(0).getFirstChild().getNodeValue()),
				currMessageElement.getElementsByTagName('body').item(0).getFirstChild().getNodeValue()
			);
			if (this.getById(message.getId()) == false) {
				this.logger.debug('addMessagesFromXML() message pushed with id ' + message.getId());
				this.messages.push(message);
			}
		}
		
		this.logger.trace('addMessagesFromXML() finished. Now ' + this.messages.length + ' messages in Messages.');
	},
	
	/**
	 * Get message array.
	 * @return {Array}
	 * 		Message array this storage managing.
	 */
	getArray: function() {
		return this.messages;
	},
	
	/**
	 * Remove a message from this storage, and server.
	 * @param {int} id
	 * 		Id of message to be removed.
	 * @return {boolean}
	 * 		true: if removed, false: if did not removed.
	 */
	removeById: function(id) {
		this.logger.trace('removeMessage() called.');
		
		MemberCommunity.controller.request.RemoveMessageRequest.request({id: id});
		
		var messages = this.messages;
		for (var i = 0 ; i < messages.length ; i++) {
			if (id == messages[i].getId()) {
				messages.splice(i, 1);
				this.logger.trace('removeMessage() finished with true.');
				return true;
			}
		}
		
		this.logger.trace('removeMessage() finished with false.');
		return false;
	},
	
	/**
	 * Get message its id equals messageId.
	 * @param {int} messageId
	 * @return {MemberCommunity.model.message.Message|false}
	 * 		
	 */
	getById: function(messageId) {
		var messages = this.messages;
		
		for (var i = 0 ; i < messages.length ; i++) {
			var currMessage = messages[i];
			if (messageId == currMessage.getId()) {
				return currMessage;
			}
			this.logger.debug('getById() ' + messageId + ' != ' + currMessage.getId());
		}
		
		return false;
	},
	
	getLatestId: function() {
		var messages = this.messages;
		var latestId = -1;
		for (var i = 0 ; i < messages.length ; i++) {
			var message = messages[i];
			if (latestId < message.getId()) {
				latestId = message.getId();
			}
		}
		
		return latestId;
	}
};

if (!MemberCommunity.controller) {
	MemberCommunity.controller = {};
}

if (!MemberCommunity.controller.request) {
	MemberCommunity.controller.request = {};
}

MemberCommunity.controller.request.AbstractRequest = Class.create({
	
	logger: Log4js.getLogger("MemberCommunity.controller.request.AbstractRequest"),
	
	/**
	 * Get parameters that will be sent as request parameters.
	 * 
	 * 
	 * @abstract
	 * @protected
	 * @param {JSON} parametersJSON
	 * 			JSON value it is specified at request() method called.
	 */
	getParameters: function(parametersJSON) {
		return parametersJSON;
	},
	
	/**
	 * Get asynchronous or not.
	 * 
	 * The default implementation return false (it means synchronous request).
	 * 
	 * If want to request asynchronous, override and return true.
	 * 
	 * @abstract
	 * @protected
	 * @return
	 * 		Asynchronous of not.
	 */
	getAsynchronous: function() {
		return false;
	},
	
	/**
	 * Caller call this method.
	 * 
	 * @public
	 * @param {JSON} parameters
	 * 		JSON notation parameters.
	 * 		It`s possible to omit.
	 */
	request: function(parameters) {
		if (!parameters) {
			parameters = {};
		}
		this.actualRequest(this.getParameters(parameters), this.getAsynchronous());
	},
	
	/**
	 * Actually request to server.
	 * @protected
	 * @param {JSON} parameters
	 * 		JSON notated object that contains parameters actually sent to server.
	 * @param {Bool} asynchronous
	 * 		true: Sleep until response, false: Immediately return.
	 */
	actualRequest: function(parameters, asynchronous) {
		new Ajax.Request(MemberCommunity.Constants.requestURL, {
			method: MemberCommunity.Constants.requestMethod,
			asynchronous: asynchronous,
			parameters: parameters,
			onSuccess: this.onSuccess.bindAsEventListener(this),
			onFailure: this.onFailure.bindAsEventListener(this)
		});
	},
	
	/**
	 * Called when response has been returned.
	 * 
	 * This method call onAccepted, onDenied, or onError method for proper situation.
	 * 
	 * @protected
	 * 
	 * @param {XMLHttpRequest} request
	 * 		XMLHttpRequest class instance that already has response.
	 */
	onSuccess: function(request) {
		this.logger.debug('Response text = ' + request.responseText.escapeHTML());
		var respJSON = request.responseText.evalJSON();

		switch (respJSON.status) {
			case 'ERROR':
				this.onError(respJSON);
			break;
			case 'DENY':
				this.onDeny(respJSON);
			break;
			case 'OK':
				this.onAccepted(respJSON);
			break;
		}
	},
	
	/**
	 * The case that no error occured and denied access.
	 * 
	 * This method only do that write out log.
	 * If want to do something other, you can override this method.
	 * 
	 * @protected
	 * 
	 * @param {JSON} request
	 * 		JSON evaluated object.
	 */
	onAccepted: function(respJSON) {
		this.logger.info("Request accepted.");
	},
	
	/**
	 * The case that the connection there are no matter but server denied.
	 * 
	 * This method only write out the log.
	 * If want to do something other, you can override this method.
	 * 
	 * @protected
	 * 
	 * @param {JSON} request
	 * 		JSON evaluated object.
	 */
	onDenied: function(respJSON) {
		this.logger.warn("Request denied. Reason : " + respJSON.reason);
	},
	
	/**
	 * The case that the connection there are no matter but error occured at server side.
	 * 
	 * This method only write out the log.
	 * If want to do something other, you can override this method.
	 * 
	 * @protected
	 * 
	 * @param {JSON} request
	 * 		JSON evaluated object.
	 */
	onError: function(respJSON) {
		this.logger.error("Error occured. Reason : " + respJSON.reason);
	},
	
	/**
	 * The case that the request connection was failed.
	 * 
	 * This method only do that write out to log.
	 * If want to do something other, you can override this method.
	 * 
	 * @protected
	 * 
	 * @param {XMLHttpRequest} request
	 * 		XMLHttpRequest class instance that already has response.
	 */
	onFailure: function(request) {
		this.logger.error("Request failed. Reason : " + respJSON.reason);
	}
});

MemberCommunity.controller.request.JoinRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger: Log4js.getLogger('MemberCommunity.controller.request.JoinRequest'),
	
	/**
	 * Add "type" property to request with "join" value.
	 * 
	 * If cookie has id, name, color, bgColor, and comment, then request with them.
	 * 
	 * @protected
	 * @param {JSON} parametersJSON
	 * 		JSON request method received.
	 * @return {JSON}
	 * 		JSON it is modified one of parametersJSON.
	 */
	getParameters: function(parametersJSON) {
		parametersJSON.type = "join";
		
		var cookieOperator = new jp.ggaogg.util.CookieOperator();
		var id = cookieOperator.getCookie(MemberCommunity.constants.CookieNameConstants.ID);
		var name = cookieOperator.getCookie(MemberCommunity.constants.CookieNameConstants.NAME);
		var color = cookieOperator.getCookie(MemberCommunity.constants.CookieNameConstants.COLOR);
		var bgColor = cookieOperator.getCookie(MemberCommunity.constants.CookieNameConstants.BGCOLOR);
		var comment = cookieOperator.getCookie(MemberCommunity.constants.CookieNameConstants.COMMENT);
		
		if (id && name && color && bgColor && comment) {
			parametersJSON.id = id;
			parametersJSON.name = name;
			parametersJSON.color = color;
			parametersJSON.bgColor = bgColor;
			parametersJSON.comment = comment;
			
			var viewer = MemberCommunity.view.member.MemberTable.getViewer();
			viewer.setName(name);
			viewer.setColor(color);
			viewer.setBgColor(bgColor);
			viewer.setComment(comment);
		}
		
		return parametersJSON;
	},
	
	/**
	 * This case - the case accepted - server response with id,
	 * so this method preserve the id to the cookie.
	 *  
	 * @protected
	 * @param {Object} $super
	 * @param {JSON} respJSON
	 * 			JSON object that contains id property.
	 */
	onAccepted: function($super, respJSON) {
		$super(respJSON);
		
		var cookieOperator = new jp.ggaogg.util.CookieOperator({days:100});
		cookieOperator.setCookie(MemberCommunity.constants.CookieNameConstants.ID, respJSON.id);
	}
	
}))();

MemberCommunity.controller.request.MembersRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger: Log4js.getLogger('MemberCommunity.controller.request.MembersRequest'),
	
	/**
	 * Add type property to request JSON with "members" value.
	 * @protected
	 * @param {JSON} parametersJSON
	 * 		JSON object it contains request parameters.
	 */
	getParameters: function(parametersJSON) {
		parametersJSON.type = "members";
		return parametersJSON;
	},
	
	/**
	 * 
	 * @param {Object} $super
	 * @param {JSON} respJSON
	 * 		JSON object that was evaluated from response text.
	 */
	onAccepted: function($super, respJSON) {
		$super(respJSON);
		
		MemberCommunity.model.member.Members.addByXML(respJSON.xml);
		MemberCommunity.view.member.MemberTable.refresh();	
		MemberCommunity.Request.logger.info('members request success.');
	}
}))();

/**
 * Request to the server for the viewer`s refresh.
 * 
 * This class only do pass {type: "refresh"} request to the server.
 * If an error occured, log it and do nothing than that.
 */
MemberCommunity.controller.request.RefreshRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger: Log4js.getLogger('MemberCommunity.controller.request.RefreshRequest'),
	
	getParameters: function(parametersJSON) {
		parametersJSON.type = "refresh";
		return parametersJSON;
	}
}))();

MemberCommunity.controller.request.UpdateRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger : Log4js.getLogger('MemberCommunity.controller.request.UpdateRequest'),
	
	/**
	 * 
	 * @param {JSON} parametersJSON
	 */
	getParameters: function(parametersJSON) {
		parametersJSON.type = "update";
		return parametersJSON;
	}
}))();

/**
 * Send message to a member(via server).
 * 
 * Parameters set to request method argument.
 * body : message body.
 * to : destination the message sent.
 */
MemberCommunity.controller.request.SendMessageRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger : Log4js.getLogger('MemberCommunity.controller.request.SendMessageRequest'),
	
	/**
	 * 
	 * @param {JSON} parametersJSON
	 * 			Have 'body' and 'to' parameter.
	 */
	getParameters : function(parametersJSON) {
		parametersJSON.type = 'sendMessage';
		return parametersJSON;
	}
}))();

/**
 * Get message list to viewer.
 */
MemberCommunity.controller.request.GetMessageListRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger : Log4js.getLogger('MemberCommunity.controller.request.GetMessageListRequest'),
	
	/**
	 * @protected
	 * @param {JSON} parametersJSON
	 */
	getParameters : function(parametersJSON) {
		parametersJSON.type = 'getMessageList';
		parametersJSON.id = MemberCommunity.model.message.MessageManager.getLatestId() + 1;
		return parametersJSON;
	},
	
	/**
	 * Inform response XML to MessageManager.
	 * @protected
	 * @param {JSON} responseJSON
	 */
	onAccepted: function(responseJSON) {
		MemberCommunity.model.message.MessageManager.addMessagesFromXML(responseJSON.xml);
	}
}))();

/**
 * Request to remove a message.
 * 
 * The request method`s parameter must contain id field.
 */
MemberCommunity.controller.request.RemoveMessageRequest = new (Class.create(MemberCommunity.controller.request.AbstractRequest, {
	
	logger : Log4js.getLogger('MemberCommunity.controller.request.RemoveMessageRequest'),
	
	/**
	 * @param {JSON} parametersJSON
	 */
	getParameters: function(parametersJSON) {
		parametersJSON.type = "removeMessage";
		return　parametersJSON;
	}
}))();
// ----------- //
// Initializer //
// ----------- //
/**
 * Initialization work.
 * 
 * One is initialize logger(Log4js).<br />
 * And the other is write necessary HTML for MemberCommunity.
 * 
 * Many classes are depends on this class's initialization.
 * 
 * @author KenjiNagao
 * @class Include initialize methods.
 */
MemberCommunity.Initializer = {
	/**
	 * Call {@link MemberCommunity.Initializer.initializeLogger} and {@link MemberCommunity.Initializer.writeInfra} functions.
	 * @function
	 * @public
	 */
	initializeAll: function() {
		this.initializeLogger();
		this.writeInfra();
	},
	
	/**
	 * Setting up loggers.
	 * @function
	 * @public
	 */
	initializeLogger: function(){
		
		var consoleAppender = new Log4js.ConsoleAppender();
		
		var keys = Object.keys(MemberCommunity.constants.LoggingConstants);
		var values = Object.values(MemberCommunity.constants.LoggingConstants);
		
		for (var i = 0 ; i < keys.length ; i++) {
			var logger = Log4js.getLogger(keys[i]);
			logger.setLevel(values[i]);
			logger.addAppender(consoleAppender);
			//logger.info(values[i] + ' logger initialized.');
		}
	
	},
	
	/**
	 * Write infrastructure HTML code for MemberCommunity.
	 * @function
	 * @public
	 * @throws IllegalStateException
	 */
	writeInfra: function(){
		var styleHTML = 
			'<style type="text/css">' +
				'#memberCommunity {position:relative; width:' + MemberCommunity.Constants.AREA_WIDTH + '; border:1px solid #000000;}' +
				'#memberCommunity_title {background-color:' + MemberCommunity.Constants.TITLE_BGCOLOR + '; text-align:center;}' +
				'#memberCommunity_members table {font-weight:bold;}' +
				'#memberCommunity_members table tbody tr td {width:' + MemberCommunity.Constants.MEMBERNAME_WIDTH + ';}' +
			'</style>';
		
		var infra = styleHTML + "\n" + 
		'<table id="memberCommunity">' +
		'<tr><td id="memberCommunity_title">' + MemberCommunity.Constants.TITLE + '</td></tr>' +
		'<tr><td id="memberCommunity_members"></td></tr>' +
		'<tr><td id="memberCommunity_copyright"><a href="http://membercommunity.nice-777.com">MemberCommunity</a></td></tr>' +
		'</table>';
		
		document.write(infra);
	}
};

MemberCommunity.Initializer.initializeAll();

/**
 * Manage about members.
 * 
 * 
 * @class
 */
MemberCommunity.model.member.Members = {
	
	logger: Log4js.getLogger('MemberCommunity.model.member.Members'),
	members: new Array(),
	
	/**
	 * Set members by a Document object that contains &lt;members&gt; element.
	 * @param {DOMDocument} membersDOM
	 * 		Document object of 'XML for &lt;SCRIPT&gt;' library.
	 * 		It contains &lt;members&gt; element.
	 * @throws InvalidArgumentException
	 * 		membersDOM was not collect.
	 */
    addByXML: function(membersXML){
		this.logger.trace('addByXML() called.');
		
		var parser = new DOMImplementation();
		parser.preserveWhiteSpace = true;
		var membersDOM = parser.loadXML(membersXML);
		var membersElements = membersDOM.getElementsByTagName('members');
		
		if (membersElements.getLength() <= 0) {
			this.logger.warn('setMembersByDOM() throws InvalidArgumentException.');
			throw new InvalidArgumentException('Invalid DOM');
		} else {
			this.logger.trace('setMemberByDOM() does not throw InvalidArgumentException.');
			var membersElement = membersElements.item(0);
	        var nlMembersElement = membersElement.getChildNodes();
	      	
	        this.members = new Array();
			
			this.logger.trace('setMembersByDOM() before individual member loop');
			
	        for (i = 0; i < nlMembersElement.getLength(); i++) {
				this.logger.trace('setMembersByDOM() head of individual member loop.');
	            var currPersonElement = nlMembersElement.item(i);
				this.members[i] = MemberCommunity.model.member.Person.unmarshal(currPersonElement);
	        }
			
			this.logger.trace('addByXML() successfully. ' + nlMembersElement.getLength() + ' members.');
			return true;
		}
    },
	
	/**
	 * Get members array.
	 * @return {Array}
	 * 		Array that contains person instances.
	 */
	getArray: function() {
		return this.members;
	},
	
	/**
	 * Get member its id equals argument id.
	 * @param {int} id
	 * 		Id of member.
	 * @return {model.member.Person|false}
	 * 		If id matched member exists, then return that member.
	 * 		Otherwise, return false.
	 */
	getById: function(id) {
		var members = this.members;
		for (var i = 0 ; i < members.length ; i++) {
			var currMember = members[i];
			if (id == currMember.getId()) {
				return currMember;
			}
		}
		
		return this.findById(id);
	},
	
	/**
	 * Find member from server who has already been offline.
	 * @param {number} id
	 * 		Id of person for looking for.
	 * @return model.member.Person|false
	 */
	findById: function(id) {
		this.logger.trace('findById(' + id + ') called.');
		this.findedPerson = false;
		MemberCommunity.Request.findMember(id);
		this.logger.trace('findById(' + id + ') finished.');
		return this.findedPerson;
	},
	
	findedPerson: false,
	
	/**
	 * Only MemberCommunity.Request.findMember() method call this method.
	 * @param {MemberCommunity.model.member.Person} person
	 * 		Person who is not online but still on DB.
	 */
	informFinded: function(person) {
		this.logger.debug('informFinded() called.');
		this.findedPerson = person;
		this.findedPerson.setIsOnline(false);
		this.logger.debug('informFinded() finished.');
	} 
};

if (!MemberCommunity.view) {
	MemberCommunity.view = {};
}

if (!MemberCommunity.view.member) {
	MemberCommunity.view.member = {};
}

/**
 * MemberTable manages viewer and other members data and display table.
 * @class Table for members.
 * @author KenjiNagao
 */
MemberCommunity.view.member.MemberTable = {

	logger: Log4js.getLogger('MemberCommunity.view.member.MemberTable'),

    // MemberCommunity.model.member.Person class instance.
    viewer: new MemberCommunity.model.member.Person(),
    
	/**
	 * Refresh area of members with this class's data.
	 */
    refresh: function(){
        var tableHTML = '<table><tbody>' +
        '<tr><td style="background-color:' + this.viewer.getBgColor() + ';"><a href="#" style="color:' +
        this.viewer.getColor() +
        ';" id="MemberCommunity_viewer" ' +
		'onclick="return MemberCommunity.window.viewer.MenuWindow.show(event);" ' +
		'>&nbsp;' +
        this.viewer.getName().escapeHTML() +
        '</a></td></tr>';
        
        var i;
		var members = MemberCommunity.model.member.Members.getArray();
        for (i = 0; i < members.length; i++) {
			var currMember = members[i];
            tableHTML += '<tr><td style="background-color:' + currMember.getBgColor() + ';">' +
				'<a href="#" id="memberCommunity_member_' + currMember.getId() + '" ' +
				'onclick="return MemberCommunity.window.member.MenuWindow.show(event, this)" ' +
				'style="color:' + currMember.getColor() + ';">&nbsp;' + 
				currMember.getName().escapeHTML() + '</a></td></tr>';
        }
        
        tableHTML += '</tbody></table>';
        
		this.logger.debug('memberCommunity_members innerHTML will be tableHTML value');
        $('memberCommunity_members').innerHTML = tableHTML;
		
    },
    
	/**
	 * Set viewer with a JSON includes name, color, comment properties.
	 * @param {MemberCommunity.model.member.Person} person
	 * 		MemberCommunity.model.member.Person instance object.
	 */
    setViewer: function(person) {
    	if (person instanceof MemberCommunity.model.member.Person) {
    		this.viewer = person;
    	} else {
    		throw new InvalidArgumentException('person expects MemberCommunity.model.member.Person class instance.');
    	}
    },
	
	/**
	 * Get viewer in MemberCommunity.view.member.MemberTable.
	 * @return {MemberCommunity.model.member.Person}
	 */
	getViewer: function() {
		return this.viewer;
	}
};

if (!MemberCommunity.window) {
	MemberCommunity.window = {};
}

MemberCommunity.window.Window = Class.create({
	logger: Log4js.getLogger('MemberCommunity.window.Window'),
	win: undefined,
	sizeDefault: {width:100, height:100},
	
	initialize: function() {
		
	},
	
	/**
	 * 
	 * @param {Object} event
	 * @return {UI.Window}
	 * 		Window instance created.
	 */
	show: function(event) {
		this.logger.debug('show(event) called.');
		
		var win = this.win = new UI.Window({
			theme: 'bluelighting'
		});
		
		var size = this.createSize(this.sizeDefault);
		this.logger.trace('show() set size.');
		win.setBounds({
			width: size.width,
			height: size.height,
			left: MemberCommunity.Globals.calcWndPosiX(event.clientX, size.width),
			top: MemberCommunity.Globals.calcWndPosiY(event.clientY, size.height)
		});
		
		this.logger.trace('show() set content.');
		win.setContent('<div class="' + MemberCommunity.constants.ClassNameConstants.WINDOW + '">' + this.createContent() + '</div>');
		this.logger.trace('show() set title.');
		win.setHeader(this.createTitle());
		this.logger.trace('show() show and focus.');
		win.show().focus();
		
		this.logger.debug('show(event) finished.');
		
		return win;
	},
	
	destroy: function() {
		this.logger.info('destroy() called.');
		var win = this.win;
		if (win != undefined) {
			win.destroy();
			win = undefined;
		}
		this.logger.info('destroy() finished.');
	},
	
	/**
	 * Protected method intended to be extended.
	 * 
	 * Create content HTML code.
	 * The default value is empty string.
	 * @return {String}
	 * 		HTML code to be contained to created window.
	 */
	createContent: function() {
		return '';
	},
	
	/**
	 * Protected method intended to be extended.
	 * 
	 * Create title string.
	 * The default value is empty string.
	 * @return {String}
	 * 		String of window header.
	 */
	createTitle: function() {
		return '';
	},
	
	/**
	 * Set width and height to json object. 
	 * @param {JSON} json
	 * 		JSON Object it has width and height property.
	 */
	createSize: function(json) {
		return json;
	}
});

/**
 * This class is abstract class and intended extended and implement show(event) method.
 * 
 * @class
 * @param {Event} event
 */
MemberCommunity.window.SingletonWindow = Class.create(MemberCommunity.window.Window, {
	
	logger: Log4js.getLogger('MemberCommunity.window.SingletonWindow'),
	
	/**
	 * Destroy own window before open new window. 
	 * @param {Object} $super
	 * 		Prototype.js uses.
	 * @param {Object} event
	 * 		Event object.
	 * @return {UI.Window}
	 * 		Window instance created.
	 */
	show: function($super, event) {
		this.destroy();
		return $super(event);
	}
});

if (!MemberCommunity.window.viewer) {
	MemberCommunity.window.viewer = {};
}

/**
 * This class describe viewer`s menu window.
 */
MemberCommunity.window.viewer.MenuWindow = new (Class.create(MemberCommunity.window.SingletonWindow, {

	createContent: function() {
		return '<a href="#" onclick="MemberCommunity.window.viewer.ProfileEditor.show(event); MemberCommunity.window.viewer.MenuWindow.destroy();">' +
				'プロフィール変更</a><br /><br />' +
			'<a href="#" onclick="MemberCommunity.window.viewer.MessageListWindow.show(event); ' +
				'MemberCommunity.window.viewer.MenuWindow.destroy();">メッセージを読む</a>';
	},
	
	createTitle: function() {
		return "";
	},
	
	createSize: function(json) {
		json.width = 100;
		json.height = 40;
		return json;
	}
	
}))();

MemberCommunity.window.viewer.MessageListWindow = new (Class.create(MemberCommunity.window.SingletonWindow, {
	
	logger: Log4js.getLogger('MemberCommunity.window.viewer.MessageListWindow'),
	
	createContent: function() {
		var logger = this.logger;
		
		logger.debug('createContent() called.');
		
		var html = '';
			
		var messages = MemberCommunity.model.message.MessageManager.getArray();
		var members = MemberCommunity.model.member.Members.getArray();
		
		logger.trace('createContent() before roop for each messages.');
		for (var i = 0 ; i < messages.length ; i++) {
			var currMessage = messages[i];
			//var currTimeBefore = parseInt((new Date().getTime() - currMessage.getTime()) / (1000 * 60));
			var currMember = MemberCommunity.model.member.Members.getById(currMessage.getFrom());
			if (!currMember) {
				continue;
			}
			var postedDate = new Date();
			postedDate.setTime(currMessage.getPostedLocaleTime());
			
			html += 
				'<div><a href="#" style="color:' + currMember.getColor().escapeHTML() + 
					'; background-color:' + currMember.getBgColor().escapeHTML() + ';" ' +
					'onclick="new MemberCommunity.window.viewer.ShowMessageWindow(event, ' + currMessage.getId().escapeHTML() + ');">' +
				'From ' + currMember.getName().escapeHTML() + ' (' + postedDate.toLocaleString() + ')' +
				'</a></div>';
		}
		if (messages.length == 0) {
			html = 'メッセージはありません。';
		}
		
		logger.debug('createContent finished.');
		return html;
	},
	
	createTitle: function() {
		return 'メッセージ一覧';
	},
	
	createSize: function(json) {
		this.logger.trace('createSize called.');
		json.width = 400;
		json.height = 250;
		this.logger.trace('createSize finished.');
		return json;
	}
}))();
MemberCommunity.window.viewer.ShowMessageWindow = Class.create(MemberCommunity.window.Window, {
	logger: Log4js.getLogger('MemberCommunity.window.viewer.ShowMessageWindow'),
	message: undefined,
	member: undefined,
	id: undefined,
	initialize: function(event, messageId) {
		this.logger.debug('initialized window with message id ' + messageId);
		MemberCommunity.window.viewer.MessageListWindow.destroy();
		this.id = MemberCommunity.registory.register(this);
		
		this.message = MemberCommunity.model.message.MessageManager.getById(messageId);
		if (this.message === false) {
			alert('無効なメッセージです。');
			return;
		}
		this.member = MemberCommunity.model.member.Members.getById(this.message.getFrom());
		if (this.member === false) {
			alert('メッセージ送信者がすでにいません。');
			return;
		}
		this.show(event).focus();
		
	},
	
	createContent: function() {
		var html = 
			'<div style="color:' + this.member.getColor() + '; background-color:' + this.member.getBgColor() + ';">' +
				this.message.getBody().escapeHTML() +
			'</div>' +
			'<div>' +
				'<input type="button" value="メッセージ削除" ' +
					'onclick="MemberCommunity.model.message.MessageManager.removeById(' + this.message.getId() +'); ' +
					' MemberCommunity.registory.get('+this.id+').destroy();"/>' +
			'</div>';
		
		return html;
	},
	
	createTitle: function() {
		return this.member.getName().escapeHTML() + ' さんからのメッセージ';
	},
	
	createSize: function(json) {
		json.width = 300;
		json.height = 200;
		return json;
	},
	
	destroy: function($super) {
		MemberCommunity.registory.remove(this.id);
		$super();
	}
});

/**
 * @author KenjiNagao
 * @class Editor for profile
 */

MemberCommunity.window.viewer.ProfileEditor = new (Class.create(MemberCommunity.window.SingletonWindow, {
	
	logger: Log4js.getLogger('MemberCommunity.window.viewer.ProfileEditor'),

	name: undefined,
	color: undefined,
	bgColor: undefined,
	comment: undefined,
	
	show: function($super, event) {
		$super(event);
		
		this.reflectViewerFromMemberTable();
		this.reflectToWindow();
	},
	
	createContent: function() {
		var editorHTML = 
			'<form action="">' +
				'<div>' +
					'<fieldset><legend>名前</legend>' +
						'<input type="text" id="' + MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_NAME + '"/>' +
					'</fieldset>' +
				'</div>' +
				'<div>' +
					'<fieldset><legend>色</legend>' +
					'前景色:<select id="' + MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_COLOR + 
						'" onchange="MemberCommunity.window.viewer.ProfileEditor.reflectFromWindow();MemberCommunity.window.viewer.ProfileEditor.reflectToWindow();">' +
						'<option value="#000000" style="background-color:#000000">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#FF0000" style="background-color:#FF0000">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#00FF00" style="background-color:#00FF00">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#0000FF" style="background-color:#0000FF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#FFFF00" style="background-color:#FFFF00">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#00FFFF" style="background-color:#00FFFF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#FF00FF" style="background-color:#FF00FF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
					'</select>' +
					'背景色:<select id="' + MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_BGCOLOR + 
						'" onchange="MemberCommunity.window.viewer.ProfileEditor.reflectFromWindow();MemberCommunity.window.viewer.ProfileEditor.reflectToWindow();">' +
						'<option value="#FFFFFF" style="background-color:#FFFFFF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#FFDDDD" style="background-color:#FFDDDD">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#DDFFDD" style="background-color:#DDFFDD">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#DDDDFF" style="background-color:#DDDDFF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#FFFFDD" style="background-color:#FFFFDD">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#DDFFFF" style="background-color:#DDFFFF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#FFDDFF" style="background-color:#FFDDFF">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
						'<option value="#DDDDDD" style="background-color:#DDDDDD">&nbsp; &nbsp; &nbsp; &nbsp; </option>' +
					'</select>' +
					'</fieldset>' +
				'</div>' +
				'<div>' +
					'<fieldset><legend>コメント</legend>' +
						'<textarea id="' + MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_COMMENT + '"></textarea>' +
					'</fieldset>' +
				'</div>' +
				'<div>' +
					'<input type="button" value="決定" onclick="MemberCommunity.window.viewer.ProfileEditor.submit();" />&nbsp;' +
					'<input type="button" value="キャンセル" onclick="MemberCommunity.window.viewer.ProfileEditor.destroy()" />' +
				'</div>' +
			'</form>';
		
		return editorHTML;
	},
	
	createTitle: function() {
		return 'プロフィールの設定';
	},
	
	createSize: function(json) {
		json.width = 330;
		json.height = 250;
		return json;
	},
	
	/**
	 * Reflect viewer information to this class's fields.
	 */
	reflectViewerFromMemberTable: function() {
		this.logger.trace('reflectViewerFromMemberTable() start');
		var viewer = MemberCommunity.view.member.MemberTable.getViewer();
		this.setName(viewer.getName());
		this.setColor(viewer.getColor());
		this.setBgColor(viewer.getBgColor());
		this.setComment(viewer.getComment());
	},
	
	/**
	 * Submit editing.
	 * 
	 * Then set new value to view.member.MemberTable and request for update.
	 * 
	 * If invalid arguments are sed, then display alert and continue for edit.
	 */
	submit: function() {
		this.logger.trace('submit() start');
		this.reflectFromWindow();
		this.destroy();
		
		var id = MemberCommunity.view.member.MemberTable.getViewer().getId();
		var name = this.getName();
		var color = this.getColor();
		var bgColor = this.getBgColor();
		var comment = this.getComment();
		if (comment.length == 0) {comment = " ";}
		
		var viewer = MemberCommunity.view.member.MemberTable.getViewer();
		try {
			viewer.setName(name);
			viewer.setColor(color);
			viewer.setBgColor(bgColor);
			viewer.setComment(comment);
			MemberCommunity.view.member.MemberTable.refresh();
		} catch (e) {
			this.logger.debug(e);
			alert(e.message);
			return;
		}
		
		this.setToServer(viewer);
		this.setToCookie(viewer);
		
		this.logger.trace('submit() end');
	},
	
	setToCookie: function(person) {
		var cookieOperator = new jp.ggaogg.util.CookieOperator({days: 100});
		cookieOperator.setCookie(MemberCommunity.constants.CookieNameConstants.NAME, person.getName());
		cookieOperator.setCookie(MemberCommunity.constants.CookieNameConstants.COLOR, person.getColor());
		cookieOperator.setCookie(MemberCommunity.constants.CookieNameConstants.BGCOLOR, person.getBgColor());
		cookieOperator.setCookie(MemberCommunity.constants.CookieNameConstants.COMMENT, person.getComment());
	},
	
	/**
	 * Invoke MemberCommunity.controller.request.UpdateRequest.request() method to inform the update to server.
	 * @param {Person} person
	 */
	setToServer: function(person) {
		var parametersJSON = {
			name: person.getName(), 
			color: person.getColor(), 
			bgColor: person.getBgColor(), 
			comment: person.getComment()
		};
		MemberCommunity.controller.request.UpdateRequest.request(parametersJSON);		
	},
	
	/**
	 * Reflect name, color, bgColor, comment properties to editor window.
	 * @private
	 */
	reflectToWindow: function() {
		this.logger.trace('reflectToWindow() start');
		
		this.getNameElem().value = this.getName();
		
		this.getColorElem().value = this.getColor();
		this.getColorElem().style.backgroundColor = this.getColor();
		
		this.getBgColorElem().value = this.getBgColor(); 
		this.getBgColorElem().style.backgroundColor = this.getBgColor();
		
		this.getCommentElem().value = this.getComment();	
		
		this.logger.trace('reflectToWindow() end');	
	},
	
	/**
	 * @private
	 */
	reflectFromWindow: function() {
		this.logger.trace('reflectFromWindow start');
		
		this.setName(this.getNameElem().value);
		this.setColor(this.getColorElem().value);
		this.setBgColor(this.getBgColorElem().value);
		this.setComment(this.getCommentElem().value);
		
		this.logger.trace('reflectFromWindow end');
	},
	
	/**
	 * @private
	 */
	getNameElem: function() {
		return $(MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_NAME);
	},
	
	/**
	 * @private
	 */
	getColorElem: function() {
		return $(MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_COLOR);
	},
	
	/**
	 * @private
	 */
	getBgColorElem: function() {
		return $(MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_BGCOLOR);
	},
	
	/**
	 * @private
	 */
	getCommentElem: function() {
		return $(MemberCommunity.constants.IdNameConstants.ID_PROFILEEDITOR_COMMENT);
	},
	
	setName: function(name) {
		this.name = name;
	},
	
	setColor: function(color) {
		this.color = color;
	},
	
	setBgColor: function(bgColor) {
		this.bgColor = bgColor;
	},
	
	setComment: function(comment) {
		this.comment = comment;
	},
	
	getName: function() {
		return this.name;
	},
	
	getColor: function() {
		return this.color;
	},
	
	getBgColor: function() {
		return this.bgColor;
	},
	
	getComment: function() {
		return this.comment;	
	}
}))();








if (!MemberCommunity.window.member) {
	MemberCommunity.window.member = {};
}

MemberCommunity.window.member.MenuWindow = new (Class.create(MemberCommunity.window.SingletonWindow, {
	
	logger: Log4js.getLogger('MemberCommunity.window.member.MenuWindow'),
	memberId: -1,
	
	/**
	 * @param {Object} event
	 * 			Event instance.
	 * @param {Object} evtElem
	 * 			Element that event occured.
	 */
	show: function($super, event, evtElem) {
		this.logger.debug('show(event, evtElem) called.');
		
		var elemId = evtElem.getAttribute('id');
		var li = elemId.lastIndexOf('_');
		this.memberId = elemId.substr(li+1, elemId.length - (li+1));
		this.logger.debug('show() selected member`s id = ' + this.memberId);
		
		return $super(event);	// cut bubble
	},
	
	createContent: function() {
		return 	'<a href="#" onclick="MemberCommunity.window.member.CommentWindow.show(event, ' + this.memberId + '); ' +
					'MemberCommunity.window.member.MenuWindow.destroy();">プロフィールを表示</a>' +
				'<br /><br />' +
				'<a href="#" onclick="MemberCommunity.window.member.SendMessageWindow.show(event, ' + this.memberId + '); ' +
					'MemberCommunity.window.member.MenuWindow.destroy();">メッセージを送信</a>';
	},
	
	createTitle: function() {
		return "";
	},
	
	createSize: function(json) {
		json.width = 100;
		json.height = 50;
		return json;
	}
}))();
MemberCommunity.window.member.CommentWindow = new (Class.create(MemberCommunity.window.Window, {
	
	logger: Log4js.getLogger('MemberCommunity.window.member.CommentWindow'),
	selectedMember: undefined,
	
	/**
	 * @param {Event} event
	 * 			Event instance.
	 * @param {int} memberId
	 * 			Id of selected member
	 */
	show: function($super, event, memberId) {
		this.selectedMember = MemberCommunity.model.member.Members.getById(memberId);
		
		return $super(event);
	},
	
	createContent: function() {
		var color = this.selectedMember.getColor();
		var bgColor = this.selectedMember.getBgColor();
		var comment = this.selectedMember.getComment().escapeHTML();
		
		this.logger.debug('name = ' + this.selectedMember.getName() + 
			', color = ' + color + ', bgColor = ' + bgColor + ', comment = ', + comment);
		
		return '<div style="color:' + color + '; background-color:' + bgColor + ';">' + comment + '</div>';
	},
	
	createTitle: function() {
		return this.selectedMember.getName().escapeHTML() + 'さんのプロフィール';
	},
	
	createSize: function(json) {
		json.width = 150;
		json.height = 150;
		return json;
	}
}))();

/**
 * Display and manage a window to send message to a member.
 * 
 * This class can manage only a window at the moment.
 * If new window is created, the old one will be destroyed.
 * 
 * @class
 * @author KenjiNagao
 */
MemberCommunity.window.member.SendMessageWindow = new (Class.create(MemberCommunity.window.SingletonWindow, {
	
	logger: Log4js.getLogger('SendMessageWindow'),
	selectedMember: undefined,
	
	show: function($super, event, memberId) {
		this.selectedMember = MemberCommunity.model.member.Members.getById(memberId);
		if (this.selectedMember === false) {
			alert('すでに彼/彼女は去ってしまいました。');
			return;
		}
		
		this.logger.debug('Open new window for ' + this.selectedMember.getName().escapeHTML() + '.');
		
		$super(event);
	},
	
	createContent: function() {
		return '<form action="javascript:MemberCommunity.window.member.SendMessageWindow.submit();")>' +
				'<div>' + this.selectedMember.getName().escapeHTML() + 'さんへ送信するメッセージを記述して下さい。</div>' +
				'<div><textarea id="memberCommunity_sendMessageWindow_message"></textarea></div>' +
				'<div><input type="submit" value="送信" /></div>' +
			'</form>';
	},
	
	createTitle: function() {
		return this.selectedMember.getName().escapeHTML() + 'さんへのメッセージ';
	},
	
	createSize: function(json) {
		json.width = 300;
		json.height = 200;
		return json;
	},
	
	/**
	 * Submit procedure.
	 * 
	 * Request sendMessage and destroy window.
	 */
	submit: function() {	
		this.logger.trace('MemberCommunity.window.member.SendMessageWindow.submit() called.');
		
		var message = $F('memberCommunity_sendMessageWindow_message');
		
		if (message.length > MemberCommunity.Constants.messageMaxLen) {
			alert('メッセージは' + MemberCommunity.Constants.messageMaxLen + '文字以内でなければなりません。');
			return;
		} else if (message.length == 0) {
			alert('空のメッセージを送ることはできません。');
			return;
		}
		
		MemberCommunity.controller.request.SendMessageRequest.request({
			to: this.selectedMember.getId(),
			body: message
		});

		this.destroy();
		
		this.logger.trace('MemberCommunity.window.member.SendMessageWindow.submit() finished.');
	}
}))();
