/*
VERSION:		3.7
	3.7		function makeMap  =>  var makeMap = function  to allow RELIABLE scoping
	3.6		makeMap = function  =>  function makeMap  to enable scoping.
	3.5		Changed drawObj() to reset existing maps to new size
	3.4		x,y properties are no longer initialized to _x,_y.  They always start as 0
	3.3		Try making scrollArea rect persist instead of re-creating it each frame
	3.2		Add "x" and "y" position properties.  Standard way to get & set the map's position, regardless of which scroll technique is used.
	3.1		Add (disabled) experimental scrolling using scrollRect property instead of _x _y  (currently disabled due to streaking issues when the framerate drops)
	
MIN USAGE:
	#include "map.as"
	makeMap( "map_mc" );		// creates a 20x15 map
	
MAX USAGE:
	#include "map.as"
	my_map = makeMap( "map_mc", this, 42, 9, 9 );		// creates a 9x9 map at depth 42
	
INTERFACE:  (functions & parameters)
	makeMap					( instanceName, target_mc, depth, width, height )
	drawCompactObj	( mapData_obj )
	drawLargeObj		( mapData_obj )
	drawObj					( mapData_obj )
	load						( fileName )
	loadChipset			( fileName )
	onLoad					( - )
	reset						( width, height )
	scroll					( x, y, screenWidth, screenHeight, smoothness )
	setTile					( x, y, id, layer )
	addCollisionMethods( collision_array )
	
FUNCTIONS:
	makeMap()					Creates the map system.
	load()						Loads, converts, resets, and draws a map, based on an XML file.  It also loads the collision data.
	drawObj()					Detects file format & calls either  drawLargeObj()  or  drawCompactObj()
	drawLargeObj()		Resets & draws a map, based on the data provided.  It also imports the collision data.  (old brute-force file format)
	drawCompactObj()	Resets & draws a map, based on the data provided.  It also imports the collision data.  (old compact file format)
	setTile()					Draws a tile.
	reset()						Clears the chipset & all layers.
	loadChipset()			Loads a chipset.  (file or linkage)
	scroll()					Moves the map to center on a movieClip inside of it.
	addCollisionMethods()			Appends externally-#included collision methods to collision_array each time the map is created, reset, or drawn.
	
CALLBACK FUNCTIONS:  (Not actual events)
	onLoad()					Runs after a chipset is loaded.  Externally defined.
	
VARIABLES:
	width							Width of the map, in tiles.
	height						Height of the map, in tiles.
	collision_array		2D array containing collision values.
	chipset_pic				Bitmap containing chipset palette.
	layer0_pic				Image containing all tiles on layer 0.		(maps can have any amount of layers)
	layer0_mc					Movieclip displaying layer0_pic.
	layer1_pic				Image containing all tiles on layer 1.
	layer1_mc					Movieclip displaying layer1_pic.
	_x  _y						Current scroll position.
	
	load_xml					Last XML file loaded by load().  If drawObj() is used instead, this variable will NOT get created nor modified.  (This ought to be a temporary variable)
	
DEPENDANCIES:
	addCollisionMethods.as
	nextDepth.as
	readXml.as
	
NOTE:
	XML data should be saved in a <map> tag, like so:
	writeXml( myData, myXml, "map" );
	
NOTE:
	Layers are assigned depths in incriments of 10
		0 = 0
		1 = 10
		2 = 20
	This allows movieClips to be inserted between them.
	
NOTE:
	chipsets can be either external files, or bitmaps with linkage.
	
TO USE MAP DATA VIA LINKAGE INSTEAD OF EXTERNAL FILES:
	You can pull this off by creating a movieClip and copying the xml data into a VERY long string,
	passing the string to a new XML object,
	and passing the movieClip's linkage to the map's load() function as if it were a filename.
	
	The map will place the movieClip and define an onLoad() function for it.
	The movieClip then calls onLoad() to pass XML data to the map system.
	
	data_txt = '<map>~~';
	data_txt += '~~</map>';				(with larger maps, you may need to split the string like this to avoid weird errors)  (my rule of thumb is to split the string when it reaches half a page long)
	xmlData = new XML(data_txt);
	onLoad( xmlData );
*/
var makeMap = function( instanceName, target_mc, depth, width, height )
{
	#include "nextDepth.as"
	
	// resolve optional parameters
	var target_mc = (target_mc) ? target_mc : this;
	var depth = (depth != undefined) ? depth : nextDepth(target_mc);
	var width = (width) ? width : 20;
	var height = (height) ? height : 15;
	
	// create container movieClip
	var _this = target_mc.createEmptyMovieClip( instanceName, depth );
	
	// define internal variables
	_this.width = width;
	_this.height = height;
	
	// create empty chipset
	_this.chipset_pic = new flash.display.BitmapData( 480, 256, true, 0 );
	
	// create empty collision array
	_this.collision_array = new Array();
	for(var x=0; x<width; x++)
	{
		_this.collision_array[x] = new Array();
		for(var y=0; y<height; y++)
		{
			_this.collision_array[x][y] = 0;
		}// for y
	}// for x
	#include "addCollisionMethods.as"
	addCollisionMethods( _this.collision_array );
	
	
	
	
	
	// ________________________________________________________
	// 	FUNCTIONS
	
	_this.reset = function( width, height )
	{
		// remove movieClips
		// remove images
		for (var nam in _this)
		{
			var thisObj = _this[nam];
			// remove layers
			if( typeof(thisObj) == "movieclip" )
			{
				if (nam.slice(0, 5) == "layer")
				{
					thisObj.removeMovieClip();
				}
			}
			// remove layer images  &  chipset
			else if( thisObj instanceof flash.display.BitmapData )
			{
				if (nam.slice(0, 5) == "layer"  ||
					nam == "chipset_pic")
				{
					_this[nam].dispose();
					delete _this[nam];		// delete original reference
				}
			}
		}// for-in:  _this
		
		// reset variables
		_this.width = (width) ? width : _this.width;		// if new size isn't specified, retain existing size
		_this.height = (height) ? height : _this.height;
		// reset chipset image
		_this.chipset_pic = new flash.display.BitmapData( 480, 256, true, 0 );
		//var destArea = new flash.geom.Rectangle( 0, 0, 480, 256 );
		//_this.chipset_pic.fillRect( destArea, 0 );
		
		// create empty collision array
		_this.collision_array = new Array();
		for(var x=0; x<_this.width; x++)
		{
			_this.collision_array[x] = new Array();
			for(var y=0; y<_this.height; y++)
			{
				_this.collision_array[x][y] = 0;
			}// for y
		}// for x
		#include "addCollisionMethods.as"
		addCollisionMethods( _this.collision_array );
	}// reset()
	
	
	
	
	
	_this.setTile = function(x, y, id, layer)
	{
		// resolve optional parameters
		var layer = (layer != undefined) ? layer : 0;
		
		// determine copy coordinates, based on id
		var chipWidth = 30;
		var xChip = id % chipWidth;
		var yChip = Math.floor( id / chipWidth );
		var copyArea = new flash.geom.Rectangle(
											xChip*16,
											yChip*16,
											16,
											16);
		
		// determine paste coordinates
		var pastePoint = new flash.geom.Point(
											  x*16,
											  y*16);	
		
		// create layer if neccessary
		var layerName = "layer"+layer+"_mc";			// layer2_mc
		var imageName = "layer"+layer+"_pic";		// layer2_pic
		if( _this[layerName] == undefined )
		{// if:  layer doesn't exist
			// image
			var thisImage = _this[imageName] = new flash.display.BitmapData(
															_this.width*16,
															_this.height*16,
															true,
															0);
			// movieClip
			_this.createEmptyMovieClip( layerName, layer*10 );
			_this[layerName].attachBitmap( thisImage, 0, false, false );
			_this[layerName].cacheAsBitmap = true;
		}// if:  layer doesn't exist
		
		// copy the tile image to the layer
		_this[imageName].copyPixels( _this.chipset_pic, copyArea, pastePoint );
	}// setTile()
	
	
	
	
	
	_this.loadChipset = function( fileName )
	{
		// clear image
		var fillArea = new flash.geom.Rectangle(0,0,480,256);
		_this.chipset_pic.fillRect( fillArea, 0 );
		
		// load image
		var newChip_pic = flash.display.BitmapData.loadBitmap( fileName );
		if( newChip_pic.width > -1 )
		{// linkage succeeded
			_this.chipset_pic = newChip_pic.clone();
			newChip_pic.dispose();
			delete newChip_pic;
			
			// call external function
			_this.onLoad();		// externally-defined function
		}// if:  linkage succeeded
		else
		{// linkage failed
			_this.createEmptyMovieClip( "load_mc", 99 );
			_this.load_mc._xscale = 1;
			_this.load_mc._yscale = 1;
			_this.loader = new MovieClipLoader();
			_this.loader.loadClip( fileName, _this.load_mc );
			
			
			_this.loader.onLoadInit = function( load_mc )
			{
				// copy loaded image
				// // snapshot
				var newChip_pic = new flash.display.BitmapData( 480, 256, true, 0 );
				newChip_pic.draw( _this.load_mc );
				// // copy
				_this.chipset_pic = newChip_pic.clone();
				newChip_pic.dispose();
				delete newChip_pic;
				
				// call external function
				_this.onLoad();		// externally-defined function
				
				// // clean-up
				_this.loader.unloadClip( load_mc );		// removes load_mc
				delete _this.loader;
			}// chipset loaded()
		}// if:  linkage failed
	}// loadChipset()
	
	
	
	
	
	_this.drawObj = function( data_obj )
	{
		switch( data_obj.format )
		{// case:  format
			case 2:		// compact format
				_this.drawCompactObj( data_obj );
			break;
			default:
				_this.drawLargeObj( data_obj );
		}// case:  format
	}// drawObj()
	
	
	
	
	
	_this.drawLargeObj = function( data_obj )
	{
		// reset
		_this.reset( data_obj.width, data_obj.height );
		
		// width
		_this.width = data_obj.width;
		
		// height
		_this.height = data_obj.height;
		
		// collision
		if( data_obj.collision != undefined)
		{// if:  collision data exists
			_this.collision_array = new Array();
			for(var x=0; x<data_obj.width; x++)
			{
				_this.collision_array[x] = new Array();
				for(var y=0; y<data_obj.height; y++)
				{
					_this.collision_array[x][y] = data_obj.collision[x][y];
				}// for y
			}// for x
			#include "addCollisionMethods.as"
			addCollisionMethods( _this.collision_array );
		}// if:  collision data exists
		
		
		
		// chipset_pic
		// // clear image
		var fillArea = new flash.geom.Rectangle(0,0,480,256);
		_this.chipset_pic.fillRect( fillArea, 0 );
		
		// load image
		var newChip_pic = flash.display.BitmapData.loadBitmap( data_obj.chipset );
		if( newChip_pic.width > -1 )
		{// linkage succeeded
			_this.chipset_pic = newChip_pic.clone();
			newChip_pic.dispose();
			delete newChip_pic;
			
			
			
			// draw layers  (setTile)
			for (var lay=0; lay<data_obj.layers.length; lay++)
			{
				for (var x=0; x<_this.width; x++)
				{
					for (var y=0; y<_this.height; y++)
					{
						var id = data_obj.layers[lay][x][y];
						_this.setTile( x, y, id, lay );
					}// for:  width
				}// for:  height
			}// for:  layers
			
			
			
			// call external function
			_this.onLoad();		// externally-defined function
			
			
			// clean-up
			delete _this.loadData;		// In case load() called this function, then clean up after load() as well.
		}// if:  linkage succeeded
		else
		{// linkage failed
			_this.createEmptyMovieClip( "load_mc", 99 );
			_this.load_mc._xscale = 1;
			_this.load_mc._yscale = 1;
			_this.loader = new MovieClipLoader();
			_this.loader.loadClip( data_obj.chipset, _this.load_mc );
			

			_this.loader.onLoadInit = function( load_mc )
			{
				// // copy loaded image
				// // // snapshot
				var newChip_pic = new flash.display.BitmapData( 480, 256, true, 0 );
				newChip_pic.draw( _this.load_mc );
				// // // copy
				var copyArea = new flash.geom.Rectangle( 0, 0, 480, 256 );
				_this.chipset_pic.copyPixels( newChip_pic, copyArea, {x:0,y:0} );
				// // // clean-up
				_this.loader.unloadClip( _this.load_mc );
				
				
				
				// draw layers  (setTile)
				for (var lay=0; lay<data_obj.layers.length; lay++)
				{
					for (var x=0; x<_this.width; x++)
					{
						for (var y=0; y<_this.height; y++)
						{
							var id = data_obj.layers[lay][x][y];
							_this.setTile( x, y, id, lay );
						}// for:  width
					}// for:  height
				}// for:  layers
				
				
				// call external function
				_this.onLoad();		// externally-defined function
				
				
				// clean-up
				delete _this.loadData;		// In case load() called this function, then clean up after load() as well.
				delete _this.loader;
			}// chipset loaded()
		}// if:  linkage failed
	}// drawLargeObj()
	
	
	
	
	
	_this.drawCompactObj = function( data_obj )
	{
		// reset
		_this.reset( data_obj.width, data_obj.height );
		
		// width
		_this.width = data_obj.width;
		
		// height
		_this.height = data_obj.height;
		
		// collision
		var readIndex = 1;
		if( data_obj.collision != undefined)
		{// if:  collision data exists
			_this.collision_array = new Array();
			for(var x=0; x<data_obj.width; x++)
			{
				_this.collision_array[x] = new Array();
				for(var y=0; y<data_obj.height; y++)
				{
					// get data & convert it from:  base-36 string array -> 2D-array of Numbers
					var thisString = data_obj.collision.charAt( readIndex );
					if(thisString == "0x"){		// 0x is misread as HEX, so handle it manually
						var thisValue = 33;
					}else{
						var thisValue = parseInt( thisString, 36 );
					}
					readIndex++;
					// write data
					_this.collision_array[x][y] = thisValue;
				}// for y
			}// for x
			#include "addCollisionMethods.as"
			addCollisionMethods( _this.collision_array );
		}// if:  collision data exists
		
		
		
		// chipset_pic
		// // clear image
		var fillArea = new flash.geom.Rectangle(0,0,480,256);
		_this.chipset_pic.fillRect( fillArea, 0 );
		
		// drawLayers()
		var drawLayers = function()
		{
			for (var lay=0; lay<data_obj.layers.length; lay++)
			{
				var readIndex = 1;
				for (var x=0; x<_this.width; x++)
				{
					for (var y=0; y<_this.height; y++)
					{
						// get data & convert it from:  base-36 string array -> 2D-array of Numbers
						var thisString = data_obj.layers[lay].substr( readIndex, 2 );
						if(thisString == "0x"){		// 0x is misread as HEX, so handle it manually
							var thisValue = 33;
						}else{
							var thisValue = parseInt( thisString, 36 );
						}
						readIndex+=2;
						_this.setTile( x, y, thisValue, lay );
					}// for:  width
				}// for:  height
			}// for:  layers
		}// drawLayers()
		
		
		// load image
		var newChip_pic = flash.display.BitmapData.loadBitmap( data_obj.chipset );
		// draw
		if( newChip_pic.width > -1 )
		{// linkage succeeded
			_this.chipset_pic = newChip_pic.clone();
			newChip_pic.dispose();
			delete newChip_pic;
			
			// draw layers  (setTile)
			drawLayers();
			
			// call external function
			_this.onLoad();		// externally-defined function
			
			// clean-up
			delete _this.loadData;		// In case load() called this function, then clean up after load() as well.
			delete drawLayers;
		}// if:  linkage succeeded
		else
		{// linkage failed
			_this.createEmptyMovieClip( "load_mc", 99 );
			_this.load_mc._xscale = 1;
			_this.load_mc._yscale = 1;
			_this.loader = new MovieClipLoader();
			_this.loader.loadClip( data_obj.chipset, _this.load_mc );
			

			_this.loader.onLoadInit = function( load_mc )
			{
				// // copy loaded image
				// // // snapshot
				var newChip_pic = new flash.display.BitmapData( 480, 256, true, 0 );
				newChip_pic.draw( _this.load_mc );
				// // // copy
				var copyArea = new flash.geom.Rectangle( 0, 0, 480, 256 );
				_this.chipset_pic.copyPixels( newChip_pic, copyArea, {x:0,y:0} );
				// // // clean-up
				_this.loader.unloadClip( _this.load_mc );
				
				// draw layers  (setTile)
				drawLayers();
				
				// call external function
				_this.onLoad();		// externally-defined function
				
				
				// clean-up
				delete _this.loadData;		// In case load() called this function, then clean up after load() as well.
				delete _this.loader;
				delete drawLayers;
			}// chipset loaded()
		}// if:  linkage failed
	}// drawCompactObj()
	
	
	
	
	
	_this.load = function( fileName )
	{
		// load XML
		XML.prototype.ignoreWhite = true;
		// attempt linkage method
		_this.attachMovie( fileName, "loadMap_mc", 98 );
		if(_this.loadMap_mc)
		{// linkage
			_this.loadMap_mc.onLoad = function( data )
			{
				// copy xml data
				_this.load_xml = data;
				
				// convert XML
				_this.loadData = new Object();
				#include "readXml.as"
				readXml( _this.load_xml, _this.loadData );
				
				// pass object to drawObj()
				_this.drawObj( _this.loadData.map );
				
				// clean-up
				loadMap_mc.removeMovieClip();
			}// onLoad()
		}// if:  use linkage
		else
		{// external file
			// if fail
			_this.load_xml = new XML();
			_this.load_xml.load( fileName );
			_this.load_xml.onLoad = function( success )
			{
				#include "readXml.as"
				if( success )
				{
					// convert XML
					_this.loadData = new Object();
					readXml( _this.load_xml, _this.loadData );
					
					// pass object to drawObj()
					_this.drawObj( _this.loadData.map );
					
				}// if:  success
			}// onLoad()
		}// if:  use external file
	}// load()
	
	
	
	
	// Get & Set the map's position		(This this instead of _x and _y from now on)		(Standard scroll interface regardless of scroll method)
	_this.x = 0;
	_this.y = 0;
	
	
	
	// use scrollRect instead of _x _y
	_this.cacheAsBitmap = true;
	_this.scrollPos = new flash.geom.Point(0,0);		// Used only by this scroll function
	_this.scrollArea = new flash.geom.Rectangle( 0,0, 320,240 );
	_this.scroll = function( x, y, screenWidth, screenHeight, smoothness)
	{
		// resolve optional parameters
		if(x==undefined && SPRITES.player._x!=undefined)
			var x= SPRITES.player._x;
		if(y==undefined && SPRITES.player._y!=undefined)
			var y= SPRITES.player._y;
		var screenWidth = screenWidth || 320;
		var screenHeight = screenHeight || 240;
		var smoothness = smoothness || 1;
		//
		if(_this.scrollArea.width != screenWidth)		_this.scrollArea.width = screenWidth;
		if(_this.scrollArea.height != screenHeight)		_this.scrollArea.height = screenHeight;
		// calculate view screen center
		var halfWidth = screenWidth / 2;
		var halfHeight = screenHeight / 2;
		// get the map's pixel width
		var mapWidth = _this.width * 16;
		var mapHeight = _this.height * 16;
		
		var __x = _this.x;
		var __y = _this.y;
		
		// horz
		if( x < halfWidth)
		{
			// far-left
			var xTarg = 0;
		}
		else if( x > mapWidth-halfWidth)
		{
			// far-right
			var xTarg = -mapWidth+screenWidth;
		}
		else
		{
			// scroll
			var xTarg = -x + halfWidth;
		}
		
		
		// vert
		if( y < halfHeight)
		{
			// top
			var yTarg = 0;
		}
		else if( y > mapHeight-halfHeight)
		{
			// bottom
			var yTarg = -mapHeight+screenHeight;
		}
		else
		{
			// scroll
			var yTarg = -y + halfHeight;
		}
		
		
		// apply target position,  OR center the map if it's too small
		if( mapWidth < screenWidth )
		{// if:  map is smaller than the screen  (horz)
			// center the map
			var xDiff = screenWidth-mapWidth;
			__x = xDiff/2;
		}// if:  map is smaller than the screen  (horz)
		else
		{
			__x += (xTarg-_this.scrollPos.x) * smoothness;
		}
		
		
		if( mapHeight < screenHeight )
		{// if:  map is smaller than the screen  (horz)
			// center the map
			var yDiff = screenHeight-mapHeight;
			__y = yDiff/2;
		}// if:  map is smaller than the screen  (horz)
		else
		{
			__y += (yTarg-_this.scrollPos.y) * smoothness;
		}
		
		
		// round to nearest pixel  (prevent "floating" sprites)
		_this.x = _this.scrollPos.x = Math.floor(__x);
		_this.y = _this.scrollPos.y = Math.floor(__y);
		
		// apply a scrollRect
		_this.scrollArea.x = -_this.scrollPos.x;
		_this.scrollArea.y = -_this.scrollPos.y;
		_this.scrollRect = _this.scrollArea;
	}// scroll()
	
	
	
	
	/*
	// use _x _y to scroll
	_this.scroll = function( x, y, screenWidth, screenHeight, smoothness)
	{
		// resolve optional parameters
		if(x==undefined && SPRITES.player._x!=undefined)
			var x= SPRITES.player._x;
		if(y==undefined && SPRITES.player._y!=undefined)
			var y= SPRITES.player._y;
		var screenWidth = (screenWidth) ? screenWidth : 320;
		var screenHeight = (screenHeight) ? screenHeight : 240;
		var smoothness = (smoothness) ? smoothness : 1;
		// calculate view screen center
		var halfWidth = screenWidth / 2;
		var halfHeight = screenHeight / 2;
		// get the map's pixel width
		var mapWidth = _this.width * 16;
		var mapHeight = _this.height * 16;
		
		var __x = _this._x;
		var __y = _this._y;
		
		// horz
		if( x < halfWidth)
		{
			// far-left
			var xTarg = 0;
		}
		else if( x > mapWidth-halfWidth)
		{
			// far-right
			var xTarg = -mapWidth+screenWidth;
		}
		else
		{
			// scroll
			var xTarg = -x + halfWidth;
		}
		
		
		// vert
		if( y < halfHeight)
		{
			// top
			var yTarg = 0;
		}
		else if( y > mapHeight-halfHeight)
		{
			// bottom
			var yTarg = -mapHeight+screenHeight;
		}
		else
		{
			// scroll
			var yTarg = -y + halfHeight;
		}
		
		
		// apply target position,  OR center the map if it's too small
		if( mapWidth < screenWidth )
		{// if:  map is smaller than the screen  (horz)
			// center the map
			var xDiff = screenWidth-mapWidth;
			__x = xDiff/2;
		}// if:  map is smaller than the screen  (horz)
		else
		{
			__x += (xTarg-_this._x) * smoothness;
		}
		
		
		if( mapHeight < screenHeight )
		{// if:  map is smaller than the screen  (horz)
			// center the map
			var yDiff = screenHeight-mapHeight;
			__y = yDiff/2;
		}// if:  map is smaller than the screen  (horz)
		else
		{
			__y += (yTarg-_this._y) * smoothness;
		}
		
		
		// round to nearest pixel  (prevent "floating" sprites)
		_this.x = Math.floor(__x);
		_this.y = Math.floor(__y);
	}// scroll()
	// x
	var init_x = _this.x || _this._x;
	function get_x(){
		return _this._x;
	}
	function set_x( newValue ){
		_this._x = newValue;
	}// set()
	_this.addProperty("x", get_x, set_x);
	_this.x = init_x;
	// y
	var init_y = _this.y || _this._y;
	function get_y(){
		return _this._y;
	}
	function set_y( newValue ){
		_this._y = newValue;
	}// set()
	_this.addProperty("y", get_y, set_y);
	_this.y = init_y;
	*/
	
	
	
	
	
	// ________________________________________________________
	
	
	
	// return reference to this map
	return _this;
}// makeMap()