var Mosaic = Asset.extend({
	tile: function(source, properties){
		properties = $merge({
			'onload': Class.empty,
			'onabort': Class.empty,
			'onerror': Class.empty
		}, properties);
		var image = new Image();
		image.src = source;
		var element = new Element('img', {'src': source});
		if (image.width && image.height) {
			element.fireEvent('load', element, 1);
			var event = properties['onload'];
			delete properties['onload'];
			event.call(this);
		} else {
			['load', 'abort', 'error'].each(function(type){
				var event = properties['on' + type];
				delete properties['on' + type];
				element.addEvent(type, function(){
					this.removeEvent(type, arguments.callee);
					event.call(this);
				});
			});
		}
		return element.setProperties(properties);
	}
});

var MooZoom = new Class({
	initialize: function(viewport, options){
		this.setOptions({
			"baseImage":null,
			"imagePath":null,
			"zoomInButton":null,
			"zoomOutButton":null,
			"maxZoom":4,
			"minZoom":1,
			"tileSize":[100,90],
			"baseImageSize":[800,1440],
			"onZoomIn": Class.empty,
			"onZoomOut": Class.empty,
			"onDrag": Class.empty,
			"onLoading": Class.empty,
			"onComplete": Class.empty
		},options);
		
		if(!$(viewport)) return;
		
		this.ready = false;
		//this.zoomFactor = this.options.maxZoom / 2; //1 step from max
		this.zoomFactor = 4;
		this.multiplier = this.options.minZoom * 2;
		this.viewport = viewport;

		
		if($(this.options.baseImage)){
			this.options.baseImageSize = [($(this.options.baseImage).width * this.options.maxZoom), ($(this.options.baseImage).height * this.options.maxZoom)];
			this.baseImage = $(this.options.baseImage).clone();
			this.baseImage.setStyles({
				"position":"absolute",
				"left":"0px",
				"top":"0px",
				"z-index":"1"
			});
		}
		
		$(viewport).setStyles({
			"width": (this.options.baseImageSize[0] / this.options.maxZoom),
			"height": (this.options.baseImageSize[1] / this.options.maxZoom)
		});
	
		this.rgn_x = 0;
		this.rgn_y = 0;
		this.rgn_w = $(viewport).getSize().size.x;
		this.rgn_h = $(viewport).getSize().size.y;
				
		if($(this.options.zoomInButton)) {
			$(this.options.zoomInButton).addEvent("click", (function(e){
				new Event(e).stop();
				this.zoomIn();
			}).bind(this));
		}
		
		if($(this.options.zoomOutButton)) {
			$(this.options.zoomOutButton).setStyle("visibility","hidden");
			$(this.options.zoomOutButton).addEvent("click", (function(e){
				new Event(e).stop();
				this.zoomOut();
			}).bind(this));
		}

	},
	
	configureCanvas: function(){
		$(this.viewport).empty();
		
		this.zoomFactor == this.options.minZoom && $(this.options.zoomInButton) ? $(this.options.zoomInButton).setStyle("visibility", "hidden") : $(this.options.zoomInButton).setStyle("visibility", "visible");
		this.zoomFactor == this.options.maxZoom && $(this.options.zoomOutButton) ? $(this.options.zoomOutButton).setStyle("visibility", "hidden") : $(this.options.zoomOutButton).setStyle("visibility", "visible");
		
		this.w = (this.options.baseImageSize[0] / this.zoomFactor);
		this.h = (this.options.baseImageSize[1] / this.zoomFactor);
		
		this.mainContainer = new Element("div",{
			"class": "MooZoom",
			"styles": {
				"width": this.w,
				"height": this.h,
				"position": "relative",
				"overflow": "hidden"
			}
		});
			
		this.mainContainer.injectInside($(this.viewport));
		
		var xLimit = this.w - this.rgn_w;
		var yLimit = this.h - this.rgn_h;
		
		if((this.rgn_x + this.rgn_w) > this.w) this.rgn_x = xLimit;
		if((this.rgn_y + this.rgn_h) > this.h) this.rgn_y = yLimit;
		if(this.rgn_x < 0) this.rgn_x = 0;
		if(this.rgn_y < 0) this.rgn_y = 0;
		
		this.mainContainer.setStyles({
			"left": -this.rgn_x,
			"top": -this.rgn_y
		});
			
		if(this.w > this.rgn_w){
			this.mainContainer.setStyle("cursor", "move").makeDraggable({
				"limit": {"x": [-xLimit, 0], "y": [-yLimit, 0]},
				"onDrag": this.options.onDrag,
				"onComplete": (function(t){ this.reportPosition(t) }).bind(this)
			});
		}
		
		this.configureTiles();
	},
	
	configureTiles: function(){
		this.options.onLoading();
		
		this.mainContainer.empty();
	
		if(this.baseImage){
			$(this.baseImage).injectInside(this.mainContainer);
			$(this.baseImage).setStyles({
				"width": this.w,
				"height": this.h
			});
		}
	
		// Get the start points for our tiles
		var startx = Math.floor( this.rgn_x / this.options.tileSize[0] );
		var starty = Math.floor( this.rgn_y / this.options.tileSize[1] );
	
		// If our size is smaller than the display window, only get these tiles!
		var len = this.rgn_w;
		if( this.w < this.rgn_w ) len = this.w;
		var endx =  Math.floor( (len + this.rgn_x) / this.options.tileSize[0] );
	
		len = this.rgn_h;
		if( this.h < this.rgn_h ) len = this.h;
		var endy = Math.floor( (len + this.rgn_y) / this.options.tileSize[1] );
	
		// Calculate the offset from the tile top left that we want to display
		var xoffset = Math.floor(this.rgn_x % this.options.tileSize[0]);
		var yoffset = Math.floor(this.rgn_y % this.options.tileSize[1]);
	
		var i, j, k;
		
		this.tileArray = [];
		this.loadCheck = [];

		var xtiles = Math.floor(this.rgn_w / this.options.tileSize[0]) + 1;
		var ytiles = Math.floor(this.rgn_h / this.options.tileSize[1]) + 1;		
		var totalTiles = xtiles * ytiles;
		
		for(k=0;k < totalTiles;k++){
			this.loadCheck[k] = false;
		}
		
		k = 0;
		// Create our image tile mosaic
		for(j = starty; j <= endy; j++){
			for(i = startx; i <= endx; i++){

				var src = this.options.imagePath + "&x=" + (i + 1) + "&y=" + (j + 1) + "&res=" + this.zoomFactor;
				var img = new Mosaic.tile(src,{
					"onload": this.imageLoaded.bind(this, [k++])
				}).setStyles({
					"position": "absolute",
					"left": (i-startx) * this.options.tileSize[0] - xoffset + this.rgn_x,
					"top": (j-starty) * this.options.tileSize[1] - yoffset + this.rgn_y,
					"z-index":"2"
				});
				
				if(!window.ie){
					img.setStyle("visibility", "hidden");
					img.addEvent("load", function(){ 
						if($(this)){ 
							this.setStyle("visibility","visible"); 
						}
					});
				}
				
				img.injectInside(this.mainContainer);
			}
		}
	},
	
	imageLoaded: function(i){
		this.loadCheck[i] = true;
		
		var result = this.loadCheck.every(function(current, index){
			return current;
		});

		if(result){
			this.options.onComplete(this);
		}

	},
	
	zoomOut: function(){
		this.options.onZoomOut();
	
		if(!this.ready){
			this.zoomIn();
			return;
		}
		
		this.zoomFactor = this.zoomFactor * 2;
		
		if(this.zoomFactor > this.options.maxZoom || this.zoomFactor < this.options.minZoom){
			this.zoomFactor = this.options.maxZoom;
			return;
		}
		
		this.multiplier = this.multiplier / 2;

		var xpos = this.rgn_x + (this.rgn_w / 2);
		var ypos = this.rgn_y + (this.rgn_h / 2);
		this.rgn_x = (xpos / 2) - (this.rgn_w /2);
		this.rgn_y = (ypos / 2) - (this.rgn_h /2);
				
		this.configureCanvas();
	},
	
	zoomIn: function(){
		this.options.onZoomIn();
	
		if(!this.ready){
			this.ready = true;
			this.rgn_x = this.rgn_w / 2;
			this.rgn_y = this.rgn_h / 2;
			this.configureCanvas();
			return;
		}

		this.zoomFactor = this.zoomFactor / 2;
		
		if(this.zoomFactor < this.options.minZoom || this.zoomFactor > this.options.maxZoom){
			this.zoomFactor = this.options.minZoom;
			return;
		}
		
		this.multiplier = this.multiplier * 2;

		if(this.zoomFactor != this.multiplier){
			var xpos = this.rgn_x + (this.rgn_w / 2);
			var ypos = this.rgn_y + (this.rgn_h / 2);
			this.rgn_x = (xpos * 2) - (this.rgn_w /2);
			this.rgn_y = (ypos * 2) - (this.rgn_h /2);
			
		} else {
			//centre canvas
			this.rgn_x = this.rgn_w / 2; 
			this.rgn_y = this.rgn_h / 2;
		}
		
		this.configureCanvas();
	},
	
	reportPosition: function(t){
		var parentSize = $(this.viewport).getPosition();
		var viewportSize = t.getPosition();
		this.rgn_x = -(viewportSize.x - parentSize.x);
		this.rgn_y = -(viewportSize.y - parentSize.y);
		
		this.configureTiles();
	}
	
});

MooZoom.implement(new Options);
