//********************************//
//     Minecraft Block Entity     //
//			 (c) McKay			  //
//********************************//

ENT.Type 			= "anim"
ENT.Base 			= "base_anim"
ENT.health			= 100
ENT.spawned 		= false

//Setup datatable hook
function ENT:SetupDataTables()
    self:DTVar( "Int", 0, "blockID" )
	self:DTVar( "Int", 1, "rotation" )
	self:DTVar( "Bool", 1, "doUpdate" )
end

//Accessor Funcs
function ENT:GetBlockID( )
    return self.dt.blockID
end

//*******************************************
//	CheckPos - check for block overlapping
//*******************************************

function ENT:CheckPos( ID )
	if (ID != 66 && ID != 67 && ID != 68 && ID != 72) then //not for torches, levers, ladders
		local bounds = 16;
		local pos = self:GetPos();
		pos.z = pos.z + 18.25; //center
		for k, v in pairs( ents.FindInBox( pos + Vector(-bounds,-bounds,-bounds), pos + Vector(bounds,bounds,bounds) ) ) do
			if ( v:IsValid() && v != self ) then
				if ( v:GetClass() == "minecraft_block") then
					if (GetConVar("minecraft_debug"):GetBool()) then print("[" ..tostring(self.dt.blockID) .. "] would overlap with ID = " .. tostring(v.dt.blockID)) end
					return false;
				end
				if ( v:GetClass() == "player") then
					if (v:GetMoveType() != MOVETYPE_NOCLIP) then
						if (GetConVar("minecraft_debug"):GetBool()) then print("player is blocking the way!") end
						return false
					else
						return true
					end
				end
			end
		end	
	end
	return true
end

//*******************************************
//	GetNearbyBlock
//*******************************************

function ENT:GetNearbyBlock( onSide )
	if ( onSide <= 0 || onSide > 6) then print("epic fail") return end
	//1 = top, 2 = bottom, 3 = front, 4 = back, 5 = left, 6 = right [when looking at a block in front of you and looking to the north!]
	
	local bounds = 15;
	local pos = self:GetPos();
	pos.z = pos.z + 18.25; //center
	if (onSide == 1) then
		pos.z = pos.z + 36.5
	end
	if (onSide == 2) then
		pos.z = pos.z - 36.5;
	end
	if (onSide == 3) then
		pos.x = pos.x + 36.5;
	end
	if (onSide == 4) then
		pos.x = pos.x - 36.5;
	end
	if (onSide == 5) then
		pos.y = pos.y - 36.5;
	end
	if (onSide == 6) then
		pos.y = pos.y + 36.5;
	end
	for k, v in pairs( ents.FindInBox( pos + Vector(-bounds,-bounds,-bounds), pos + Vector(bounds,bounds,bounds) ) ) do
		if ( v:IsValid() && v != self ) then
			if ( v:GetClass() == "minecraft_block" || v:GetClass() == "minecraft_block_waterized") then
				if (GetConVar("minecraft_debug"):GetBool()) then print("[" ..tostring(self.dt.blockID) .. "] found nearby block with ID = " .. tostring(v:GetBlockID())) end
				return v;
			end
		end
	end
	return nil
end

//*******************************************
//	Think - do something if something changes
//*******************************************

