// Copyright web.AnimeStudio.JP 2011
// For trial use only
(function () {
	var	DATA_ATTRIBUTE = 'data-zenmai';
	var	REEL_ATTRIBUTE = 'zenmai';
	var	canDstIn = false;
	var	parser;
	var	showError = null, HTTPREQUEST_ERROR = "", BADCHAR1_MESSAGE = "", BADCHAR2_MESSAGE = "", BADCHAR3_MESSAGE = "", BADNUMBER_MESSAGE = "", BADSTRING_MESSAGE = "", BADWORD1_MESSAGE = "", BADWORD2_MESSAGE = "", BADARRAY_MESSAGE = "", BADOBJECT_MESSAGE = "", SYNTAXERROR_MESSAGE = "";

	if ( test() ) return;
	window.addEventListener( 'load', open, false );

	function test () {
		var	src, dst, ctx, img;
		
		src = document.createElement('canvas');
		if ( ! src || ! src.getContext ) return true;
		src.width = 2;
		src.height = 1;
		ctx = src.getContext('2d');
		if ( ! ctx || ! ctx.getImageData ) return true;
		ctx.fillRect( 0, 0, 2, 1 );
		
		dst = document.createElement('canvas');
		dst.width = 4;
		dst.height = 1;
		ctx = dst.getContext('2d');
		ctx.clearRect( 0, 0, 4, 1 );
		
		ctx.setTransform( 1, 0, 0, 1, 1, 0 );
		ctx.drawImage( src, 0, 0, 2, 1 );
		
		ctx.globalCompositeOperation = 'destination-out';
		ctx.setTransform( 1, 0, 0, 1, 2, 0 );
		ctx.drawImage( src, 0, 0, 2, 1 );
		
		img = ctx.getImageData( 0, 0, 4, 1 );
		if ( img.data[7] !== 255 || img.data[11] !== 0 ) return true;
		
		ctx.globalCompositeOperation = 'destination-in';
		ctx.drawImage( src, 0, 0, 2, 1 );
		
		img = ctx.getImageData( 0, 0, 4, 1 );
		canDstIn = ( img.data[7] === 0 );

		return false;
	}

	function open (eve) {
		setTimeout( function () {initialize();}, 100 );
		
		function initialize () {
			loader();
			
			function loader () {
				var	elms, i, ii, elm, url;
				elms = document.querySelectorAll( '*[' + DATA_ATTRIBUTE + ']' );
				for ( i=0,ii=elms.length; i<ii; i++ ) {
					elm = elms[i];
					url = elm.getAttribute(DATA_ATTRIBUTE);
					if ( url ) {
						load( elm, url, null );
					}
				}
				
				function load ( elm, url, arg ) {
					try {
						var	request = new XMLHttpRequest();
						request.onreadystatechange = function () {
							if (request.readyState === 4 && (request.status === 200 || request.status === 0)) {//0 for local test
								loaded( elm, request.responseText, arg );
							}
						};
						request.open( 'GET', url );
						request.send( null );
					} catch ( err ) {
						if ( showError ) showError( HTTPREQUEST_ERROR );
					}
				}
			}
		}
	}
	
	function loaded ( elm, txt, arg ) {
		if ( ! txt ) return;
		var	dat = parse( txt );
		if ( dat ) {
			decode( elm, dat, arg );
		}
		
		function parse ( txt ) {
			try {
				return parser( txt );
			} catch ( err ) {
				if ( showError ) showError( err.message + '\n' + err.text.substring( (err.at -1), (err.at + 50) ) );
			}
			return null;
		}

		function decode ( elm, dat, arg ) {
			var	cnt, i, ii, img;
			var	codes = dat.pictures;
			if ( codes ) {
				dat.images = [];
				cnt = codes.length;
				for ( i=0,ii=cnt; i<ii; i++ ) {
					img = new Image();
					dat.images[i] = img;
					img.onload = function () {
						if ( --cnt === 0 ) {
							decoded( elm, dat, arg );
						}
					};
					img.src = codes[i];
				}
			}
			
			function decoded ( elm, dat, arg ) {
				delete dat.pictures;
				start( elm, dat, arg );//setTimeout( function () {start( elm, dat, arg );}, 0);
			}
		}
	}
	
	function start ( elm, dat, arg ) {
		var	reel = wind( dat, arg );
		if ( reel ) {
			reel.mount( replace( elm ) );
		}
		
		function replace ( elm ) {
			if ( elm.tagName.toLowerCase === 'canvas' ) return elm;
			var	can = document.createElement('canvas');
			if ( typeof elm.width === 'number' && typeof elm.height === 'number' ) {
				can.width = elm.width;
				can.height = elm.height;
			} else {
				can.width = 0;
				can.height = 0;
			}
			if ( elm.name ) {
				can.id = elm.name;
			}
			if ( elm.id ) {
				can.id = elm.id;
			}
			if ( elm.className ) {
				can.className = elm.className;
			}
			if ( elm.style.cssText ) {
				can.style.cssText = elm.style.cssText;
			}
			elm.parentNode.replaceChild( can, elm );
			return can;
		}
	}
	
	function wind ( dat, arg ) {
		var	screen;
		var	width = dat.width;
		var	height = dat.height;
		var length = dat.length;
		var	fps = dat.fps;
		var	draws = [];
		var	nextes = [];
		var durations = [];
		var	caches = [];
		var	clock = null;
		var	loop = true;
		var	auto = true;
		var	duration;
		var	wait, tail;
		var	fore, back = null;
		var	toshow = null;
		var	shown = false;
		var	p;
		var	forceSize = false;
		var	canvases;
		
		if ( dat.level > 0 || typeof width !== 'number' || width <=0 || typeof height !== 'number' || height <= 0 || typeof length !== 'number' || length <= 0 || typeof fps !== 'number' || fps <= 0 ) return null;

		if ( typeof dat.loop === 'boolean' ) loop = dat.loop;
		if ( typeof dat.auto === 'boolean' ) auto = dat.auto;
		if ( dat.size === 'force' ) forceSize = true;
		
		canvases = canFactory();
		
		return {mount:mount,playing:playing,play:play,stop:stop,turn:turn};
		
		function mount ( canvas ) {
			screen = canvas;
			
			if ( forceSize || ! screen.width ) screen.width = width;
			if ( forceSize || ! screen.height ) screen.height = height;
			if ( dat.click === 'turn' ) {
				screen.addEventListener( 'click', turn );
			}
			
			fore = canvases.get();
			init();
			wait = 0;
			step();
			
			screen[REEL_ATTRIBUTE] = this;
			return this;
		}
		
		function playing () {
			return !! clock;
		}
		
		function play () {
			if ( ! clock ) {
				clock = setInterval( tick, 1000/fps );
			}
			return this;
		}
		
		function stop () {
			if ( clock ) {
				clearInterval( clock );
				clock = null;
			}
			return this;
		}
		
		function turn () {
			if ( clock ) {
				stop();
			} else {
				play();
			}
			return this;
		}

		function init () {
			var	i, ii, j, jj, p, off, lays;
			var	one, tap, taps, can;
			var	shots = dat.shots;
			
			if ( caches.length ) {
				can = back;
				if ( can ) {
					back = null;
					canvases.put( can );
				}
				for ( i=0,ii=caches.length; i<ii; i++ ) {
					can = caches[i].imageCanvas;
					if ( can ) {
						caches[i].imageCanvas = null;
						canvases.put( can );
					}
					(function (layers) {
						var	j, jj, sub;
						for ( j=0,jj=layers.length; j<jj; j++ ) {
							sub = layers[j].imageCanvas;
							if ( sub ) {
								layers[j].imageCanvas = null;
								canvases.put( sub );
							}
							sub = layers[j].layers;
							if ( sub ) arguments.callee( sub );
						}
					})(caches[i].layers);
				}
			}
		
			for ( i=0,ii=shots.length; i<ii; i++ ) {
				draws[i] = -1;
				nextes[i] = 0;
				durations[i] = !!(off = shots[i].offset)?off:0;
				caches[i] = {};
				
				if ( !!(taps = shots[i].taps) ) {
					caches[i].taps = [];
					for ( j=0,jj=taps.length; j<jj; j++ ) {
						one = {};
						one.xx = one.yy = 1.0;
						one.xy = one.yx = 0.0;
						one.dx = one.dy = 0.0;
						tap = taps[j];
						for ( p in tap ) {
							if ( tap.hasOwnProperty( p ) ) {
								switch ( p ) {
								case 'xxyys':
									tap.yys = tap.xxs = tap.xxyys;
									delete tap.xxyys;
									break;
								case 'xyyxs':
									tap.yxs = tap.xys = tap.xyyxs;
									delete tap.xyyxs;
									break;
								case 'xxyy':
									one.xx = one.yy = tap.xxyy;
									break;
								case 'xyyx':
									one.xy = one.yx = tap.xyyx;
									break;
								case 'xx':
								case 'yy':
								case 'xy':
								case 'yx':
								case 'dx':
								case 'dy':
									one[p] = tap[p];
									break;
								}
							}
						}
						one.matrix = layerMatrix( one );
						caches[i].taps[j] = one;
					}
				}

				if ( !!(lays = shots[i].layers) ) {
					caches[i].layers = (function ( lays ) {
						var	j, jj, lay, sub, one, ini = [];
						for ( j=0,jj=lays.length; j<jj; j++ ) {
							one = {cell:0,ope:'source-over',alpha:1.0};
							lay = lays[j];
							if ( lay.tap ) {
								one.tap = caches[i].taps[lay.tap-1];
							}
							if ( lay.ope ) {
								one.ope = lay.ope;
							}
							if ( lay.alpha ) {
								one.alpha = lay.alpha;
							}
							if ( lay.cell ) {
								one.cell = lay.cell;
							}
							if ( !!(sub = lay.layers) ) {
								one.layers = arguments.callee( sub );
								one.imageCanvas = null;
								one.imageMatrix = null;
								one.imageIndex = -1;
							}
							ini[j] = one;
						}
						return ini;
					})( lays );
					caches[i].imageCanvas = null;
					caches[i].imageMatrix = null;
				}
			}
			duration = 0;
			tail = 0;
		}
		
		function step () {
			var	i, ii, n, nn, shot, can;
			for ( i=0,ii=durations.length; i<ii; i++ ) {
				if ( (durations[i] -= duration) === 0 ) {
					shot = dat.shots[i];
					n = nextes[i];
					nn = ( shot.durations )?shot.durations.length:shot.length;
					if ( n === nn ) {
						durations[i] = length - tail;
						nextes[i] = 0;
						draws[i] = -1;
						can = caches[i].imageCanvas;
						if ( can ) {
							caches[i].imageCanvas = null;
							canvases.put( can );
						}
						caches[i].imageMatrix = null;
					} else {
						nextes[i]++;
						if ( ! shot.durations ) {
							durations[i] = 1;
						} else if ( shot.durations[n] ) {
							durations[i] = shot.durations[n];
						}
						draws[i] = n;
					}
				}
			}
			duration = Math.min.apply( null, durations );
			
			draw();
			
			if ( wait <= 0 ) {
				show();
			}
		}
		
		function draw () {
			var	todraw, ctx;
			var	idx, keep, img, can;
			var	i, ii, drawn = false;
			
			toshow = todraw = fore;
			for ( i=0,ii=draws.length; i<ii; i++ ) {
				if ( (idx = draws[i]) < 0 ) {
					can = caches[i].imageCanvas;
					if ( can ) {
						caches[i].imageCanvas = null;
						canvases.put( can );
					}
					continue;
				}
				if ( ! todraw ) {
					todraw = back = canvases.get();
				}
				keep = durations[i] > duration;
				img = caches[i].imageCanvas;
				if ( img ) {
					ctx = bufferContextClear( todraw );
					ctx.drawImage( img, 0, 0, img.width, img.height );
					if ( ! keep ) {
						can = caches[i].imageCanvas;
						if ( can ) {
							caches[i].imageCanvas = null;
							canvases.put( can );
						}
					}
				} else {
					if ( keep ) {
						todraw = img = canvases.get();
						caches[i].imageCanvas = img;
					}
					drawShot( todraw, dat.shots[i], idx, caches[i] );
				}
				if ( toshow !== todraw ) {
					ctx = bufferContext( toshow );
					ctx.drawImage( todraw, 0, 0, todraw.width, todraw.height );
				}
				todraw = back;
				drawn = true;
			}
			if ( ! drawn ) {
				bufferContextClear( toshow );
			}
		}
		
		function drawShot ( buf, shot, idx, cache ) {
			tapsUpdate();
			drawLayers( buf, shot.layers, idx, cache.layers, [1,0,0,1,0,0], null );
			
			function tapsUpdate () {
				var	j, jj, boo;
				var	tap, one;
				
				if ( shot.taps ) {
					for ( j=0,jj=shot.taps.length; j<jj; j++ ) {
						boo = false;
						tap = shot.taps[j];
						one = cache.taps[j];
						if ( tap.xxs && (tap.xxs[idx] !== undefined) ) {
							one.xx = tap.xxs[idx];
							boo = true;
						}
						if ( tap.yys && (tap.yys[idx] !== undefined) ) {
							one.yy = tap.yys[idx];
							boo = true;
						}
						if ( tap.xys && (tap.xys[idx] !== undefined) ) {
							one.xy = tap.xys[idx];
							boo = true;
						}
						if ( tap.yxs && (tap.yxs[idx] !== undefined) ) {
							one.yx = tap.yxs[idx];
							boo = true;
						}
						if ( tap.dxs && (tap.dxs[idx] !== undefined) ) {
							one.dx = tap.dxs[idx];
							boo = true;
						}
						if ( tap.dys && (tap.dys[idx] !== undefined) ) {
							one.dy = tap.dys[idx];
							boo = true;
						}
						if ( boo ) {
							one.matrix = layerMatrix( one );
						}
					}
				}
			}
			
			function drawLayers ( buf, lays, idx, ones, mtx, bcc ) {
				var	ctx = bcc || bufferContextClear( buf );
				layersUpdate();
				layersBuffer();
				
				function layersUpdate () {
					var	j, jj;
					var	lay, one, mmm;
					for ( j=0,jj=lays.length; j<jj; j++ ) {
						lay = lays[j];
						one = ones[j];
						
						if ( lay.alphas && (lay.alphas[idx] !== undefined) ) {
							one.alpha = lay.alphas[idx];
						}
						if ( lay.cells && (lay.cells[idx] !== undefined) ) {
							one.cell = lay.cells[idx];
						}
						if ( lay.layers ) {
							if ( one.cell && one.alpha ) {
								if ( (jj === 1) && (one.alpha === 1.0) && (one.ope === 'source-over') ) {
									mmm = multiMatrix( mtx, one.tap.matrix );
									drawLayers( buf, lay.layers, idx, one.layers, mmm, ctx );
								} else {
									if ( ! one.imageCanvas ) {
										one.imageCanvas = canvases.get();
										one.imageIndex = -1;
									}
									mmm = multiMatrix( mtx, one.tap.matrix );
									if ( (one.imageIndex >= 0) && compareMatrix( mmm, one.imageMatrix ) ) {
										one.imageIndex = -1;
									}
									if ( one.imageIndex !== one.cell ) {
										drawLayers( one.imageCanvas, lay.layers, idx, one.layers, mmm, null );
										if ( lay.cells && (idx + 1 >= lay.cells.length || lay.cells[idx+1] !== undefined) ) {
											one.imageIndex = -1;
										} else {
											one.imageIndex = one.cell;
											one.imageMatrix = mmm;
										}
									}
								}
							} else {
								if ( one.imageIndex >= 0 && lay.cells && (idx + 1 >= lay.cells.length || lay.cells[idx+1] !== undefined) ) {
									one.imageIndex = -1;
								}
							}
						}
					}
				}
				
				function layersBuffer () {
					var	j, jj;
					var	lay, one, img, can;
					var	msk, mmm;
					for ( j=0,jj=ones.length; j<jj; j++ ) {
						lay = lays[j];
						one = ones[j];
						if ( one.cell && one.alpha ) {
							ctx.globalAlpha = one.alpha;
							if ( one.layers ) {
								ctx.setTransform( 1, 0, 0, 1, 0, 0 );
								ctx.globalCompositeOperation = one.ope;
								img = one.imageCanvas;
							} else {
								img = dat.images[one.cell-1];
								if ( ! canDstIn && one.ope === 'destination-in' ) {
									if ( ! one.imageCanvas ) {
										one.imageCanvas = canvases.get();
										one.imageIndex = -1;
									}
									mmm = multiMatrix( mtx, one.tap.matrix );
									if ( (one.imageIndex >= 0) && compareMatrix( mmm, one.imageMatrix ) ) {
										one.imageIndex = -1;
									}
									if ( one.imageIndex !== one.cell ) {
										msk = bufferContext( one.imageCanvas );
										msk.fillRect( 0, 0, width, height );
										msk.globalCompositeOperation = 'destination-out';
										msk.setTransform.apply( msk, mmm );
										msk.drawImage( img, 0, 0, img.width, img.height );
										if ( lay.cells && (idx + 1 >= lay.cells.length || lay.cells[idx+1] !== undefined) ) {
											one.imageIndex = -1;
										} else {
											one.imageIndex = one.cell;
											one.imageMatrix = mmm;
										}
									}
									img = one.imageCanvas;
									ctx.setTransform( 1, 0, 0, 1, 0, 0 );
									ctx.globalCompositeOperation = 'destination-out';
								} else {
									multiTransform( ctx, mtx, one.tap.matrix );
									ctx.globalCompositeOperation = one.ope;
								}
							}
							if ( img ) {
								ctx.drawImage( img, 0, 0, img.width, img.height );
							}
						} else {
							if ( one.imageIndex >= 0 && lay.cells && (idx + 1 >= lay.cells.length || lay.cells[idx+1] !== undefined) ) {
								one.imageIndex = -1;
							}
						}
						can = one.imageCanvas;
						if ( can && one.imageIndex < 0 ) {
							one.imageCanvas = null;
							canvases.put( can );
						}
					}
					
					function multiTransform ( ctx, l, r ) {
						ctx.setTransform( l[0]*r[0] + l[2]*r[1], l[1]*r[0] + l[3]*r[1],
								l[0]*r[2] + l[2]*r[3], l[1]*r[2] + l[3]*r[3],
								l[0]*r[4] + l[2]*r[5] + l[4], l[1]*r[4] + l[3]*r[5] + l[5] );
					}
				}
				
				function multiMatrix ( l, r ) {
					return [l[0]*r[0] + l[2]*r[1], l[1]*r[0] + l[3]*r[1],
							l[0]*r[2] + l[2]*r[3], l[1]*r[2] + l[3]*r[3],
							l[0]*r[4] + l[2]*r[5] + l[4], l[1]*r[4] + l[3]*r[5] + l[5]];
				}
				
				function compareMatrix ( l, r ) {
					var	i;
					if ( ! l || ! r ) return true;
					if ( l === r ) return false;
					for ( i=5; i>=0; i-- ) {
						if ( l[i] !== r[i] ) return true;
					}
					return false;
				}
			}
		}
		
		function canFactory () {
			var	stock = [];
			return {get:get,put:put};
			
			function get () {
				var	can = stock.pop();
				if ( ! can ) {
					can = document.createElement('canvas');
					can.width = width;
					can.height = height;
				}
				return can;
			}
			
			function put ( can ) {
				if ( can ) {
					stock.push( can );
				}
			}
		}
		
		function bufferContextClear ( buf ) {
			var	ctx = bufferContext( buf );
			ctx.clearRect( 0, 0, buf.width, buf.height );
			return ctx;
		}
		
		function bufferContext ( buf ) {
			var	ctx = buf.getContext('2d');
			ctx.globalCompositeOperation = 'source-over';
			ctx.globalAlpha = 1.0;
			ctx.setTransform( 1, 0, 0, 1, 0, 0 );
			return ctx;
		}
	
		function layerMatrix( tap ) {
			return [tap.xx,tap.xy,-tap.yx,tap.yy,tap.dx,tap.dy];
		}
		
		function tick () {
			if ( --wait <= 0 ) {
				if ( shown ) {
					show();
				}
			}
		}
		
		function show () {
			if ( toshow ) {
				if ( tail === 0 ) {
					if ( auto ) {
						play();
					} else {
						stop();
					}
				}
				var	ctx = screen.getContext('2d');
				ctx.clearRect( 0, 0, screen.width, screen.height );
				ctx.drawImage( toshow, 0, 0, screen.width, screen.height );
				toshow = null;
				wait += duration;
				tail += duration;
				if ( tail >= length ) {// > data error
					init();
					auto = loop;
				}
				setTimeout( step, 0 );
				shown = true;
			} else {
				setTimeout( show, 16 );// 16 = 1000/60
				shown = false;
			}
		}
	}
	//customized subset of JSON parser Douglas Crockford JavaScript:The Good Parts O'REILLY
	parser = (function () {
		var	at, ch, text,
			
			error = function (m) {
				throw {
					name:	'SyntaxError',
					message:	m,
					at:		at,
					text:	text
				};
			},
			
			next = function (c) {
				if (c && c !== ch) {
					error(BADCHAR1_MESSAGE + c + BADCHAR2_MESSAGE + ch + BADCHAR3_MESSAGE);
				}
				ch = text.charAt(at);
				at += 1;
				return ch;
			},
			
			number = function () {
				var	number,
					string = '';
				
				if (ch === '-') {
					string = '-';
					next('-');
				}
				while (ch >= '0' && ch <= '9') {
					string += ch;
					next();
				}
				if (ch === '.') {
					string += '.';
					while (next() && ch >= '0' && ch <= '9') {
						string += ch;
					}
				}
				//no exponent
				number = +string;
				if (isNaN(number)) {
					error(BADNUMBER_MESSAGE);
				} else {
					return number;
				}
			},
			
			string = function () {
				var	string = '';
				if (ch === '"') {
					while (next()) {
						if (ch === '"') {
							next();
							return string;
						} else if (ch === '\\') {
							//no escapee
							break;
						} else {
							string += ch;
						}
					}
				}
				error(BADSTRING_MESSAGE);
			},
			
			white = function () {
				while (ch && ch <= ' ') {
					next();
				}
			},
			
			word = function () {
				switch (ch) {
				case 't':
					next('t');
					next('r');
					next('u');
					next('e');
					return true;
				case 'f':
					next('f');
					next('a');
					next('l');
					next('s');
					next('e');
					return false;
				case 'n':
					next('n');
					next('u');
					next('l');
					next('l');
					return null;
				}
				error(BADWORD1_MESSAGE + ch + BADWORD2_MESSAGE);
			},
			
			value,
			
			array = function () {
				var	array = [];
				
				if (ch === '[') {
					next('[');
					white();
					if (ch === ']') {
						next(']');
						return array;
					}
					while (ch) {
						if (ch === ',' || ch === ']') {//tolerate empty element
							array.length += 1;
						} else {
							array.push(value());
							white();
						}
						if (ch === ']') {
							next(']');
							return array;
						}
						next(',');
						white();
					}
				}
				error(BADARRAY_MESSAGE);
			},
			
			object = function () {
				var	key,
					object = {};
				
				if (ch === '{') {
					next('{');
					white();
					if (ch === '}') {
						next('}');
						return object;
					}
					while (ch) {
						key = string();
						white();
						next(':');
						object[key] = value();
						white();
						if (ch === '}') {
							next('}');
							return object;
						}
						next(',');
						white();
					}
				}
				error(BADOBJECT_MESSAGE);
			};
		
		value = function () {
			white();
			switch (ch) {
			case '{':
				return object();
			case '[':
				return array();
			case '"':
				return string();
			case '-':
				return number();
			default:
				return ch >= '0' && ch <= '9' ? number() : word();
			}
		};
		
		return function (source) {
			var	result;
			
			text = source;
			at = 0;
			ch = ' ';
			result = value();
			white();
			if (ch) {
				error(SYNTAXERROR_MESSAGE);
			}
			//no reviver
			return result;
		};
	})();
})();
