import {world, system} from '@minecraft/server';
    
export class PortalGun_Main{
	
	static run(){
		//////////////////////////////////
		//GET PORTAL ENTITIES WHEN SPAWNED
		//////////////////////////////////
		world.events.entitySpawn.subscribe((eventData)=>{
			let entity = eventData.entity;
			let entityId = entity.typeId;
			let nearestPlayer;
			let nearestPlayerName;
			if(entityId.includes("pg:")){
				nearestPlayer = this.getNearestPlayer(entity);
				if(!nearestPlayer) return;
				nearestPlayerName = nearestPlayer.name;
			}
			//PORTAL GUN BULLET FIRED
			if(entityId == "pg:bullet_b_glados" || entityId == "pg:bullet_o_glados" || entityId == "pg:bullet_b" || entityId == "pg:bullet_o" || entityId == "pg:bullet_rnm1" || entityId == "pg:bullet_rnm2"){							
				if(entityId.includes("bullet_b") || entityId.includes("1")){					
					entity.nameTag = `${nearestPlayerName},pg1`;
				}else if(entityId.includes("bullet_o") || entityId.includes("2")){
					entity.nameTag = `${nearestPlayerName},pg2`;
				}				
			}
			if(entityId == "pg:portal_b_glados" || entityId == "pg:portal_o_glados" || entityId == "pg:portal_b" || entityId == "pg:portal_o" || entityId == "pg:portal_rnm1" || entityId == "pg:portal_rnm2"){
				let oldPortal = this.getPortalGunBullet({tags:[`${entity.nameTag}`]});							
				if(oldPortal) oldPortal.triggerEvent("despawn");
				let rightAngle = this.getRightAngle(entity.getRotation().y);
				let centeredLocation = this.getCenteredLocation(entity.location);
				entity.teleport(centeredLocation,entity.dimension,0,rightAngle);				
				entity.addTag(entity.nameTag);
				entity.nameTag = "";				
				if(!this.hasWall(entity)){
					entity.triggerEvent("floor_portal");
					entity.addTag("floor");
				}		
			}
			if(entityId == "pg:blackhole_bullet" || entityId == "pg:blackhole_bullet2"){
				entity.nameTag = `${nearestPlayerName},bb`;
			}
			if(entityId == "pg:blackhole_entity" || entityId == "pg:blackhole_entity2"){
				let oldBlackhole = this.getPortalGunBullet({tags:[`${entity.nameTag}`]});
				if(oldBlackhole) oldBlackhole.triggerEvent("despawn");
				entity.addTag(entity.nameTag);
				entity.nameTag = "";
			}
		});
	
		//////////////////////////////////
		//PORTAL TELEPORT / PORTAL DESPAWN
		//////////////////////////////////
		system.runInterval(()=>{
			let allPortal = this.getAllPortals();
			for(let portal of allPortal){					
				if(!this.hasWall2Tall(portal) && !portal.hasTag("floor") || portal.hasTag("floor") && (!this.hasFloor(portal) || !this.hasFloorOneBlocks(portal))) portal.triggerEvent("despawn");					
				let portalTag = portal.getTags()[0];
				let portalOwnerName = portalTag.split(",")[0];				
				let nearestEntity = this.getNearestEntityLimitedDistance(portal);
				if(!nearestEntity) continue;				
				if(portalTag.includes("pg1")){					
					let oppositePortal = this.getOppositePortal({type:portal.typeId.replace("_b","_o").replace("1","2"),tags:[`${portalOwnerName},pg2`]});
					if(!oppositePortal) continue;		
					nearestEntity.removeTag(`${portalOwnerName},cube`);		
					portal.runCommandAsync(`gamerule sendcommandfeedback false`);
					portal.runCommandAsync(`tp @e[family=!portal,r=1,x=~,y=~0.5,z=~,c=1] @e[tag="${portalOwnerName},pg2",c=1]`);
					oppositePortal.runCommandAsync(`tp @e[family=!portal,r=1,x=~,y=~0.5,z=~,c=1] ^^^0.8`);
					portal.runCommandAsync(`gamerule sendcommandfeedback true`);
				}else if(portalTag.includes("pg2")){
					let oppositePortal = this.getOppositePortal({type:portal.typeId.replace("_o","_b").replace("2","1"),tags:[`${portalOwnerName},pg1`]});
					if(!oppositePortal) continue;
					nearestEntity.removeTag(`${portalOwnerName},cube`);
					portal.runCommandAsync(`gamerule sendcommandfeedback false`);
					portal.runCommandAsync(`tp @e[family=!portal,r=1,x=~,y=~0.5,z=~,c=1] @e[tag="${portalOwnerName},pg1",c=1]`);
					oppositePortal.runCommandAsync(`tp @e[family=!portal,r=1,x=~,y=~0.5,z=~,c=1] ^^^0.8`);
					portal.runCommandAsync(`gamerule sendcommandfeedback true`);
				}							
			}			
		},1);

		//////////////////////////////////
		//CUBE CARRY
		//////////////////////////////////
		system.runInterval(()=>{
			let allPlayers = world.getPlayers();
			for(let player of allPlayers){
				let cubeEntity = this.getCube({type:"pg:portal_cube",tags:[`${player.name},cube`]});
				if(!cubeEntity) continue;
				let viewDX = player.getViewDirection().x*1.5;
				let viewDY = player.getViewDirection().y*1.5;
				let viewDZ = player.getViewDirection().z*1.5;
				cubeEntity.teleport({x:player.location.x+viewDX,y:player.location.y+viewDY+1.2,z:player.location.z+viewDZ},cubeEntity.dimension,0,0,true);			
			}			
		},1);

		//////////////////////////////////
		//DESPAWNING BLACKHOLES / CARRY CUBE
		//////////////////////////////////
		world.events.beforeItemUse.subscribe((eventData)=>{
			let itemId = eventData.item.typeId;
			let player = eventData.source;
			if(itemId == "pg:portal_gun_blackhole_beta" || itemId == "pg:portal_gun_blackhole2_beta"){
				if(player.isSneaking){
					let blackhole = this.getPortalGunBullet({tags:[`${player.name},bb`]});
					if(!blackhole) return;
					blackhole.triggerEvent("despawn");
					player.onScreenDisplay.setActionBar("Blackhole Removed!");
				}
			}else if((itemId.includes("blue") || itemId.includes("orange")) && itemId.includes("pg:")){
				let entities = Array.from(player.getEntitiesFromViewDirection({maxDistance:4}));
				for(let entity of entities){
					if(!entity.typeId.includes("cube")) continue;
					if(!entity) return;
					if(entity.hasTag(`${player.name},cube`) && player.isSneaking){
						entity.removeTag(`${player.name},cube`);
						eventData.cancel = true;
						return;
					}
					if(entity.hasTag(`${player.name},cube`)){
						entity.removeTag(`${player.name},cube`);
						entity.applyImpulse({x:player.getViewDirection().x*2,y:player.getViewDirection().y*2,z:player.getViewDirection().z*2});
						eventData.cancel = true;
						return;
					}
					if(entity.typeId.includes("portal_cube")){
						entity.addTag(`${player.name},cube`);
						eventData.cancel = true;
						return;
					}	
				}		
			}
		});
	}

