//Copyright (C) Ben Parbury - All Rights Reserved. Unauthorized copying of this file, via any medium is strictly prohibited. Written by Ben Parbury <ben@gridlockinteractive.com> 2014
var gBlockDeleteTime = 200;

EBlockSubType =
{
	Normal : "normal",
	Domino : "domino",
	TrominoI : "trominoI",
	TrominoL : "trominoL",
	TetrominoI : "tetrominoI",
	TetrominoZ : "tetrominoZ",
	TetrominoS : "tetrominoS",
	TetrominoT : "tetrominoT",
	TetrominoL : "tetrominoL",
	TetrominoJ : "tetrominoJ",
	Big : "big",
	Giga : "giga"
}

function Block()
{
	this.type = EBlockType.Normal;
	this.subType = EBlockSubType.Normal;
	this.rotation = 0;
	this.value = 0;
	this.x = 0;
	this.y = 0;
	
	this.dx = 0;
	this.dy = 0;
	
	this.px = 0;	//location of block last frame
	this.py = 0;
	this.palpha = 1;
	this.redraw = true;
	
	this.ox = 0;	//location to draw overlay
	this.oy = 0;

	//special blocks
	this.switchPower = false;
	this.connected = false;
	this.fading = false;
	this.strength = true;
	
	this.moveObject = null;
	
	this.tween = null;

	this.deleting = false;
	this.deleteTime = 0;
	this.deletingId = undefined;
	this.startDeletingTime = undefined;
	
	this.subBlocks = new Array();
	this.adjacentOffsets = new Array();
	
	this.deletedSound = gSounds.Get("deleted");
	this.slideStop = gSounds.Get("slideStop");
	this.playSlideStop = 0;

	this.Init = function(value, subType, rotation, x, y, switchPower, connected, fading)
	{
		gConsole.Assert(value > 0);
		gConsole.Assert(rotation >= 0 && rotation < 4);

		this.value = value;
		this.rotation = rotation;

		this.x = x;
		this.y = y;

		this.switchPower = switchPower;
		this.connected = connected;
		this.fading = fading;
		
		this.SetSubType(subType);	//should be before BuildAdjacementOffsets
		this.InitRotation();			//should be before BuildAdjacementOffsets
		this.InitOverlayLocation();	//should be after InitRotation and SetSubType
		this.BuildAdjacementOffsets();
		gBlockRenderer.BuildArcCommands(this);
		gBlockRenderer.BuildPreRender(this);
		
		
		if(this.fading)
		{
			this.deletingSound = gSounds.Get("deleting");
		}
		this.slideLoopSound = this.switchPower ? gSounds.Get("switchSlideLoop") : gSounds.Get("slideLoop");
	}
	
	this.SetSubType = function(subType)
	{
		switch(subType)
		{
			case EBlockSubType.Big:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(1, 0));
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(1, 1));
				break;
			}
			case EBlockSubType.Giga:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(1, 0));
				this.subBlocks.push(new Vec2(2, 0));
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(1, 1));
				this.subBlocks.push(new Vec2(2, 1));
				this.subBlocks.push(new Vec2(0, 2));
				this.subBlocks.push(new Vec2(1, 2));
				this.subBlocks.push(new Vec2(2, 2));
				break;
			}
			case EBlockSubType.Domino:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(0, 1));
				break;
			}
			case EBlockSubType.TrominoI:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(0, 2));
				break;
			}
			case EBlockSubType.TrominoL:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(1, 1));
				break;
			}
			case EBlockSubType.TetrominoI:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(0, 2));
				this.subBlocks.push(new Vec2(0, 3));
				break;
			}
			case EBlockSubType.TetrominoZ:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(1, 0));
				this.subBlocks.push(new Vec2(1, 1));
				this.subBlocks.push(new Vec2(2, 1));
				break;
			}
			case EBlockSubType.TetrominoS:
			{
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(1, 1));
				this.subBlocks.push(new Vec2(1, 0));
				this.subBlocks.push(new Vec2(2, 0));
				break;
			}
			case EBlockSubType.TetrominoT:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(1, 0));
				this.subBlocks.push(new Vec2(1, 1));
				this.subBlocks.push(new Vec2(2, 0));
				break;
			}
			case EBlockSubType.TetrominoL:
			{
				this.subBlocks.push(new Vec2(0, 0));
				this.subBlocks.push(new Vec2(0, 1));
				this.subBlocks.push(new Vec2(0, 2));
				this.subBlocks.push(new Vec2(1, 2));
				break;
			}
			case EBlockSubType.TetrominoJ:
			{
				this.subBlocks.push(new Vec2(1, 0));
				this.subBlocks.push(new Vec2(1, 1));
				this.subBlocks.push(new Vec2(1, 2));
				this.subBlocks.push(new Vec2(0, 2));
				break;
			}
			case EBlockSubType.Normal:
			default: //intentional
			{
				this.subBlocks.push(new Vec2(0, 0));
				break;
			}
		}
		
		this.subType = subType;
	}
	
	this.InitOverlayLocation = function()
	{
		var even = (this.subBlocks.length % 2) == 0;
		var x = 0;
		var y = 0;
		if(!even)
		{
			var index = Math.round(this.subBlocks.length/2) - 1;
			x += this.subBlocks[index].x * kTileSize;
			y += this.subBlocks[index].y * kTileSize;
		}
		else
		{
			var index = Math.round(this.subBlocks.length/2);
			x += ((this.subBlocks[index].x * kTileSize) + (this.subBlocks[index - 1].x * kTileSize))/2;
			y += ((this.subBlocks[index].y * kTileSize) + (this.subBlocks[index - 1].y * kTileSize))/2;
		}
		
		this.ox = x;
		this.oy = y;
	}
	
	this.InitRotation = function()
	{
		if(this.rotation != 0)
		{
			var angle = this.rotation * (Math.PI/2);
			
			var xShift = 0;
			var yShift = 0;
			
			for(var i = 0; i < this.subBlocks.length; i++)
			{
				var subBlock = this.subBlocks[i];
				var prevX = subBlock.x;
				var prevY = subBlock.y;
				subBlock.x = Math.round((prevX * Math.cos(angle)) - (prevY * Math.sin(angle)));
				subBlock.y = Math.round((prevX * Math.sin(angle)) + (prevY * Math.cos(angle)));
				xShift = Math.min(subBlock.x, xShift);
				yShift = Math.min(subBlock.y, yShift);
			}

			xShift = xShift >= 0 ? 0 : xShift;
			yShift = yShift >= 0 ? 0 : yShift;

			for(var i = 0; i < this.subBlocks.length; i++)
			{
				var subBlock = this.subBlocks[i];
				subBlock.x -= xShift;
				subBlock.y -= yShift;
			}
		}
	}
	
	this.BuildAdjacementOffsets = function()
	{
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			this.BuildAdjacementOffset(subBlock, 0, -1);
			this.BuildAdjacementOffset(subBlock, 1, 0);
			this.BuildAdjacementOffset(subBlock, 0, 1);
			this.BuildAdjacementOffset(subBlock, -1, 0);
		}
	}
	
	this.BuildAdjacementOffset = function(currentSubBlock, dx, dy)
	{
		var x = currentSubBlock.x + dx;
		var y = currentSubBlock.y + dy;

		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			if(subBlock.x == x && subBlock.y == y)
			{
				return;
			}
		}
		
		this.adjacentOffsets.push(new Vec2(x, y));
	}
	
	this.HasSubBlockAt = function(x, y)
	{
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			if(subBlock.x == x && subBlock.y == y)
			{
				return true; 
			}
		}
		
		return false;
	}
	
	this.GetX = function()
	{
		return Math.round(this.x);
	}
	
	this.GetY = function()
	{
		return Math.round(this.y);
	}
	
	this.SetPosition = function(x, y)
	{
		this.x = x;
		this.y = y;
	}
	
	this.LoadToPosition = function(x, y)
	{
		this.tween = null;
		this.SetPosition(x, y);
		this.dx = 0;
		this.dy = 0;
	}
	
	this.TweenComplete = function()
	{
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);
		
		this.dx = 0;
		this.dy = 0;

		this.Check();
		this.tween = null;
		this.moveObject = null;
	}
	
	this.Update = function()
	{
		if(this.tween)
		{
			this.tween.Update();
			if(!this.slideLoopSound.IsPlaying())
			{
				this.slideLoopSound.Play();
			}
			this.playSlideStop = 2;
		}
		else
		{
			if(this.playSlideStop)
			{
				this.playSlideStop--;
			}
			if(this.slideLoopSound.IsPlaying())
			{
				this.slideLoopSound.Stop();
			}
		}
		
		var tile = gTileSystem.GetTileAt(this.GetX(), this.GetY());
		if(tile && tile.Activate)
		{
			tile.Activate(this);
		}
		
		if(this.deleting && !gUndoSystem.IsUndoing())
		{
			this.deleteTime++;
			if(this.deleteTime >= gBlockDeleteTime)
			{
				gParticleSystem.Spawn("DeleteBlockParticle", this);
				return true;
			}
		}
		
		return false;
	}
	
	this.DrawClear = function()
	{
		gBlockRenderer.DrawClear(this);
	}
	
	this.Draw = function()
	{
		gBlockRenderer.Draw(this);
	}
	
	this.DrawOverlay = function()
	{
		gBlockRenderer.DrawOverlay(this);
	}
	
	this.GetBlockFillStyle = function()
	{
		return gStyles.GetBlockFillStyle(this);
	}
	
	this.GetBlockOutlineStrokeStyle = function()
	{
		return gStyles.GetBlockOutlineStrokeStyle(this);
	}

	this.GetBlockStrokeStyle = function()
	{
		return gStyles.GetBlockStrokeStyle(this);
	}
	
	this.GetBlockRounding = function()
	{
		return gStyles.GetBlockRounding(this);
	}
	
	this.GetBlockWidth = function()
	{
		var max = 0;
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			max = Math.max(this.subBlocks[i].x, max);
		}
		
		return (max + 1) * kTileSize;
	}
	
	this.GetBlockHeight = function()
	{
		var max = 0;
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			max = Math.max(this.subBlocks[i].y, max);
		}
		
		return (max + 1) * kTileSize;
	}
	
	this.GetAlphaFraction = function()
	{
		if(this.fading && this.deleteTime > 0)
		{
			var fraction = (this.deleteTime/gBlockDeleteTime);
			var perceivable = 0.05;
			var alpha = Math.cos((fraction * Math.PI * 0.5) + Math.PI) + 1 - perceivable;
			alpha = Math.clamp(alpha, 0, 1 - perceivable);
			return alpha;
		}
		else
		{
			return 0;
		}
	}
	
	this.GetTextFillStyle = function()
	{
		var fraction = this.GetAlphaFraction();
		var a = 1 - fraction;
		
		return "rgba(255, 255, 255, " + a + ")";
	}

	this.GetTextShadowFillStyle = function()
	{
		var fraction = this.GetAlphaFraction();
		var a = 1 - fraction;
		
		return "rgba(0, 0, 0, " + a + ")";
	}
	
	this.CanMoveSwitchOverlapCheck = function(dx, dy, switchingBlock)
	{
		var sbdx = -dx;
		var sbdy = -dy;
		
		var length = this.subBlocks.length;
		for(var i = 0; i < length; i++)
		{
			var subBlock = this.subBlocks[i];
			var sbLength = switchingBlock.subBlocks.length;
			for(var j = 0; j < sbLength; j++)
			{
				sbSubBlock = switchingBlock.subBlocks[j];
				
				if(this.x + subBlock.x + dx == switchingBlock.x + sbSubBlock.x + sbdx && this.y + subBlock.y + dy == switchingBlock.y + sbSubBlock.y + sbdy)
				{
					return true;
				}
			}
		}
		
		return false;
	}
	
	this.CanMove = function(object, dx, dy)
	{
		if(this.switchPower)
		{
			dx = -dx;
			dy = -dy;
			if(object && object instanceof Block)
			{
				if(this.CanMoveSwitchOverlapCheck(dx, dy, object))
				{
					return false;
				}
			}
		}
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			if(!this.CanMoveInternal(object, this.x + subBlock.x + dx, this.y + subBlock.y + dy, dx, dy))
			{
				if(this.playSlideStop)
				{
					this.slideStop.Play();
					this.playSlideStop = 0;
				}
				return false;
			}
		}
		
		return true;
	}
	
	this.CanMoveInternal = function(object, x, y, dx, dy)
	{
		var tile = gTileSystem.GetTileAt(x, y);
		if(tile && tile.CanMoveBlockTo())
		{
			var block = gBlockSystem.GetBlockAt(x, y)
			if(this.compare(block))	//Sometime we find ourselves, when there is already a block in the way
			{
				block = gBlockSystem.GetBlockOn(x, y)	
			}
			if(!block || this.compare(block) || (object && object.compare && object.compare(block)) || (block.dx == dx && block.dy == dy) || (object && object.hasOwnProperty('strength') && object.strength && block.CanMove(this, dx, dy)))
			{
				var player = gPlayerSystem.GetPlayerAt(x, y)
				if(!player || this.switchPower)
				{
					//Stop player moving inside polyswitch blocks
					if(this.switchPower && this.compare(block) && object instanceof Player)
					{
						var subBlockLength = this.subBlocks.length;
						if(subBlockLength > 1)
						{
							var targetX = object.x + object.tx;
							var targetY = object.y + object.ty;
							if(targetX == x && targetY == y)
							{
								return false;
							}
						}
						return true;
					}
					return true;
				}
			}
		}

		return false;
	}
	
	this.Move = function(object, dx, dy)
	{
		if(this.switchPower)
		{
			dx = -dx;
			dy = -dy;
		}
		
		this.moveObject = object;

		this.dx = dx;
		this.dy = dy;
		if(this.type == EBlockType.Sliding)
		{
			gUndoSystem.AddRecord(new UndoBlockSlide(this, this.x, this.y, this.moveObject));
		}
		else
		{
			gUndoSystem.AddRecord(new UndoBlockMovement(this, this.x, this.y));
		}
		this.tween = new Tween(this, this.x, this.y, this.GetX() + dx, this.GetY() + dy, Math.max(gTweenSteps, 1));
		
		if(object && object.hasOwnProperty('strength') && object.strength)
		{
			for(var i = 0; i < this.subBlocks.length; i++)
			{
				var subBlock = this.subBlocks[i];
				var block = gBlockSystem.GetBlockAt(this.x + subBlock.x + dx, this.y + subBlock.y + dy);
				if(block && block != this && block.tween == null)
				{
					block.Move(this, dx, dy);
				}
			}
		}
		if(this.connected)
		{
			for(var i = 0; i < gBlockSystem.blocks.length; i++)
			{
				var block = gBlockSystem.blocks[i];
				if(this.value == block.value && !block.tween && block.connected)
				{
					if(block.CanMove(null, dx, dy))
					{
						block.Move(null, dx, dy);
					}
				}
			}
		}
	}
	
	this.CheckAdjacent = function(blockArray, originalValue)
	{
		var adjArray = this.GetAdjacentSpaces();
		blockArray.push_unique(this);
		for(var i = 0; i < adjArray.length; i++)
		{
			var space = adjArray[i];
			var block = gBlockSystem.GetBlockAt(space.x, space.y);
			if(block)
			{
				if(block.value == originalValue || block.value == 1)
				{
					if(blockArray.push_unique(block))
					{
						block.CheckAdjacent(blockArray, originalValue);
					}
				}
			}
		}
	}
	
	this.CheckAdjacentWildcard = function(blockArray)
	{
		var adjArray = this.GetAdjacentSpaces();
		for(var i = 0; i < adjArray.length; i++)
		{
			var space = adjArray[i];
			var block = gBlockSystem.GetBlockAt(space.x, space.y);
			if(block)
			{
				if(block.value == this.value || block.value == 1 || this.value == 1)
				{
					if(blockArray.push_unique(block))
					{
						block.CheckAdjacentWildcard(blockArray);
					}
				}
			}
		}
	}
	
	this.CheckWildcardDeleting = function()
	{
		gConsole.Assert(this.IsDeleting());
		
		var adjArray = this.GetAdjacentSpaces();
		for(var i = 0; i < adjArray.length; i++)
		{
			var space = adjArray[i];
			var block = gBlockSystem.GetBlockAt(space.x, space.y);
			if(block)
			{
				var deletingId = this.AllocateOrGetDeletingId();
				gConsole.Assert(deletingId != undefined);
				block.SetDeleting(deletingId);
			}
		}
	}

	this.GetAdjacentSpaces = function()
	{	
		var array = new Array();

		for(var i = 0; i < this.adjacentOffsets.length; i++)
		{
			var offset = this.adjacentOffsets[i];
			array.push(new Vec2(this.GetX() + offset.x, this.GetY() + offset.y));
		}

		return array;
	}
	
	this.IsBlockAt = function(x, y)
	{
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			if(this.IsSubBlockAt(subBlock, x, y))
			{
				return true;
			}
		}
		
		return false;
	}
	
	this.IsSubBlockAt = function(subBlock, x, y)
	{
		var roundedX = this.GetX();
		var roundedY = this.GetY();
		var delta = Math.abs(roundedX - this.x) + Math.abs(roundedY - this.y);
		var subBlockX = roundedX + subBlock.x;
		var subBlockY = roundedY + subBlock.y;
		if(delta > 0.5)
		{
			return (subBlockX + this.dx == x && subBlockY + this.dy == y);
		}
		else
		{
			return (subBlockX == x && subBlockY == y)
		}
	}

	this.IsBlockMovingTo = function(x, y)
	{
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			if(this.IsSubBlockMovingTo(subBlock, x, y))
			{
				return true;
			}
		}
		
		return false;
	}
	
	this.IsSubBlockMovingTo = function(subBlock, x, y)
	{
		var roundedX = this.GetX();
		var roundedY = this.GetY();
		var subBlockX = roundedX + subBlock.x;
		var subBlockY = roundedY + subBlock.y;
		return (subBlockX + this.dx == x && subBlockY + this.dy == y);
	}
	
	this.IsBlockOn = function(x, y)
	{
		for(var i = 0; i < this.subBlocks.length; i++)
		{
			var subBlock = this.subBlocks[i];
			if(this.IsSubBlockOn(subBlock, x, y))
			{
				return true;
			}
		}
		
		return false;
	}
	
	this.IsSubBlockOn = function(subBlock, x, y)
	{
		var subBlockX = Math.round(this.x) + subBlock.x;
		var subBlockY = Math.round(this.y) + subBlock.y;
		return (subBlockX == x && subBlockY == y);
	}
	
	this.Check = function()
	{
		if(this.value != 1)
		{
			this.CheckAndMarkDeleting();
		}
		else
		{
			var blockArray = new Array();
			this.CheckAdjacentWildcard(blockArray);
			for(var i = 0; i < blockArray.length; i++)
			{
				var block = blockArray[i];
				if(block.value != 1)
				{
					block.Check();
				}
			}
			if(this.IsDeleting())
			{
				this.CheckAndMarkDeleting();
			}
		}
	}
	
	this.CheckAndMarkDeleting = function()
	{
		var blockArray = new Array();
		this.CheckAdjacent(blockArray, this.value);
		var shouldDelete = this.CheckShouldDelete(blockArray);
		
		if(shouldDelete)
		{
			var deletingId = this.AllocateOrGetDeletingId(blockArray);
			gConsole.Assert(deletingId != undefined);
			
			this.SetDeleting(deletingId);
			for(var i = 0; i < blockArray.length; i++)
			{
				blockArray[i].SetDeleting(deletingId);
			}
			
			gConsole.Assert(this.IsDeleting());
			gConsole.Assert(this.deletingId != undefined);
		}
	}
	
	this.CheckShouldDelete = function(blockArray)
	{
		if((blockArray.length >= this.value) || this.IsDeleting())
		{
			return true;
		}
		else
		{
			for(var i = 0; i < blockArray.length; i++)
			{
				if(blockArray[i].IsDeleting())
				{
					return true;
				}
			}
		}
		
		return false;
	}
	
	this.AllocateOrGetDeletingId = function(blockArray)
	{
		//We have a deletingId, use it
		if(this.IsDeleting())
		{
			return this.deletingId;
		}
		else
		{
			//We were pushed into blocks that were already deleting
			for(var i = 0; i < blockArray.length; i++)
			{
				if(blockArray[i].deletingId != undefined)
				{
					return blockArray[i].deletingId;
				}
			}
		}
		
		//allocate new id
		return gBlockSystem.AllocateDeletingId();
	}
	
	this.SetDeleting = function(deletingId)
	{
		if(!this.deleting)
		{
			if(this.fading)
			{
				this.deletingSound.Play();
			}
			this.deleting = true;
			this.startDeletingTime = gLevelSystem.time;
			gConsole.Assert(deletingId != undefined);
			this.deletingId = deletingId;
			gConsole.Assert(this.deleteTime == 0);
			if(!this.fading)
			{
				this.deleteTime = gBlockDeleteTime;
			}
			gBlockSystem.MarkedAsDeleting(this);
			gStatsSystem.UpdateStat(EStatsType.DeletedBlocks, 1);
			if(this.value == 1)
			{
				this.CheckWildcardDeleting();
			}
		}
	}
	
	this.IsDeleting = function()
	{
		return this.deleting;
	}
	
	this.ScoredAndDeleted = function()
	{
		this.deletedSound.Play();
		this.slideLoopSound.Stop();
	}
	
	this.compare = function(block)
	{
		return block && block.x == this.x && block.y == this.y && block.value == this.value && block.type == this.type;
	}
	
	this.Debug = function()
	{
		var x = (this.x * kTileSize);
		var y = (this.y * kTileSize);
		if(this.dx + this.dy != 0)
		{
			gDraw.WatchAt(x, y, "Delta " + this.dx + ", " + this.dy);
			y += 10;
		}
		if(this.deleting)
		{
			gDraw.WatchAt(x, y, "Deleting " + this.deleteTime);
			y += 10;
		}
		if(this.startDeletingTime)
		{
			gDraw.WatchAt(x, y, "StartTime " + this.startDeletingTime);
			y += 10;
		}
	}
}

function SlidingBlock()
{
	this.type = EBlockType.Sliding;
	this.strength = true;
	
	this.__proto__ = new Block;
	
	this.TweenComplete = function()
	{
		this.x = Math.round(this.x);
		this.y = Math.round(this.y);

		this.dx = 0;
		this.dy = 0;

		this.Check();

		var dx = this.tween.dx;
		var dy = this.tween.dy;
		if(this.switchPower)
		{
			dx = -dx;
			dy = -dy;
		}
		if(this.CanMove(null, dx, dy))
		{
			this.Move(null, dx, dy);
		}
		else
		{
			this.tween = null;
		}
	}
}

function StaticBlock()
{
	this.type = EBlockType.Static;
	
	this.__proto__ = new Block;
	
	this.CanMove = function(object, dx, dy)
	{
		return false;
	}
	
}