function ENT:Think( )
	if (self:GetNetworkedBool("doUpdate") == true) then
		local ID = self.dt.blockID;
		if (SERVER) then
			if (GetConVar("minecraft_debug"):GetBool()) then print("["..tostring(ID).."] update... (server)") end
		else
			if (GetConVar("minecraft_debug"):GetBool()) then print("["..tostring(ID).."] update... (client)") end
		end
	
		//intelligent grass blocks
		if (ID == 2) then
			local temp = self:GetNearbyBlock( 1 )
			if (temp != nil) then //if there is a block on top of me
				local ID2 = temp:GetBlockID()
				if (GetConVar("minecraft_debug"):GetBool()) then print("["..tostring(ID).."] detected block with ID = "..tostring(ID2).." on top") end
				//have to check for waterized blocks manually, because GetBlockID() doesn't seem to work properly
				local check = true
				if (temp:GetClass() == "minecraft_block_waterized") then
					if (temp:GetNWString("water") == "true") then
						check = false
					end
				end
				if (temp:IsValid()) then
					if (ID2 != 55 && ID2 != 59 && ID2 != 60 && ID2 != 61 && ID2 != 62 && ID2 != 63 && ID2 != 64 && ID2 != 65 && ID2 != 66 && ID2 != 67
						&& ID2 != 67 && ID2 != 68 && ID2 != 70 && ID2 != 72 && ID2 != 73 && ID2 != 74 && ID2 != 75 && ID2 != 76 && ID2 != 56 && ID2 != 17 && check) then
						self:SetModel( "models/MCModelPack/blocks/dirt.mdl" );
						self.dt.blockID = 1;
						
						if (SERVER) then //don't want this message twice
							if (GetConVar("minecraft_debug"):GetBool()) then print("grass will now turn magically into dirt") end
						end
					end
					//create snowy grass blocks if a snow layer is placed on top
					if (ID2 == 56 || ID2 == 17) then
						self:SetSkin( 1 );
					end
				end
			end
		end
		
		//TODO: add more

		self:SetNetworkedBool("doUpdate",false);
	end
end

//***********************************************
//	BlockInit - special block behaviour on spawn
//***********************************************