	static hasWall2Tall(entity){
		let rotationY = entity.getRotation().y;
		let lookingDirection = this.getRightAngle(rotationY);
		let wallVector;
		let wallVector2;
		switch(lookingDirection){
			case 0:
				wallVector = {x:entity.location.x,y:entity.location.y,z:entity.location.z-1}
				wallVector2 = {x:entity.location.x,y:entity.location.y+1,z:entity.location.z-1}
				break;
			case 90:
				wallVector = {x:entity.location.x+1,y:entity.location.y,z:entity.location.z}
				wallVector2 = {x:entity.location.x+1,y:entity.location.y+1,z:entity.location.z}
				break;
			case 180:
				wallVector = {x:entity.location.x,y:entity.location.y,z:entity.location.z+1}
				wallVector2 = {x:entity.location.x,y:entity.location.y+1,z:entity.location.z+1}
				break;
			case 270:
				wallVector = {x:entity.location.x-1,y:entity.location.y,z:entity.location.z}
				wallVector2 = {x:entity.location.x-1,y:entity.location.y+1,z:entity.location.z}
				break;
		}
		let wall = entity.dimension.getBlock(wallVector);
		let wall2 = entity.dimension.getBlock(wallVector2)
		if(!wall.typeId.includes("air") && !wall2.typeId.includes("air")) return true;
		return false;
	}

