////////////////////////////////////////////////
// consts
////////////////////////////////////////////////
var DOM_ID_ONLY = true;
var MarginForToolbar = "23px";

////////////////////////////////////////
// utilities
////////////////////////////////////////
var addClass = function(node, class_name) {
	if(node) node.className += (node.className ? " " : "") + class_name
};
var removeClass = function(node, class_name) {
	if(node) node.className = node.className.replace(new RegExp("(\s)?" + class_name, "gi"), '')
};
var addOrRemoveClass = function(condition, node, class_name) {
	if(condition) addClass(node, class_name)
	else removeClass(node, class_name)
};
var ExtractIdFromUrl = function(_name, _url) {
	var url = _url || location.href;
	var name = _name + "s";
	var patt = new RegExp(name + "/([0-9]+)")

	var _match = patt.exec(url);

	if(_match && _match.length >= 2) return _match[1];
	else return false;
};
Array.prototype.is_empty = function() {
  return (!this || this.length <= 0)
};
Array.prototype.last = function() {
  return this[this.length - 1];
};
if(!Array.indexOf) {
  Array.prototype.indexOf = function(_m) {
    for(var i=0; i<this.length; i++) if(this[i] == _m) return i;
    return -1;
  }
};
Array.prototype.eject = function(_v) {
  return this.splice(this.indexOf(_v), 1);
}
String.prototype.is_empty = function() {
  return (!this || this.replace(/\s/g, '').length <= 0);
}
var __A = function(_v) {
  if (!_v) return [];
  if (_v.toArray && typeof(_v) != "string") return _v.toArray();
  var iterable = (typeof _v == "string") ? _v.split(/,/) : _v;
  var length =  iterable.length;
  var results = new Array(length);
  while (length--) results[length] = iterable[length];

  return results;
}
////////////////////////////////////////////////
// minified ajax
////////////////////////////////////////////////
var zAjax = function(authKey) {
  this._xhr = null;
	this.authKey = authKey;
};

zAjax.prototype.send = function(_o) {
  this._url = _o.url;
  this._data = _o.data;
  this._method = _o.method.toUpperCase();
  this.scb = _o.success || function() {};
  this.fcb = _o.failure || function() {};

  if(_o.sync) return this.sync();
  else this.async();
};

zAjax.prototype.sync = function() {
  this.xhr().open(this._method, this._url, false);
  if(this._method == "POST") this.xhr().setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	this.xhr().setRequestHeader('X-Requested-With', 'XMLHttpRequest');
	this.xhr().setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*')
  if(this._data) this.xhr().send(this.toQueryString(this._data));
  else this.xhr().send(null)

  this.callback();

  return this.xhr();
};

zAjax.prototype.async = function() {
  this.xhr().open(this._method, this._url, true);
  if(this._method == "POST") this.xhr().setRequestHeader("Content-type", "application/x-www-form-urlencoded");
	this.xhr().setRequestHeader('X-Requested-With', 'XMLHttpRequest');
	this.xhr().setRequestHeader('Accept', 'text/javascript, text/html, application/xml, text/xml, */*')

  this.xhr().onreadystatechange = Bind(this, this.orsc);

  this.xhr().send(this.toQueryString(this._data));
};

zAjax.prototype.callback = function() {
  if(this.xhr().status == 200) this.scb(this.xhr());
  else this.fcb(this.xhr());
}

zAjax.prototype.toQueryString = function(data) {
  if(typeof data == "string") return this.addAuthenticityToken(data);
  var queries = new Array();
  for(var i in data) queries.push(i + "=" + encodeURIComponent(String(data[i])));

  return this.addAuthenticityToken(queries.join("&"));
};

zAjax.prototype.addAuthenticityToken = function(data) {
	if(typeof AUTH_TOKEN == "undefined") return data;
	var prehead = (data && !data.is_empty()) ? data + "&" : "";

	return prehead + "authenticity_token=" + encodeURIComponent(AUTH_TOKEN);
};


zAjax.prototype.orsc = function() {
  if(this.xhr().readyState == 4) this.callback();
};

zAjax.prototype.xhr = function() {
  if(!this._xhr) this._xhr = this._XMLHttpRequest();

  return this._xhr;
};

