// 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.0

(function () {

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

/* -------------------------------------------------------------------
* 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,	/* 実数部(-1.94) */
		b: -1.3,	/* 虚数部(-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;	// 1ピクセルあたりの複素数平面の距離
	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
		}
	}
	// Web Worker
	p.worker = new Worker("Mandelbrot_set_worker.js");
	//
	this.p = p;
};

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

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

proto.draw = function(params) {
	if( ! this.p ) { 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 stime = new Date();
	// Web Worker
	var _this = this;
	p.worker.onmessage = function(e) {
		var data = e.data.split("\t");
		var data_len = e.data.length;
		for( var i=0; i<data_len; i++ ) {
			p.imagedata.data[i] = parseInt(data[i]);
		}
		p.ctx.putImageData(p.imagedata, 0, 0);
		var etime = new Date();
		var t = etime.getTime() - stime.getTime();
		document.getElementById("state").innerHTML = t + " ms";
		// click event listener
		_this.p.listener.click = function(e) {_this._redraw(e);};
		_this.p.listener.mousemove = function(e) {_this._draw_indicator(e);};
		_this.p.listener.mouseout = function(e) {_this._delete_indicator(e);};
		p.canvas.addEventListener("click", _this.p.listener.click, false);
		p.canvas.addEventListener("mousemove", _this.p.listener.mousemove, false);
		p.canvas.addEventListener("mouseout", _this.p.listener.mouseout, false);
		p.canvas.style.cursor = "crosshair";
		//
		document.getElementById("drawbtn").disabled = false;
	};
	var worker_p = {};
	for( var k in p ) {
		if( /^(canvas|ctx|imagedata|worker)$/.test(k) ) { continue; }
		worker_p[k] = p[k];
	}
	var ps = JSON.stringify(worker_p);
	p.worker.postMessage(ps);
};

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

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;
	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;
	}
}

})();