function ENT:BlockInit( ID , hitEntity )
	if (GetConVar("minecraft_debug"):GetBool()) then print("block spawned with ID = " .. tostring(ID)) end
	if (GetConVar("minecraft_debug"):GetBool()) then print("tracer hit " .. tostring(hitEntity:GetClass())) end
	
	//are we spawning on another block?
	local onBlock = false
	if (!hitEntity:IsWorld() && hitEntity:GetClass() == "minecraft_block") then
		onBlock = true
		if (GetConVar("minecraft_debug"):GetBool() == 2) then print("onBlock = true!") end
	end

	//get the view direction (1 = North, 2 = East, 3 = South, 4 = West)
	//I hereby declare that North is the direction you are facing in gm_construct on spawn
	local viewdir = -1
	local tr = self.Owner:GetEyeTrace()
	local hitpos = tr.HitPos - self.Owner:GetPos()
	if (GetConVar("minecraft_debug"):GetBool() == 2) then print("hitpos.x = ".. tostring(hitpos.x) .. " hitpos.y = ".. tostring(hitpos.y)) end
	local startpos = tr.StartPos - self.Owner:GetPos()
	local rotpoint = RotatePoint2D( hitpos, startpos, 45 ) //rotate the "compass rose" by 45 degrees
	local thevector = rotpoint - startpos
	if (GetConVar("minecraft_debug"):GetBool() == 2) then print("posx = " .. tostring(thevector.x)) end
	if (GetConVar("minecraft_debug"):GetBool() == 2) then print("posy = " .. tostring(thevector.y)) end
	if (thevector.x < 0 && thevector.y > 0) then
		if (GetConVar("minecraft_debug"):GetBool()) then print("player -> North") end
		viewdir = 1
	end
	if (thevector.x > 0 && thevector.y > 0) then
		if (GetConVar("minecraft_debug"):GetBool()) then print("player -> East") end
		viewdir = 2
	end
	if (thevector.x > 0 && thevector.y < 0) then
		if (GetConVar("minecraft_debug"):GetBool()) then print("player -> South") end
		viewdir = 3
	end
	if (thevector.x < 0 && thevector.y < 0) then
		if (GetConVar("minecraft_debug"):GetBool()) then print("player -> West") end
		viewdir = 4
	end
	
	//on which of the possible 6 sides of an already existing block are we spawning?
	//1 = top, 2 = bottom, 3 = front, 4 = back, 5 = left, 6 = right [when looking at a block in front of you and looking to the north!]
	local onSide = -1
	if (onBlock == true) then
		if (GetConVar("minecraft_debug"):GetBool() == 2) then print("self:GetPos() -> x = " .. tostring(self:GetPos().x) .. " ; y = " .. tostring(self:GetPos().y) .. " ; z = " .. tostring(self:GetPos().z)) end
		if (GetConVar("minecraft_debug"):GetBool() == 2) then print("hitE:GetPos() -> x = " .. tostring(hitEntity:GetPos().x) .. " ; y = " .. tostring(hitEntity:GetPos().y) .. " ; z = " .. tostring(hitEntity:GetPos().z)) end
		local selfX = self:GetPos().x;
		local selfY = self:GetPos().y;
		local selfZ = self:GetPos().z;
		local hitX = hitEntity:GetPos().x;
		local hitY = hitEntity:GetPos().y;
		local hitZ = hitEntity:GetPos().z;
		if (selfX == hitX && selfY == hitY && selfZ > hitZ) then
			onSide = 1;
			if (GetConVar("minecraft_debug"):GetBool()) then print("top") end
		end
		if (selfX == hitX && selfY == hitY && selfZ < hitZ) then
			onSide = 2;
			if (GetConVar("minecraft_debug"):GetBool()) then print("bottom") end
		end
		if (selfX > hitX && selfY == hitY) then
			onSide = 3
			if (GetConVar("minecraft_debug"):GetBool()) then print("front") end
		end
		if (selfX < hitX && selfY == hitY) then
			onSide = 4
			if (GetConVar("minecraft_debug"):GetBool()) then print("back") end
		end
		if (selfX == hitX && selfY < hitY) then
			onSide = 5
			if (GetConVar("minecraft_debug"):GetBool()) then print("left") end
		end
		if (selfX == hitX && selfY > hitY) then
			onSide = 6
			if (GetConVar("minecraft_debug"):GetBool()) then print("right") end
		end
	end

	//fix piston spawn height
	if ( ID == 44 ) then
		local pos = self:GetPos()
		pos.z = pos.z + 27.375
		self:SetPos( pos )
	end
	
	//fix wall sign spawn height
	if ( ID == 65 ) then
		local pos = self:GetPos()
		pos.z = pos.z + (18.25/2)
		self:SetPos( pos )
	end
	
	//auto rotate furnaces, dispensers, stairs, chests, pumpkins, beds, rails, portals, iron bars, glas panes to face the player on spawn
	if ( ID == 24 || ID == 23 || ID == 45 || ID == 46 || ID == 47 || ID == 77 || ID == 25 
				  || ID == 34 || ID == 78 || ID == 73 || ID == 74 || ID == 75 || ID == 76
				  || ID == 59 || ID == 60 || ID == 61) then
		self:SetAngles( Vector( 0, -90*(viewdir-1), 0 ) )
	end
	
	//auto rotate wall signs, stick to other blocks
	if ( ID == 65 ) then
		if (onBlock) then
			if (onSide == 4) then
				self:SetAngles( Vector( 0, -90*2, 0 ) )
			end
			if (onSide == 5) then
				self:SetAngles( Vector( 0, -90, 0 ) )
			end
			if (onSide == 6) then
				self:SetAngles( Vector( 0, -90*3, 0 ) )
			end
		else
			self:SetAngles( Vector( 0, -90*(viewdir-1), 0 ) )
		end
	end
	
	//auto rotate dooors, set correct position
	if (ID == 62 || ID == 63) then
		if (viewdir == 2 || viewdir == 4) then
			self:SetAngles( Vector(0 , 90 , 0) )
		end
		local pos = self:GetPos();
		//local min,max = self:WorldSpaceAABB();       doesn't work because
		//local halfwidth = math.abs(min.x - max.x)/2; the bounding box sadly is a tiny teeny bit bigger than the actual model
		//HACKHACK: I hate having to use fixed values determined by testing with convars
		local halfwidth = 3.4
		if (viewdir == 1) then
			pos.x = pos.x + 18.25 - halfwidth;
		end
		if (viewdir == 2) then
			pos.y = pos.y - 18.25 + halfwidth;
		end
		if (viewdir == 3) then
			pos.x = pos.x - 18.25 + halfwidth;
		end
		if (viewdir == 4) then
			pos.y = pos.y + 18.25 - halfwidth;
		end
		self:SetPos( pos )
	end
	
	//auto rotate signs to always face the player (like in minecraft)
	if (ID == 64) then
		local base = Vector( -1, 0, 0 ) //North vector
		local thevector = self:GetPos() - self.Owner:GetPos()
		local angle = GetAngleBetweenVectors( base, thevector )
		if (GetConVar("minecraft_debug"):GetBool()) then print("angle = " .. tostring(angle)) end
		self:SetAngles( Vector( 0, angle, 0) )
	end
	
	//rotate all 2.5d sprites 45 degrees (saplings, shrubs, sugar cane, mushrooms, flowers, grass, plants, cobweb), disable player collisions
	if (ID == 70) then
		self:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
		self:SetAngles( Vector( 0,45,0 ) )
	end
	
	//auto rotate ladders, stick to other blocks
	if (ID == 72) then
		if (onBlock) then
			if (onSide == 4) then
				self:SetAngles( Vector( 0,-90*2,0) )
			end
			if (onSide == 5) then
				self:SetAngles( Vector( 0,-90,0) )
			end
			if (onSide == 6) then
				self:SetAngles( Vector( 0,-90*3,0) )
			end
		else
			self:SetAngles( Vector( 0,-90*(viewdir-1),0) )
		end
	end
	
	//auto rotate redstone repeaters
	if (ID == 57 || ID == 58) then
		if (viewdir == 2 || viewdir == 4) then
			self:SetAngles( Vector( 0, 90*(viewdir-1), 0) )
		else
			self:SetAngles( Vector( 0, 90*(viewdir+1), 0) )
		end
	end
	
	//intelligently place torches, auto rotation and positioning ; also set special collision group
	if (ID == 66 || ID == 67) then
		self:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
		local torchAngle = 20;
		if (onSide == 3) then //front
			local pos = self:GetPos();
			pos.x = pos.x - 18.25;
			pos.z = pos.z + (18.25/2);
			self:SetPos( pos );
			self:SetAngles( Vector( torchAngle, 0, 0) )
		end
		if (onSide == 4) then //back
			local pos = self:GetPos();
			pos.x = pos.x + 18.25;
			pos.z = pos.z + (18.25/2);
			self:SetPos( pos );
			self:SetAngles( Vector( -torchAngle, 0, 0) )
		end
		if (onSide == 5) then //left
			local pos = self:GetPos();
			pos.y = pos.y + 18.25;
			pos.z = pos.z + (18.25/2);
			self:SetPos( pos );
			self:SetAngles( Vector( -torchAngle, 90, 0) )
		end
		if (onSide == 6) then //right
			local pos = self:GetPos();
			pos.y = pos.y - 18.25;
			pos.z = pos.z + (18.25/2);
			self:SetPos( pos );
			self:SetAngles( Vector( torchAngle, 90, 0) )
		end
	end
	
	//and the same with levers
	if (ID == 68) then
		self:SetCollisionGroup( COLLISION_GROUP_DEBRIS_TRIGGER );
		local leverAngle = 90;
		if (onSide == 3) then //front
			local pos = self:GetPos();
			pos.x = pos.x - 18.25;
			pos.z = pos.z + 18.25;
			self:SetPos( pos );
			self:SetAngles( Vector( leverAngle, 0, 0) )
		end
		if (onSide == 4) then //back
			local pos = self:GetPos();
			pos.x = pos.x + 18.25;
			pos.z = pos.z + 18.25;
			self:SetPos( pos );
			self:SetAngles( Vector( -leverAngle, 0, 0) )
		end
		if (onSide == 5) then //left
			local pos = self:GetPos();
			pos.y = pos.y + 18.25;
			pos.z = pos.z + 18.25;
			self:SetPos( pos );
			self:SetAngles( Vector( -leverAngle, 90, 0) )
		end
		if (onSide == 6) then //right
			local pos = self:GetPos();
			pos.y = pos.y - 18.25;
			pos.z = pos.z + 18.25;
			self:SetPos( pos );
			self:SetAngles( Vector( leverAngle, 90, 0) )
		end
	end	
	
	//tnt blocks
	if (ID == 39) then
		local ent = ents.Create( "mc_tnt" )
		ent:SetPos( self:GetPos() )
		ent:SetKeyValue( "DisableShadows", "1" )
		ent:SetKeyValue( "targetname", "mcblock" )
		ent:Spawn()
		local phys = ent:GetPhysicsObject()
		if (phys:IsValid()) then
			phys:EnableMotion( false ) //freeze the block
			phys:Wake()
		end
		self:Remove()
	end
	
	//cake 
	if (ID == 48) then
		local ent = ents.Create( "mc_cake" )
		ent:SetPos( self:GetPos() )
		ent:SetKeyValue( "DisableShadows", "1" )
		ent:SetKeyValue( "targetname", "mcblock" )
		ent:Spawn()
		local phys = ent:GetPhysicsObject()
		if (phys:IsValid()) then
			phys:EnableMotion( false )
			phys:Wake()
		end
		self:Remove()	
	end
	
	//auto rotation of glass panes, iron bars, portals if they are spawning touching already existing ones
	if (ID == 59 || ID == 60 || ID == 61) then
		if (onBlock) then
			if (hitEntity.dt.blockID == ID) then
				self:SetAngles( hitEntity:GetAngles() );
			end
		else
			local t1 = self:GetNearbyBlock(1);
			local t2 = self:GetNearbyBlock(2);
			local t3 = self:GetNearbyBlock(3);
			local t4 = self:GetNearbyBlock(4);
			local t5 = self:GetNearbyBlock(5);
			local t6 = self:GetNearbyBlock(6);
			if (t1 != nil) then
				if (t1.dt.blockID == ID) then
					self:SetAngles( t1:GetAngles() )
				end
			end
			if (t2 != nil) then
				if (t2.dt.blockID == ID) then
					self:SetAngles( t2:GetAngles() )
				end
			end
			if (t3 != nil) then
				if (t3.dt.blockID == ID) then
					self:SetAngles( t3:GetAngles() )
				end
			end
			if (t4 != nil) then
				if (t4.dt.blockID == ID) then
					self:SetAngles( t4:GetAngles() )
				end
			end
			if (t5 != nil) then
				if (t5.dt.blockID == ID) then
					self:SetAngles( t5:GetAngles() )
				end
			end
			if (t6 != nil) then
				if (t6.dt.blockID == ID) then
					self:SetAngles( t6:GetAngles() )
				end
			end
		end
	end
	
	//TODO: add more