	static hasWall(entity){
		let rotationY = entity.getRotation().y;
		let lookingDirection = this.getRightAngle(rotationY);
		let wallVector;
		switch(lookingDirection){
			case 0:
				wallVector = {x:entity.location.x,y:entity.location.y,z:entity.location.z-1}
				break;
			case 90:
				wallVector = {x:entity.location.x+1,y:entity.location.y,z:entity.location.z}
				break;
			case 180:
				wallVector = {x:entity.location.x,y:entity.location.y,z:entity.location.z+1}
				break;
			case 270:
				wallVector = {x:entity.location.x-1,y:entity.location.y,z:entity.location.z}
				break;
		}
		let wall = entity.dimension.getBlock(wallVector);
		if(!wall.typeId.includes("air")) return true;
		return false;
	}
	static hasWallTwoBlocks(entity){
		let rotationY = entity.getRotation().y;
		let lookingDirection = this.getRightAngle(rotationY);
		let wallVector;
		switch(lookingDirection){
			case 0:
				wallVector = {x:entity.location.x,y:entity.location.y,z:entity.location.z-2}
				break;
			case 90:
				wallVector = {x:entity.location.x+2,y:entity.location.y,z:entity.location.z}
				break;
			case 180:
				wallVector = {x:entity.location.x,y:entity.location.y,z:entity.location.z+2}
				break;
			case 270:
				wallVector = {x:entity.location.x-2,y:entity.location.y,z:entity.location.z}
				break;
		}
		let wall = entity.dimension.getBlock(wallVector);
		if(!wall.typeId.includes("air")) return true;
		return false;
	}

	static getCenteredLocation(location){
		let newX = Math.floor(location.x)+0.5			
		let newY = Math.floor(location.y);
		if((newY+0.5)-newY>0.5){
			newY += 0.5;
		}
		let newZ = Math.floor(location.z)+0.5
		return {x:newX,y:newY,z:newZ};
	}

	static getRightAngle(rotationY){
		if(rotationY>=-180 && rotationY<=-140 || rotationY<=180 && rotationY>=140){
			return 180;
		}else if(rotationY<140 && rotationY>=40){
			return 90;
		}else if(rotationY<40 && rotationY>=-50){
			return 0;
		}else if(rotationY<-50 && rotationY<140){
			return 270;
		}
		return 0;
	}
	static hasObstruction(entity){
		let entityPos = entity.location;
		let entityDim = entity.dimension;
		let obstruction = entityDim.getBlock({x:entityPos.x,y:entityPos.y+1,z:entityPos.z});
		if(!obstruction.typeId.includes("air")) return true;
		return false;
	}

	static hasFloor(entity){
		let entityPos = entity.location;
		let entityDim = entity.dimension;
		let floor = entityDim.getBlock({x:entityPos.x,y:entityPos.y-1,z:entityPos.z});
		if(!floor.typeId.includes("air")) return true;
		return false;
	}

	static hasFloorOneBlocks(entity){
		let rotationY = entity.getRotation().y;
		let lookingDirection = this.getRightAngle(rotationY);
		let floorVector;
		switch(lookingDirection){
			case 0:
				floorVector = {x:entity.location.x,y:entity.location.y-1,z:entity.location.z-1}
				break;
			case 90:
				floorVector = {x:entity.location.x+1,y:entity.location.y-1,z:entity.location.z}
				break;
			case 180:
				floorVector = {x:entity.location.x,y:entity.location.y-1,z:entity.location.z+1}
				break;
			case 270:
				floorVector = {x:entity.location.x-1,y:entity.location.y-1,z:entity.location.z}
				break;
		}
		let floor = entity.dimension.getBlock(floorVector);
		if(!floor.typeId.includes("air")) return true;
		return false;
	}

