// Copyright 2009 futomi  http://www.html5.jp/
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Mandelbrot_set.js v1.0.1
// 2009-10-04

(function () {

if( document.getElementById("drawbtn").addEventListener ) {
	document.getElementById("drawbtn").addEventListener("click", function() {
		// draw the Mandelbrot set.
		var canvas = document.getElementById("sample");
		var man = new Mandelbrot(canvas);
		man.draw();
	}, false);
} else {
	document.getElementById("i").disabled = true;
	document.getElementById("z").disabled = true;
	document.getElementById("drawbtn").disabled = true;
}

/* -------------------------------------------------------------------
* constructor
* ----------------------------------------------------------------- */
Mandelbrot = function(canvas) {
	if( ! canvas ) {
		return null;
	}
	var ctx = canvas.getContext('2d');
	if( ! ctx ) {
		return null;
	}
	// style of canvas
	canvas.style.cursor = "crosshair";
	// parameters
	var p = {
		max_iteration: 256,
		a: -1.94,
		b: -1.3,
		zoom: 1,
		zoom_unit: 2
	};
	// variables
	p.canvas = canvas;
	p.ctx = ctx;
	p.w = parseInt(canvas.width);
	p.h = parseInt(canvas.height);
	p.imagedata = ctx.getImageData(0, 0, p.w, p.h);
	p.pixel = 2.6 / p.w / p.zoom;
	p.listener = {};
	// color pallet
	p.colors = [];
	for( var n=0; n<=256; n++ ) {
		p.colors[n] = {
			r: ((n-64)%64)*4,
			g: ((n-32)%32)*8,
			b: (n%16)*16
		}
	}
	//
	this.p = p;
};

/* -------------------------------------------------------------------
* prototype of Mandelbrot
* ----------------------------------------------------------------- */
var proto = Mandelbrot.prototype;

/* -------------------------------------------------------------------
* public methods
* ----------------------------------------------------------------- */

proto.draw = function(params) {
	if( ! this.p ) { return; }
	if( ! this.p.ctx ) { return; }
	var p = this.p;
	// canvas size
	var canvas_size = 500;
	if( document.getElementById("w") ) {
		canvas_size = document.getElementById("w").value;
		canvas_size = parseInt(canvas_size);
		if(canvas_size < 100 || canvas_size > 1024) { canvas_size = 500; }
		document.getElementById("w").value = canvas_size;
		p.canvas.width = params.w;
		p.canvas.height = params.w;
		p.w = params.w;
		p.h = params.w;
	}
	// max iteration
	var max_iteration = 256;
	if( document.getElementById("i") ) {
		max_iteration = document.getElementById("i").value;
		max_iteration = parseInt(max_iteration);
		if(max_iteration < 1 || max_iteration > 5000) { max_iteration = 256; }
		document.getElementById("i").value = max_iteration;
		p.max_iteration = max_iteration
	}
	// zoom unit
	var zoom_unit = 2;
	if( document.getElementById("z") ) {
		zoom_unit = document.getElementById("z").value;
		zoom_unit = parseInt(zoom_unit);
		if(zoom_unit < 2 || zoom_unit > 10) { zoom_unit = 2; }
		document.getElementById("z").value = zoom_unit;
		p.zoom_unit = zoom_unit
	}
	//
	this._pre_process();
	//
	var _this = this;
	window.setTimeout( function(){_this._process();}, 50);
};

/* -------------------------------------------------------------------
* private methods
* ----------------------------------------------------------------- */

proto._process = function() {
	var p = this.p;
	var stime = new Date();
	for( var x=0; x<p.w; x++ ) {
		for( var y=0; y<p.h; y++ ) {
			this._add_pixel(x, y);
		}
	}
	p.ctx.putImageData(p.imagedata, 0, 0);
	var etime = new Date();
	var t = etime.getTime() - stime.getTime();
	document.getElementById("state").innerHTML = t + " ms";
	// event listener
	var _this = this;
	window.setTimeout( function(){_this._post_process();}, 50);
};

proto._post_process = function() {
	var p = this.p;
	document.getElementById("drawbtn").disabled = false;
	var _this = this;
	p.listener.click = function(e) {_this._redraw(e);};
	p.listener.mousemove = function(e) {_this._draw_indicator(e);};
	p.listener.mouseout = function(e) {_this._delete_indicator(e);};
	p.canvas.addEventListener("click", p.listener.click, false);
	p.canvas.addEventListener("mousemove", p.listener.mousemove, false);
	p.canvas.addEventListener("mouseout", p.listener.mouseout, false);
	p.canvas.style.cursor = "crosshair";
};

proto._pre_process = function() {
	var p = this.p;
	//p.canvas.style.cursor = "wait";
	if(p.listener.click) {
		p.canvas.removeEventListener("click", p.listener.click, false);
	}
	if(p.listener.mousemove) {
		p.canvas.removeEventListener("mousemove", p.listener.mousemove, false);
	}
	if(p.listener.mouseout) {
		p.canvas.removeEventListener("mouseout", p.listener.mouseout, false);
	}
	var btn = document.getElementById("drawbtn");
	btn.firstChild.nodeValue = "Reset";
	btn.disabled = true;
	document.getElementById("state").innerHTML = "wait a minute...";
};

proto._delete_indicator = function(e) {
	var p = this.p;
	// draw the original picuture.
	p.ctx.putImageData(p.imagedata, 0, 0);
};

proto._draw_indicator = function(e) {
	var pos = this._get_mouse_pos(e);
	if(pos == null) { return; }
	var p = this.p;
	// draw the original picuture.
	p.ctx.putImageData(p.imagedata, 0, 0);
	// draw the indicator.
	var x = pos.x - p.w / p.zoom_unit / 2;
	var y = pos.y - p.h / p.zoom_unit / 2;
	p.ctx.strokeStyle = "#ffffff";
	p.ctx.strokeRect(x, y, p.w/p.zoom_unit, p.h/p.zoom_unit);
};

proto._redraw = function(e) {
	var p = this.p;
	if( p.prosessing == true ) { return; }
	var pos = this._get_mouse_pos(e);
	if(pos == null) { return; }
	p.zoom = p.zoom * p.zoom_unit;
	p.a = p.a + ( pos.x - ( p.w / p.zoom_unit / 2 ) ) * p.pixel;
	p.b = p.b + ( pos.y - ( p.h / p.zoom_unit / 2 ) ) * p.pixel;
	p.pixel = 2.6 / p.w / p.zoom;
	//
	this.draw();
}

proto._get_mouse_pos = function(e) {
	if(e.offsetX && e.offsetY) {
		return { x: e.offsetX, y: e.offsetY };
	} else if(e.pageX && e.pageY) {
		var elm = e.target;
		if( ! elm ) { return null; }
		var x = elm.offsetLeft;
		var y = elm.offsetTop;
		while(elm.offsetParent) {
			elm = elm.offsetParent;
			x += elm.offsetLeft;
			y += elm.offsetTop;
		}
		return { x: e.pageX - x, y: e.pageY - y };
	} else {
		return null;
	}
}

proto._add_pixel = function(x, y) {
	var p = this.p;
	// calculate the complex value (a, b) from the point(x, y) in the coordinate space of the canvas.
	var a = p.a + x * p.pixel;
	var b = p.b + y * p.pixel;
	// calculate whether this complex value is bounded.
	var bp = this._is_bounded(a, b);
	var iteration = bp[0];
	var a = bp[1];
	var b = bp[2];
	// color
	var color = this._get_color(iteration, a, b);
	// add the pixel data into the imagedata object.
	var idx = (p.w * y + x) * 4;
	var ary = [color.r, color.g, color.b, 255];
	for( var i=0; i<4; i++ ) {
		p.imagedata.data[idx+i] = ary[i];
	}
};

proto._is_bounded = function(a0, b0) {
	var p = this.p;
	var iteration = 0;
	var a = 0;
	var b = 0;
	while ( a*a + b*b <= 4 && iteration < p.max_iteration ) {
		var atemp = a*a - b*b + a0;
		b = 2*a*b + b0;
		a = atemp;
		iteration ++;
	}
	if( iteration == p.max_iteration ) {
		return [0, a, b];
	} else {
		return [iteration, a, b];
	}
};

proto._get_color = function(n) {
	return this.p.colors[ n % 256 ];
}

})();