end

//*****************************************************************
//	PhysicsCollide - what happens when something touches a block
//*****************************************************************

function ENT:PhysicsCollide( data, physobj )
	if (data.HitEntity:IsWorld() || data.HitEntity:GetClass() == "minecraft_block" || data.HitEntity:GetClass() == "minecraft_block_waterized") then return end
	
	//when running into cactus blocks, deal damage!
	if (self.dt.blockID == 32) then
		data.HitEntity:TakeDamage( 15, data.HitEntity, self )
	end
	
	//TODO: add moar
end

//***************************************
//	Random helper functions
//***************************************

function RotatePoint2D( toRotate, Anchor, Angle )
	local radians = -Angle*(math.pi/180); //convert degrees to radians
	local xdiff = toRotate.x-Anchor.x;
	local ydiff = toRotate.y-Anchor.y;                                                                        
	return Vector(math.cos(radians)*xdiff-math.sin(radians)*ydiff+Anchor.x,math.sin(radians)*xdiff+math.cos(radians)*ydiff+Anchor.y,0);
end

function GetAngleBetweenVectors( vector1, vector2 )
	vector1.z = 0;
	vector2.z = 0;
	local temp1 = vector1:Dot( vector2 );
	local temp2 = vector1:Length() * vector2:Length();
	local radians = math.acos(temp1 / temp2);
	local final = -radians/(math.pi/180);
	local angle = math.abs(math.atan2(vector2.y,vector2.x) - math.atan2(vector1.y,vector1.x));
	if (angle > math.pi) then
		final = -final;
	end
	return final;
end