	static haveSideBlocks(entity){
		let entityPos = entity.location;
		let entityDim = entity.dimension;
		let blockXPos = entityDim.getBlock({x:entityPos.x+1,y:entityPos.y,z:entityPos.z});
		let blockZPos = entityDim.getBlock({x:entityPos.x,y:entityPos.y,z:entityPos.z+1});
		let blockXNeg = entityDim.getBlock({x:entityPos.x-1,y:entityPos.y,z:entityPos.z});
		let blockZNeg = entityDim.getBlock({x:entityPos.x,y:entityPos.y,z:entityPos.z-1});

		let blockXPos2 = entityDim.getBlock({x:entityPos.x+1,y:entityPos.y+1,z:entityPos.z});
		let blockZPos2 = entityDim.getBlock({x:entityPos.x,y:entityPos.y+1,z:entityPos.z+1});
		let blockXNeg2 = entityDim.getBlock({x:entityPos.x-1,y:entityPos.y+1,z:entityPos.z});
		let blockZNeg2 = entityDim.getBlock({x:entityPos.x,y:entityPos.y+1,z:entityPos.z-1});

		if(!blockXPos.typeId.toLowerCase().includes("air")){
			return true;
		}
		if(!blockZPos.typeId.toLowerCase().includes("air")){
			return true;
		}
		if(!blockXNeg.typeId.toLowerCase().includes("air")){
			return true;
		}
		if(!blockZNeg.typeId.toLowerCase().includes("air")){
			return true;
		}

		if(!blockXPos2.typeId.toLowerCase().includes("air")){
			return true;
		}
		if(!blockZPos2.typeId.toLowerCase().includes("air")){
			return true;
		}
		if(!blockXNeg2.typeId.toLowerCase().includes("air")){
			return true;
		}
		if(!blockZNeg2.typeId.toLowerCase().includes("air")){
			return true;
		}
		return false;
	}

	static getNearestPlayer(portalBulletEntity){
		let portalBulletLocation = portalBulletEntity.location;
		let portalBulletDimension = portalBulletEntity.dimension;
		let nearestPlayerQuery = {closest:1,location:portalBulletLocation,families:["player"]};
		let nearestPlayer = Array.from(portalBulletDimension.getEntities(nearestPlayerQuery));
		return nearestPlayer[0];
	}

	static getNearestPlayerLimitedDistance(portalEntity){
		let portalLocation = portalEntity.location;
		let portalDimension = portalEntity.dimension;
		let nearestPlayerQuery = {closest:1,maxDistance:0.5,location:portalLocation,families:["player"]};
		let nearestPlayer = Array.from(portalDimension.getEntities(nearestPlayerQuery));
		return nearestPlayer[0];
	}

	static getNearestEntityLimitedDistance(portalEntity){
		let portalLocation = portalEntity.location;
		let portalDimension = portalEntity.dimension;
		let adjustedLocation = {x:portalLocation.x,y:portalLocation.y+0.5,z:portalLocation.z};
		let nearestEntityQuery = {closest:1,maxDistance:0.8,location:adjustedLocation,excludeFamilies:["portal","bluep","orangep"]};
		let nearestEntity = Array.from(portalDimension.getEntities(nearestEntityQuery));
		return nearestEntity[0];
	}

	static getOppositePortal(query){
		let portalEntity = Array.from(world.getDimension("overworld").getEntities(query));
		return portalEntity[0];
	}

	static getAllPortals(){
		let portalEntityQuery = {families:["portal"]};
		let portalEntities = Array.from(world.getDimension("overworld").getEntities(portalEntityQuery));
		return portalEntities;
	}

	static getPortalGunBullet(query){
		let portalBulletEntity = Array.from(world.getDimension("overworld").getEntities(query));
		return portalBulletEntity[0];
	}

	static getCube(query){
		let cubeEntity = Array.from(world.getDimension("overworld").getEntities(query));
		return cubeEntity[0];
	}
	
}