zAjax.prototype._XMLHttpRequest = function() {
  var try_these = [
    function () { return new XMLHttpRequest(); },
    function () { return new ActiveXObject('Msxml2.XMLHTTP'); },
    function () { return new ActiveXObject('Microsoft.XMLHTTP'); },
    function () { return new ActiveXObject('Msxml2.XMLHTTP.4.0'); },
    function () { throw "Browser does not support XMLHttpRequest"; }
  ];

  for (var i = 0; i < try_these.length; i++) {
    var func = try_these[i];
    try {
      return func();
    } catch (e) {
    }
  }
};
////////////////////////////////////////////////
// event listener
////////////////////////////////////////////////
var OnEvent = new function() {
	this.__zunkBoxes = { def: [] }
};
OnEvent.clear = function(__box) {
	for(var b in this.__zunkBoxes) {
		if(this.__zunkBoxes[b] && this.__zunkBoxes[b].length > 0) {
			if(__box && b != __box) continue;
			var item = null;
			while(item = this.__zunkBoxes[b].pop()) {
				this.del(item[0], item[1], item[2]);
			}
		}
	}
};
OnEvent.add = function(_e, _a, _fn, _box) {
  var _m = ["addEventListener", "attachEvent"];
	if(_box) {
		if(!this.__zunkBoxes[_box]) this.__zunkBoxes[_box] = [];
		this.__zunkBoxes[_box].push([_e, _a, _fn])
	}
  else this.__zunkBoxes['def'].push([_e, _a, _fn]);

  return this.__bindEvent(_m, _e, _a, _fn);
};
OnEvent.del = function(_e, _a, _fn) {
  var _m = ["removeEventListener", "detachEvent"];

  return this.__bindEvent(_m, _e, _a, _fn);
};
OnEvent.__case_ie = function(_a) {
  var __map = {
    blur: "focusout"
  };

  return __map[_a] || _a;
};
OnEvent.__bindEvent = function(_m, _e, _a, _fn) {
  if (_e[_m[0]]) {
    _e[_m[0]](_a, _fn, false);
  } 
  else if (_e[_m[0]]) {
    _e[_m[0]]('on' + this.__case_ie(_a), _fn);
  } 
  else {
    _e['on' + _a] = _fn;
  }
};

////////////////////////////////////////////////
// Bind
////////////////////////////////////////////////
var BindEvent = function(_this, _fn, _args) {
  var fn = _fn;
  var args = __A(_args);
  return function(e) {
    return fn.apply(_this, args.concat([(e || window.event)]));
  };
}
var Bind = function(_this, _fn, _args) {
  var fn = _fn;
  var args = __A(_args);
  return function() {
    return fn.apply(_this, args.concat(__A(arguments)));
  };
}
////////////////////////////////////////////////
// eventor
////////////////////////////////////////////////
var Eventor = function(_e) {
	this.title = "eventor";
	this.junkies = {};
	this.junkies["_event"] = _e;
};
Eventor.prototype = {
	_event: function() {
		return this.junkies["_event"];
	},

	_node: function() {
		if(!this.junkies["_node"]) {
			this.junkies["_node"] = this._event().target || this._event().srcElement;
			if(this.junkies["_node"].nodeType == 3) this.junkies["_node"] = this.junkies["_node"].parentNode
		}

		return this.junkies["_node"];
	},

	_type: function() {
		if(!this.junkies["_type"]) this.junkies["_type"] = this._event().type

		return this.junkies["_type"];
	},

	_keycode: function() {
		if(!this.junkies["_keycode"]) this.junkies["_keycode"] = this._event().keycode || this._event().keyCode;

		return this.junkies["_keycode"];
	},

	_page: function() {
		if(!this.junkies["_page"]) {

			var sl = window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft;
			var st = window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop;

			this.junkies["_page"] = { 
				x: this._event().pageX || this._event().clientX + sl,
				y: this._event().pageY || this._event().clientY + st
			}
		}

		return this.junkies["_page"];
	},

	_mouse: function() {
		if(!this.junkies["_mouse"]) {
			this.junkies["_mouse"] = {};
			this.junkies["_mouse"]["_x"] = this._event().clientX;
			this.junkies["_mouse"]["_y"] = this._event().clientY;
		}

		return this.junkies["_mouse"];
	},
	
	_wheel: function() {
		if(!this.junkies["_wheel"]) {
			// up < 0
			// down > 0
			this.junkies["_wheel"] = {};


			this.junkies["_wheel"]["_delta"] = this._event().detail / 3 * -1 || this._event().wheelDelta / 120;
			if(Browser.chrome) this.junkies["_wheel"]["_delta"] = this.junkies["_wheel"]["_delta"] / 3;
			this.junkies["_wheel"]["_direction"] = (this.junkies["_wheel"]["_delta"] > 0) ? "up" : "down";
		}

		return this.junkies["_wheel"];
	},

	break_event: function() {
		if (this._event().stopPropagation) {
			this._event().stopPropagation();
		} else {
			this._event().returnValue = false;
		}

		if (this._event().preventDefault) {
			this._event().preventDefault();
		} else {
			this._event().cancelBubble = true;
		}
	}
}

////////////////////////////////////////
// Pinpoints
////////////////////////////////////////
var Pinpoints = function() {
	this.images = [];
	this.activated_images = [];
	this.tryInterval = 1000;
};

Pinpoints.prototype.init = function(user_id) {
	this.user_id = user_id;
	this.collect_images();
	this.activate_all_images();
};

/////////////////////////////////////
// collect
Pinpoints.prototype.collect_images = function() {
	for(var i=0;i<document.images.length;i++) {
		// ToDo: condition for select images.
		if(!/\/assets\//i.test(document.images[i].src)) continue;
		// ToDo: condition to skip if already activated
		if(/^zPin_[0-9]+$/i.test(document.images[i].id)) continue;
		this.images.push(document.images[i]);
	}
};

/////////////////////////////////////
// activate all 
Pinpoints.prototype.activate_all_images = function() {
	this.worker = this.timer().setInterval(Bind(this, this.wait_complete), this.tryInterval);
};
Pinpoints.prototype.wait_complete = function() {
	if(this.images.length == 0) this.timer().clearInterval(this.worker);
	try {
		var images = this.images.clone();
		for(var i=0; i<images.length; i++) {
			var image = images[i];
			if(image.complete) {
				this.images.eject(image);
				this.activated_images.push(new Pinable(image, this.user_id));
			}
		}
	} catch(e) {
		this.timer().clearInterval(this.worker);
	}
}
Pinpoints.prototype.timer = function() {
	if(!this.factory_timer) this.factory_timer = window;
	return this.factory_timer;
};
var Pinpoint = new Pinpoints();

////////////////////////////////////////
// Pinable
////////////////////////////////////////
var Pinable = function(image, user_id) {
	this.id = new Date().getTime();
	this.user_id = user_id;
	this.opacity = "6";
	this.image = image;
	this.bless();
	this.image_position = this.inspect_position(this.image);
	this.render_toolbar();
	this.collect_pins();
};

/////////////////////////////////////
// bless
Pinable.prototype.bless = function() {
	if(this.image.id) this.image.setAttribute("longdesc", this.image.id);
	this.image.id = "zPin_" + this.id;
	this.image.style.marginTop = MarginForToolbar;
};

/////////////////////////////////////
// render toolbar 
Pinable.prototype.render_toolbar = function() {
	this.toolbar = document.createElement("ul");
	this.toolbar.className = "zPin_toolbar";

	this.render_buttons();

	this.image.parentNode.insertBefore(this.toolbar, this.image);
};
/////////////////////////////////////
// render buttons 
Pinable.prototype.render_buttons = function() {
	var create_button = document.createElement("li");
	create_button.className = "zPin_create_button";
	create_button.innerHTML = "<a href='' onclick='return false'><span>+Image Comment</span></a>";
	this.toolbar.appendChild(create_button);

	OnEvent.add(create_button, "click", Bind(this, function() {
		this.hide_all_pinpoints();
		this.render_frame();
	}));
	
	var toggle_show_button = document.createElement("li");
	toggle_show_button.className = "zPin_toggle_show_button";
	toggle_show_button.innerHTML = "<a href='' onclick='return false'><span>Show/Hide</span></a>";
	this.toolbar.appendChild(toggle_show_button);
	
	OnEvent.add(toggle_show_button, "click", Bind(this, function() {
		this.toggle_all_pinpoints();
	}));

};
/////////////////////////////////////
// remove frame 
Pinable.prototype.remove_frame = function() {
	this.canvas.parentNode.removeChild(this.canvas);
};
Pinable.prototype.render_edit_frame = function(pinpoint) {
	var pin_id = /[0-9]+?$/.exec(pinpoint.id).last();
	this.render_frame({ hide: true });

	var pinpoint_position = this.inspect_position(pinpoint, true);
	this.hide_all_pinpoints();

	this.form_wrapper.style.marginLeft = pinpoint_position.lt_x + "px";
	this.form_wrapper.style.marginTop = pinpoint_position.rb_y + "px";

	this.title_field.value = this.pindesc_dom(pin_id).innerHTML;

	this.frame.style.left = pinpoint_position.lt_x + "px";
	this.frame.style.top = pinpoint_position.lt_y + "px";
	this.frame.style.width = pinpoint_position.width + "px";
	this.frame.style.height = pinpoint_position.height + "px";

	this.resize_inner_frame();

	this.save_button.rel = pin_id;
	this.save_button.onclick = BindEvent(this, function(_ev) {
		var pin_id = new Eventor(_ev)._node().rel;
		this.update_pin(pin_id);
	});

	this.delete_button = document.createElement("input");
	this.delete_button.className = "zPin_delete";
	this.delete_button.type = "button";
	this.delete_button.value = "delete";
	this.delete_button.rel = pinpoint.id;
	this.delete_button.onclick = BindEvent(this, function(_ev) {
		var ev = new Eventor(_ev);
		this.destroy_pin(/[0-9]+?$/.exec(ev._node().rel).last());
	});
	this.form_wrapper.appendChild(this.delete_button);

	this.arrange_frame_handles();

	this.canvas.style.visibility = "visible";
};
Pinable.prototype.clear_canvas = function() {
	var canvas = document.getElementById("zPin_canvas");
	if(canvas) canvas.parentNode.removeChild(canvas);
};
/////////////////////////////////////
// render frame 
Pinable.prototype.render_frame = function(options) {
	if(!options) var options = {};
	this.clear_canvas();

	this.canvas = document.createElement("div");
	this.canvas.className = "zPin_canvas";
	this.canvas.id = "zPin_canvas";
	this.canvas.style.width = this.image.clientWidth + "px";
	this.canvas.style.height = this.image.clientHeight + "px";
	this.canvas.style.position = "absolute";
	this.canvas.style.left = this.image_position.lt_x + "px";
	this.canvas.style.top = this.image_position.lt_y + "px";

	this.form_wrapper = document.createElement("div");
	this.form_wrapper.className = "zPin_form_wrapper";

	this.title_field = document.createElement("textarea");
	this.title_field.className = "zPin_title";
	this.form_wrapper.appendChild(this.title_field);

	this.save_button = document.createElement("input");
	this.save_button.className = "zPin_save";
	this.save_button.type = "button";
	this.save_button.value = "save";
	this.save_button.onclick = Bind(this, this.create_pin);
	this.form_wrapper.appendChild(this.save_button);

	this.cancel_button = document.createElement("input");
	this.cancel_button.className = "zPin_cancel";
	this.cancel_button.type = "button";
	this.cancel_button.value = "cancel";
	this.cancel_button.onclick = Bind(this, function() {
		this.remove_frame();
		this.show_all_pinpoints();
	});
	this.form_wrapper.appendChild(this.cancel_button);
	this.canvas.appendChild(this.form_wrapper);

	this.frame = document.createElement("div");
	this.frame.className = "zPin_frame";
	this.frame.style.left = 0;
	this.frame.style.top = 0;
	this.frame.style.width = "100px";
	this.frame.style.height = "100px";
	this.canvas.appendChild(this.frame);
	document.body.appendChild(this.canvas);

	this.inner_frame = document.createElement("div");
	this.inner_frame.className = "zPin_inner_frame";
	this.frame.appendChild(this.inner_frame);

	this.inner_handler = document.createElement("div");
	this.inner_handler.className = "zPin_inner_handler";
	this.inner_frame.appendChild(this.inner_handler);

	this.resize_inner_frame();


	// lt handler
	this.lt = document.createElement("div");
	this.lt.className = "frame_handler lt";
	this.frame.appendChild(this.lt);

	// rt handler
	this.rt = document.createElement("div");
	this.rt.className = "frame_handler rt";
	this.frame.appendChild(this.rt);

	// lb handler
	this.lb = document.createElement("div");
	this.lb.className = "frame_handler lb";
	this.frame.appendChild(this.lb);

	// rb handler
	this.rb = document.createElement("div");
	this.rb.className = "frame_handler rb";
	this.frame.appendChild(this.rb);

	this.arrange_frame_handles();

	if(!options['hide']) this.canvas.style.visibility = "visible";

	this.hijack_event_to_dragging(this.canvas);
};

/////////////////////////////////////
// hijack event to drag
Pinable.prototype.hijack_event_to_dragging = function(node) {
	OnEvent.clear("canvas_dragging");

	// to prevent IE dragging
	OnEvent.add(node, "dragstart", BindEvent(this, this.dragstart), "canvas_dragging");
	// drag start
	OnEvent.add(node, "mousedown", BindEvent(this, this.mousedown), "canvas_dragging");
	// drag drawing
	OnEvent.add(node, "mousemove", BindEvent(this, this.mousemove), "canvas_dragging");
	// drag end
	OnEvent.add(node, "mouseup", BindEvent(this, this.mouseup), "canvas_dragging");
};
Pinable.prototype.dragstart = function(_ev) {
	var e = new Eventor(_ev);
	e.break_event();
};
var Log = function(s) { console.debug(s) }
Pinable.prototype.mousedown = function(_ev) {
	var e = new Eventor(_ev);
	this.start_pos = e._page();
	var node = e._node();
	if(/zPin_canvas/i.test(node.className)) return;
	// if(/zPin_inner_frame/.test(node.className)) node = this.frame;
	if(/zPin_inner_handler/.test(node.className)) node = this.frame;

	this.drag_activated_node = node;
	this.gap_x = this.start_pos.x - parseInt(node.style.left);
	this.gap_y = parseInt(node.style.top) - this.start_pos.y;

	// to prevent FF dragging
	if(!/(input|textarea)/i.test(node.tagName)) e.break_event();
};
Pinable.prototype.mousemove = function(_ev) {
	if(!this.drag_activated_node) return;

	var e = new Eventor(_ev);
	var node = this.drag_activated_node;
	var cur_pos = e._page();

	this.form_wrapper.style.marginLeft = parseInt(this.frame.style.left) + "px";
	this.form_wrapper.style.marginTop = parseInt(this.frame.style.top) + parseInt(this.frame.clientHeight) + "px";

	if(!/frame_handler/i.test(node.className)) {
		if((cur_pos.x - this.gap_x >= 0) && (cur_pos.x - this.gap_x) + parseInt(node.clientWidth) < this.image.clientWidth) node.style.left = cur_pos.x - this.gap_x + "px";
		if((cur_pos.y + this.gap_y >= 0) && (cur_pos.y + this.gap_y) + parseInt(node.clientHeight) < this.image.clientHeight) node.style.top = cur_pos.y + this.gap_y + "px";
	}
	else if(/frame_handler/i.test(node.className)) {
		if(cur_pos.x <= this.image_position.lt_x) cur_pos.x = this.image_position.lt_x + 3;
		if(cur_pos.x > this.image_position.rb_x) cur_pos.x = this.image_position.rb_x - 3;
		if(cur_pos.y <= this.image_position.lt_y) cur_pos.y = this.image_position.lt_y + 3;
		if(cur_pos.y > this.image_position.rb_y) cur_pos.y = this.image_position.rb_y - 3;

		var _x = cur_pos.x - this.start_pos.x;
		var _y = cur_pos.y - this.start_pos.y;

		if(/rb/i.test(node.className)) {
			this.frame.style.width = parseInt(this.frame.style.width) + _x + "px"; // resize x

			this.frame.style.height = parseInt(this.frame.style.height) + _y + "px"; // resize y
		}
		else if(/rt/i.test(node.className)) {
			this.frame.style.width = parseInt(this.frame.style.width) + _x + "px"; // resize x

			this.frame.style.top = parseInt(this.frame.style.top) + _y + "px"; // move y
			this.frame.style.height = parseInt(this.frame.style.height) - _y + "px"; // resize y
		}
		else if(/lb/i.test(node.className)) {
			this.frame.style.left = parseInt(this.frame.style.left) + _x + "px"; // move x
			this.frame.style.width = parseInt(this.frame.style.width) - _x + "px"; // resize x

			this.frame.style.height = parseInt(this.frame.style.height) + _y + "px"; // resize y
		}
		else if(/lt/i.test(node.className)) {
			this.frame.style.left = parseInt(this.frame.style.left) + _x + "px"; // move x
			this.frame.style.width = parseInt(this.frame.style.width) - _x + "px"; // resize x

			this.frame.style.top = parseInt(this.frame.style.top) + _y + "px"; // move y
			this.frame.style.height = parseInt(this.frame.style.height) - _y + "px"; // resize y
		}

		this.resize_inner_frame();
		this.arrange_frame_handles();
		this.start_pos = cur_pos;
	}
};
Pinable.prototype.mouseup = function(_ev) {
	var e = new Eventor(_ev);
	this.drag_activated_node = null;
};
Pinable.prototype.resize_inner_frame = function() {
	this.inner_frame.style.width = parseInt(this.frame.style.width) - 2 + "px";
	this.inner_frame.style.height = parseInt(this.frame.style.height) - 2 + "px";
	this.inner_handler.style.width = parseInt(this.frame.style.width) - 4 + "px";
	this.inner_handler.style.height = parseInt(this.frame.style.height) - 4 + "px";
};
Pinable.prototype.arrange_frame_handles = function() {
	this.lt.style.left = "-1px";
	this.lt.style.top = "-1px";
	this.rt.style.left = parseInt(this.frame.style.width) - 7 + "px";
	this.rt.style.top = "-1px";
	this.lb.style.left = "-1px";
	this.lb.style.top = parseInt(this.frame.style.height) - 7 + "px";
	this.rb.style.left = parseInt(this.frame.style.width) - 7 + "px";
	this.rb.style.top = parseInt(this.frame.style.height) - 7 + "px";
};

////////////////////////////////////////////
// render single pin
Pinable.prototype.render_pin = function(pin) {
	this.remove_pin(pin.id);

	var pinpoint = document.createElement("div");
	pinpoint.id = this.pinpoint_dom(pin.id, DOM_ID_ONLY);
	pinpoint.className = "zPin_pinpoint";
	pinpoint.setAttribute("longdesc", pin.user_id);

	var lt_x = this.image_position.lt_x + (this.image.width * pin.lt_x / 100);
	var lt_y = this.image_position.lt_y + (this.image.height * pin.lt_y / 100);
	var rb_x = this.image_position.lt_x + (this.image.width * pin.rb_x / 100);
	var rb_y = this.image_position.lt_y + (this.image.height * pin.rb_y / 100);

	pinpoint.style.left = lt_x + "px";
	pinpoint.style.top = lt_y + "px";

  var _width = rb_x - lt_x - 4;
  var _height = rb_y - lt_y - 4;

	pinpoint.style.width = _width + 2 + "px"
	pinpoint.style.height = _height + 2 + "px"



  //
	var border_layer = document.createElement("div");
	border_layer.className = "zPin_border_layer";
	border_layer.style.width = _width + "px";
	border_layer.style.height = _height + "px";
	pinpoint.appendChild(border_layer);


  //
	var inner_layer = document.createElement("div");
	inner_layer.className = "zPin_inner_layer";
	inner_layer.style.width = _width - 2 + "px";
	inner_layer.style.height = _height - 2 + "px";
	border_layer.appendChild(inner_layer);

	var inner_bg = document.createElement("div");
	inner_bg.className = "zPin_inner_bg";
	inner_bg.style.width = _width - 2 + "px";
	inner_bg.style.height = _height - 2 + "px";
	inner_layer.appendChild(inner_bg);

	var pindesc = document.createElement("div");
	pindesc.id = this.pindesc_dom(pin.id, DOM_ID_ONLY);
	pindesc.className = "zPin_pindesc";
	pindesc.style.top = rb_y + "px";
	pindesc.style.left = lt_x + "px";
	pindesc.style.width = _width - 8 + "px";
//	pindesc.style.opacity = this.opacity / 10;
//	pindesc.style.filter = "alpha(opacity='" + this.opacity * 10 + "')";
	pindesc.innerHTML = pin.desc;

	this.pin_container().appendChild(pinpoint);
	this.pin_container().appendChild(pindesc);

	// inner_layer.onclick = BindEvent(this, function(_ev) {
	inner_bg.onclick = BindEvent(this, function(_ev) {
		var e = new Eventor(_ev);
		// var pinpoint = e._node().parentNode.parentNode;
		var pinpoint = e._node().parentNode.parentNode.parentNode;
		var owner_id = Number(pinpoint.getAttribute("longdesc"));
		if(this.user_id != owner_id) return;

		var id = /[0-9]+?$/.exec(pinpoint.id).last();
		this.render_edit_frame(pinpoint);
	});

	// inner_layer.onmouseover = BindEvent(this, function(_ev) {
	inner_bg.onmouseover = BindEvent(this, function(_ev) {
		var e = new Eventor(_ev);
    
		// var pinpoint = e._node().parentNode.parentNode;
		var pinpoint = e._node().parentNode.parentNode.parentNode;
		if(!/pinpoint_/.test(pinpoint.id)) {
			e.break_event();
			return false;
		}
		
		$(pinpoint).toggleClassName("current");

		var id = /[0-9]+?$/.exec(pinpoint.id).last();
		this.pindesc_dom(id).style.display = "block";
	});

	// inner_layer.onmouseout = BindEvent(this, function(_ev) {
	inner_bg.onmouseout = BindEvent(this, function(_ev) {
		var e = new Eventor(_ev);

		// var pinpoint = e._node().parentNode.parentNode;
		var pinpoint = e._node().parentNode.parentNode.parentNode;
    $(pinpoint).toggleClassName("current");

		var id = /[0-9]+?$/.exec(pinpoint.id);
		this.pindesc_dom(id).style.display = "none";
	});

};

////////////////////////////////////////////
// render all pins
Pinable.prototype.render_pins = function(pins) {
	var obsolete_pin_ids = this.collect_current_pin_ids();

	for(var i=0; i<pins.length; i++) {
		var pin = pins[i].pinpoint;
		obsolete_pin_ids.eject(pin.id);
		this.render_pin(pin);

		if(this.pinpoint_dom(pin.id)) continue;
	}

	// remove if deleted pinpoints still exists
	var obsolete_id = null;
	while(obsolete_id = obsolete_pin_ids.pop()) {
		this.remove_pin(obsolete_id);
	}
};


////////////////////////////////////////////
// Ajax Wrapper
// 
Pinable.prototype.ajax = function(options) {
	try {

		// zAjax
		_res = new zAjax().send({
			url: options["url"],
			sync: options["sync"] || false,
			method: options["method"] || "POST",
			data: options["data"],
			success: options.succ,
			failure: options.fail
		})

		// prototype
		// async = (!options["sync"]) ? false : true
		// new Ajax.Request(options["url"], {
			// asynchronous: async,
			// method: options["method"] || "POST",
			// evalScripts: true,
			// parameters: options["data"],
			// onSuccess: options.succ,
			// onFailure: options.fail
		// });

	} catch(e) {
		// error
	}
};

////////////////////////////////////////////
// Ajax actions
// collect
Pinable.prototype.collect_pins = function() {
	this.ajax({
		method: "GET",
		url: this.urls.collect + "?asset_id=" + ExtractIdFromUrl("asset", this.image.src),
		succ: Bind(this, function(res) {
			var pins = eval( "(" + res.responseText + ")" );
			this.render_pins(pins);
		}),
		fail: Bind(this, function() {
			// fail
		})
	});
};

////////////////////////////////////////////
// Ajax actions
// create
Pinable.prototype.create_pin = function() {

	var frame_position = this.inspect_position(this.frame);

	this.ajax({
		method: "POST",
		sync: true,
		url: this.urls.create, 
		data: {
			"pinpoint[asset_id]": ExtractIdFromUrl("asset", this.image.src),
			"pinpoint[desc]": this.title_field.value,
			"pinpoint[lt_x]": (frame_position.lt_x / this.image.width) * 100,
			"pinpoint[lt_y]":  (frame_position.lt_y / this.image.height) * 100,
			"pinpoint[rb_x]": (frame_position.rb_x / this.image.width) * 100,
			"pinpoint[rb_y]": (frame_position.rb_y / this.image.height) * 100
		},
		succ: Bind(this, function(res) {
			if(!res.responseText || res.responseText.is_empty()) {
				this.collect_pins();
				this.clear_canvas();
				this.show_all_pinpoints();
			} else  {
				eval(res.responseText);
			}
		}),
		fail: Bind(this, function(res) {
			// ToDo: fail
		})
	});
};

////////////////////////////////////////////
// Ajax actions
// update
Pinable.prototype.update_pin = function(id) {
	var frame_position = this.inspect_position(this.frame);

	this.ajax({
		method: "POST",
		url: this.urls.update + "/" + id,
		data: {
			"_method": "PUT",
			"pinpoint[asset_id]": ExtractIdFromUrl("asset", this.image.src),
			"pinpoint[desc]": this.title_field.value,
			"pinpoint[lt_x]": (frame_position.lt_x / this.image.width) * 100,
			"pinpoint[lt_y]":  (frame_position.lt_y / this.image.height) * 100,
			"pinpoint[rb_x]": (frame_position.rb_x / this.image.width) * 100,
			"pinpoint[rb_y]": (frame_position.rb_y / this.image.height) * 100
		},
		succ: Bind(this, function(res) {
			var pin = eval("(" + res.responseText + ")");
			this.remove_pin(pin.pinpoint.id);
			this.render_pin(pin.pinpoint);
			this.clear_canvas();
			this.show_all_pinpoints();
		}),
		fail: Bind(this, function(res) {
			// ToDo: fail
		})
	});
};

////////////////////////////////////////////
// Ajax actions
// destroy
Pinable.prototype.destroy_pin = function(id) {
	this.ajax({
		method: "POST",
		url: this.urls.destroy + "/" + id,
		data: {
			"_method": "DELETE"
		},
		succ: Bind(this, function(res) {
			var pin = eval("(" + res.responseText + ")");
			this.remove_pin(pin.pinpoint.id);
			this.clear_canvas();
			this.show_all_pinpoints();
		}),
		fail: Bind(this, function(res) {
			// ToDo: fail
		})
	});
};


////////////////////////////////////////////
// Singleton Container
// pin / toolbar
Pinable.prototype.pin_container = function() {
	if(!this.__pin_container) {
		var pin_container = document.createElement("div");
		pin_container.id = "zPin_container_" + this.id;
		pin_container.className = "zPin_container";
		document.body.appendChild(pin_container);
		this.__pin_container = pin_container;
	}

	return this.__pin_container;
};

////////////////////////////////////////////
// Pinalble utiltities
Pinable.prototype.hide_all_pinpoints = function() {
	this.pin_container().style.display = "none";
};

Pinable.prototype.show_all_pinpoints = function() {
	this.pin_container().style.display = "block";
};

Pinable.prototype.toggle_all_pinpoints = function() {
	if(this.pin_container().style.display == "block") this.hide_all_pinpoints();
	else this.show_all_pinpoints();
};
Pinable.prototype.inspect_position = function(node, relative) {
	var width = node.clientWidth;
	var height = node.clientHeight;
	var lt = this.positionedOffset(node);
	// var lt = this.cumulativeOffset(node);

	var _position = { 
		lt_x: lt[0],
		lt_y: lt[1],
		rb_x: lt[0] + width,
		rb_y: lt[1] + height,
		width: width,
		height: height
	};

	if(relative) {
		_position.lt_x = _position.lt_x - this.image_position.lt_x;
		_position.lt_y = _position.lt_y - this.image_position.lt_y;
		_position.rb_x = _position.lt_x + width;
		_position.rb_y = _position.lt_y + height;
	}

	// ToDo: IE6 monkey patch
	if(Prototype.Browser.IE && _position.lt_x < 0) {
		_position.lt_x = _position.lt_x + 900;
	}

	return _position;
};

Pinable.prototype.collect_current_pin_ids = function() {
	var pins = this.pin_container().childNodes;

	var ids = new Array();
	for(var i=0; i<pins.length; i++) {
		if(!/pinpoint/.test(pins[i].className)) continue;
		var id = /[0-9]+?$/.exec(pins[i].id).last();
		if(id) ids.push(Number(id[0]));
	}

	return ids;
};

Pinable.prototype.cumulativeOffset = function(element) {
	var lt_y = 0;
	var lt_x = 0;
	do {
		lt_y += element.offsetTop  || 0;
		lt_x += element.offsetLeft || 0;
		element = element.offsetParent;
	} while (element);

	return [lt_x, lt_y];
};

Pinable.prototype.positionedOffset = function(element) {
	var lt_y = 0;
	var lt_x = 0;
	do {
		lt_y += element.offsetTop  || 0;
		lt_x += element.offsetLeft || 0;
		element = element.offsetParent;
		if(element) {
			if(/^body$/i.test(element.tagName)) break;
			if(/(relative|absolute)/i.test(String(element.style.position))) break;
		}
	} while (element);

	return [lt_x, lt_y];
};

Pinable.prototype.remove_pin = function(pin_id) {
	var pinpoint = this.pinpoint_dom(pin_id);
	var pindesc = this.pindesc_dom(pin_id);
	if(pinpoint) pinpoint.parentNode.removeChild(pinpoint);
	if(pindesc) pindesc.parentNode.removeChild(pindesc);
};

Pinable.prototype.pinpoint_dom = function(pin_id, dom_id_only) {
	var dom_id = "img_" + this.id + "_pinpoint_" + pin_id; 
	var ret = (dom_id_only) ? dom_id : document.getElementById(dom_id)
	return ret
};

Pinable.prototype.pindesc_dom = function(pin_id, dom_id_only) {
	var dom_id = "img_" + this.id + "_pindesc_" + pin_id; 
	var ret = (dom_id_only) ? dom_id : document.getElementById(dom_id)
	return ret
};

Pinable.prototype.removeIfExist = function(element_id) {
	var _exist = document.getElementById(element_id);
	if(_exist) {
		_exist.parentNode.removeChild(_exist);
		return true;
	}
	else return false;
};

Pinable.prototype.urls = {
	create: "/pinpoints",
	update: "/pinpoints",
	collect: "/pinpoints",
	destroy: "/pinpoints"
};
