 import { BlockPermutation, MinecraftDimensionTypes, system, world } from '@minecraft/server';  
import { ModalFormData, ActionFormData } from '@minecraft/server-ui';
import Area_Names from "../Area_Names.js"

let waypoints = []
let distanceDivide = 128

system.runTimeout(()=>{
    //Initialize Waypoints
    if(world.getDynamicProperty("waypoints") != undefined){
        waypoints = JSON.parse(world.getDynamicProperty("waypoints"))
    }       
    world.sendMessage(`§e[Simple Waystone] Addon Active`)
}, 60)

world.afterEvents.playerJoin.subscribe(({playerName})=>{
    const dimensions = [MinecraftDimensionTypes.overworld, MinecraftDimensionTypes.nether, MinecraftDimensionTypes.theEnd]
    for(let dimension of dimensions){
        let player = world.getDimension(dimension).getEntities({name:playerName})[0]
        if(player == undefined) continue
        system.runTimeout(()=>{
            player.runCommandAsync(`inputpermission set @s camera enabled`)  
            player.runCommandAsync(`inputpermission set @s movement enabled`)
        },20)   
    }    
})

/*
*   WAYPOINT BROKEN
*/
world.afterEvents.playerBreakBlock.subscribe((eventData)=>{
    const {brokenBlockPermutation, block, player, dimension} = eventData
    const {location} = block 
    let blockName = brokenBlockPermutation.type.id
    if(!blockName.includes("sw:waystone")) return
    if(blockName.includes("bot")){
        dimension.getBlock(location).setPermutation(BlockPermutation.resolve("minecraft:air"))
        dimension.getBlock({x:location.x,y:location.y+1,z:location.z}).setPermutation(BlockPermutation.resolve("minecraft:air"))
    }else{
        dimension.getBlock(location).setPermutation(BlockPermutation.resolve("minecraft:air"))
        dimension.getBlock({x:location.x,y:location.y-1,z:location.z}).setPermutation(BlockPermutation.resolve("minecraft:air"))
    }
    let newLocation = {x:location.x,y:location.y,z:location.z}
    let index
    if(blockName.includes("bot")){
        index = waypoints.findIndex((elements) => {
            const x = elements.locationBot.x
            const y = elements.locationBot.y
            const z = elements.locationBot.z
            const newX = newLocation.x
            const newY = newLocation.y
            const newZ = newLocation.z
            if(x == newX && y == newY && z == newZ) return true
            return false
        })
    }else{
        index = waypoints.findIndex((elements) => {
            const x = elements.locationTop.x
            const y = elements.locationTop.y
            const z = elements.locationTop.z
            const newX = newLocation.x
            const newY = newLocation.y
            const newZ = newLocation.z
            if(x == newX && y == newY && z == newZ) return true
            return false
        })
    }
    if(index == -1) return
    waypoints.splice(index,1)
    let waypointString = JSON.stringify(waypoints)
    world.setDynamicProperty("waypoints", waypointString)
    waypoints = JSON.parse(world.getDynamicProperty("waypoints"))
})

system.runInterval(()=>{
    const allPlayers = world.getPlayers({tags:["open_waystone"]})
    for(let player of allPlayers){
        //world.sendMessage(player.name)
        //world.sendMessage(`${Area_Names.generateName()}`)
        player.removeTag("open_waystone")
        let waypointRaycast = player.getBlockFromViewDirection({maxDistance:16})
        if(!waypointRaycast) continue
        let waypointBlock = waypointRaycast.block      
        let waypointLocation = waypointBlock.location
        let waypointBlockName = waypointBlock.typeId
        if(!waypointBlockName.includes("sw:waystone")) continue
        
        for(let wp of waypoints){
            let oldLocation         
            let newLocation = {x:waypointLocation.x,y:waypointLocation.y,z:waypointLocation.z}
            if(waypointBlockName.includes("bot")){
                oldLocation = {x:wp.locationBot.x,y:wp.locationBot.y,z:wp.locationBot.z}               
            }else{
                oldLocation = {x:wp.locationTop.x,y:wp.locationTop.y,z:wp.locationTop.z}         
            }  

            /*
            * SHOW WAYPOINT LIST
            */
            if(JSON.stringify(oldLocation) == JSON.stringify(newLocation)){               
                let waypointsRegistered = []
                let registered = false
                if(player.getDynamicProperty("waypoints") != undefined){
                    waypointsRegistered = JSON.parse(player.getDynamicProperty("waypoints"))
                }else{
                    player.setDynamicProperty("waypoints","[]")
                    waypointsRegistered = JSON.parse(player.getDynamicProperty("waypoints"))
                }
                for(let wpRegistered of waypointsRegistered){
                    if(JSON.stringify(wpRegistered.id).includes(JSON.stringify(wp.id))){
                        registered = true
                        break
                    }
                }
                if(!registered){
                    waypointsRegistered.push(wp)
                    let propertyString = JSON.stringify(waypointsRegistered)
                    try{
                        player.setDynamicProperty("waypoints",propertyString)
                    }catch(e){
                        player.sendMessage(`§cMaximum waystone reached! (Current Waystones:${waypoints.length})`)
                        waypointsRegistered.splice(waypointsRegistered.length-1,1)
                        return
                    }
                    player.sendMessage("§eWaypoint Registered!")
                    player.playSound("random.levelup")
                }
                
                /*
                * VALIDATE WAYPOINTS
                */
               let wpRegisteredTemp = []
               for(let wp2 of waypoints){
                    for(let wpRegistered of waypointsRegistered){
                        if(JSON.stringify(wp2.id) == (JSON.stringify(wpRegistered.id))){
                            wpRegisteredTemp.push(wpRegistered)
                        }
                    }
               }
               let propertyString = JSON.stringify(wpRegisteredTemp)
               player.setDynamicProperty("waypoints",propertyString)
               waypointsRegistered = JSON.parse(player.getDynamicProperty("waypoints"))

               /*
                * SHOW WAYPOINT LIST FORM
                */
                system.runTimeout(()=>{
                    const waypointList = new ActionFormData()
                    waypointList.title("Waypoint List")
                    waypointList.body(`Current Waypoint: ${wp.name}`)
                    let xpNeeded = []
                    for(let wpRegistered of waypointsRegistered){
                        if(JSON.stringify(wp.id).includes(JSON.stringify(wpRegistered.id))){
                            if(wpRegistered.dimension.includes("overworld")){
                                waypointList.button(`§e§l>> §r${wpRegistered.name} §l§e<<\nYour Here`)
                            }else if(wpRegistered.dimension.includes("nether")){
                                waypointList.button(`§e§l>> §r§4${wpRegistered.name} §l§e<<\nYour Here`)
                            }else if(wpRegistered.dimension.includes("end")){
                                waypointList.button(`§e§l>> §r§5${wpRegistered.name} §l§e<<\nYour Here`)
                            }                           
                            xpNeeded.push(0)
                        }else{
                            let otherWaypointVector = {x:wpRegistered.locationTop.x,y:wpRegistered.locationTop.y,z:wpRegistered.locationTop.z}
                            const distance = getDistance(otherWaypointVector, newLocation)
                            let xpRequirement = Math.floor(distance/distanceDivide) > 16 ? 16 : Math.floor(distance/distanceDivide)
                            if(wpRegistered.dimension.includes("overworld")){
                                waypointList.button(`§r${wpRegistered.name} §2§l${xpRequirement}`)
                            }else if(wpRegistered.dimension.includes("nether")){
                                waypointList.button(`§r§4${wpRegistered.name} §2§l${xpRequirement}`)
                            }else if(wpRegistered.dimension.includes("end")){
                                waypointList.button(`§r§5${wpRegistered.name} §2§l${xpRequirement}`)
                            }      
                            xpNeeded.push(xpRequirement)
                        }
                        
                    }
                    waypointList.show(player).then((response)=>{
                        if(response.selection != undefined)
                        {                        
                            if(xpNeeded[response.selection]<=player.level){
                                if(!JSON.stringify(wp.id).includes(JSON.stringify(waypointsRegistered[response.selection].id))){   
                                    let waypointTargetLocation = waypointsRegistered[response.selection].locationBot
                                    let waypointTargetDimension = world.getDimension(waypointsRegistered[response.selection].dimension)
                                    let teleportLocation = getFreeArea(waypointTargetDimension, waypointTargetLocation)      
                                    if(teleportLocation != undefined){                      
                                        player.addLevels(-xpNeeded[response.selection])
                                        player.playSound("mob.shulker.teleport")
                                        system.runTimeout(()=>{                                                                                    
                                            player.teleport(teleportLocation,{dimension:waypointTargetDimension})                                                                                                                                               
                                        },8)
                                    }else{
                                        player.sendMessage("§cWaypoint is Obstructed.")
                                    }
                                }                          
                            }else{
                                player.sendMessage("§cNot Enough Levels to teleport.")
                            }                  
                        }                   
                    })             
                },1)                
                return
            }

            if(getDistance(oldLocation,newLocation)<=2){
                player.sendMessage("Waypoint can't be near to other waypoints")
                return
            }
        }
       
        /*
        * SHOW WAYPOINT SETUP FORM
        */
        const setupWaypointForm = new ModalFormData();
        setupWaypointForm.title("Waystone")
        setupWaypointForm.textField("Waystone Name", "Name", Area_Names.generateName(waypoints));
        setupWaypointForm.show(player).then((formData)=>{
            if(formData.formValues == undefined) return
            if(formData.formValues[0].trim() != ""){
                let property = world.getDynamicProperty("waypoints")
                if(property != undefined){
                    waypoints = JSON.parse(property)
                }               
                let newWaypoint
                if(waypointBlockName.includes("bot")){
                    newWaypoint = {id:generateWaypointId(),name:formData.formValues,locationTop:{x:waypointLocation.x,y:waypointLocation.y+1,z:waypointLocation.z},locationBot:waypointLocation,dimension:player.dimension.id}
                }else{
                    newWaypoint = {id:generateWaypointId(),name:formData.formValues,locationTop:waypointLocation,locationBot:{x:waypointLocation.x,y:waypointLocation.y-1,z:waypointLocation.z},dimension:player.dimension.id}
                }
                waypoints.push(newWaypoint)             
                let waypointString = JSON.stringify(waypoints)
                try{
                world.setDynamicProperty("waypoints", waypointString)
                }catch(e){
                    player.sendMessage(`§cMaximum waystone reached! (Current Waystones:${waypoints.length})`)
                    waypoints.splice(waypoints.length-1,1)
                    return
                }
            }
        })
    }
},2)

/*
*   USE GATE TOKEN
*/
world.afterEvents.itemUse.subscribe(({source,itemStack})=>{
    if(itemStack.typeId == "sw:gate_token"){
        let player = source
        let newLocation = player.location
        let waypointsRegistered = JSON.parse(player.getDynamicProperty("waypoints"))
        const waypointList = new ActionFormData()
        waypointList.title("Waypoint List")
        waypointList.body(`Gate Token`)
        let xpNeeded = []
        for(let wpRegistered of waypointsRegistered){
            let otherWaypointVector = {x:wpRegistered.locationTop.x,y:wpRegistered.locationTop.y,z:wpRegistered.locationTop.z}
            const distance = getDistance(otherWaypointVector,newLocation)
            let xpRequirement = Math.floor(distance/distanceDivide) > 16 ? 16 : Math.floor(distance/distanceDivide)
            if(wpRegistered.dimension.includes("overworld")){
                waypointList.button(`§r${wpRegistered.name} §2§l${xpRequirement}`)
            }else if(wpRegistered.dimension.includes("nether")){
                waypointList.button(`§r§4${wpRegistered.name} §2§l${xpRequirement}`)
            }else if(wpRegistered.dimension.includes("end")){
                waypointList.button(`§r§5${wpRegistered.name} §2§l${xpRequirement}`)
            }      
            xpNeeded.push(xpRequirement)
            
        }
        waypointList.show(player).then((response)=>{
            if(response.selection != undefined)
            {                        
                if(xpNeeded[response.selection]<=player.level){   
                    let waypointTargetLocation = waypointsRegistered[response.selection].locationBot
                    let waypointTargetDimension = world.getDimension(waypointsRegistered[response.selection].dimension)
                    let teleportLocation = getFreeArea(waypointTargetDimension, waypointTargetLocation)
                    let mainHand = player.getComponent("equippable")
                    if(itemStack.amount == 1) {
                        mainHand.setEquipment("Mainhand", undefined)
                    }else{
                        itemStack.amount -= 1
                        mainHand.setEquipment("Mainhand", itemStack)
                    }
                    player.runCommandAsync(`inputpermission set @s camera disabled`)
                    player.runCommandAsync(`inputpermission set @s movement disabled`)
                    player.runCommandAsync(`execute as @s at @s rotated as @s run summon sw:token ~~0.65~ ~ ~`)     
                    player.teleport(player.location, {rotation:{x:0, y:player.getRotation().y}})
                    if(teleportLocation != undefined){  
                        system.runTimeout(()=>{       
                        player.runCommandAsync(`inputpermission set @s camera enabled`)  
                        player.runCommandAsync(`inputpermission set @s movement enabled`)              
                        player.addLevels(-xpNeeded[response.selection])
                        player.playSound("mob.shulker.teleport")                                                                                                         
                        player.teleport(teleportLocation,{dimension:waypointTargetDimension})                                                                                                                                               
                        },19)
                    }else{
                        player.sendMessage("§cWaypoint is Obstructed.")
                    }                        
                }else{
                    player.sendMessage("§cNot Enough Levels to teleport.")
                }                  
            }                   
        })             
    }
})

/*
*   STOP WAYPOINTS FROM BEING PUSHED AND PULLED BY PISTONS
*/
world.afterEvents.playerPlaceBlock.subscribe(({block : pistonBlock, player : pistonPlacer})=>{
    const pistonLocation = pistonBlock.location
    const facingCoordinate = getPistonFacingCoordinate(pistonBlock.permutation, pistonLocation, 1)
    const facingBlock = pistonBlock.dimension.getBlock(facingCoordinate)
    if(facingBlock.permutation.matches("sw:waystone_bottom") || facingBlock.permutation.matches("sw:waystone_top") || facingBlock.permutation.matches("sw:waystone")){
        pistonPlacer.runCommandAsync(`setblock ${pistonLocation.x} ${pistonLocation.y} ${pistonLocation.z} air destroy`)
    }
}, {blockTypes:["minecraft:piston", "minecraft:sticky_piston"]})

/*
*   COMMANDS
*/

world.beforeEvents.chatSend.subscribe((eventData)=>{
    const {message, sender} = eventData;
    if(message == "sw clear"){
        if(sender.hasTag("op")){
            sender.sendMessage("Cleared Property")
            system.run(()=>{
                world.setDynamicProperty("waypoints", "[]")
                sender.setDynamicProperty("waypoints","[]")
                waypoints = []
            })  
        }else{
            sender.sendMessage("§cYou don't have permission to use this command! Needs an op tag -> /tag @s add op")
        }            
        eventData.cancel = true
    }
    if(message == "sw myself"){
        sender.sendMessage(`§eTotal waypoints registered: ${JSON.parse(sender.getDynamicProperty("waypoints")).length}, Own Property Character Limit: ${sender.getDynamicProperty("waypoints").length}/32767`)
        for(let wpEntry of JSON.parse(sender.getDynamicProperty("waypoints"))){
            sender.sendMessage("§a" + JSON.stringify(wpEntry))
        }
        eventData.cancel = true
    }
    if(message == "sw world"){
        sender.sendMessage(`§eTotal waypoints in world: ${waypoints.length}, World Property Character Limit: ${JSON.stringify(waypoints).length}/32767`)
        for(let wpEntry of JSON.parse(world.getDynamicProperty("waypoints"))){
            sender.sendMessage("§a" + JSON.stringify(wpEntry))
        }
        eventData.cancel = true
    }
})

function generateWaypointId(){
    let letterAdd = ["A","B","C","D","E","F","G"]
    let generateId = Math.floor(Math.random()*999)
    for(let wp of waypoints){
        if(JSON.stringify(wp.id) == generateId.toString()){
           return generateId = generateId.toString() + letterAdd[Math.floor(Math.random()*6)]
        }
    }
    return generateId
}

function getDistance(firstCoordinate, secondCoordinate){
    return Math.sqrt( 
        Math.pow((firstCoordinate.x - secondCoordinate.x), 2) + 
        Math.pow((firstCoordinate.y - secondCoordinate.y), 2) + 
        Math.pow((firstCoordinate.z - secondCoordinate.z), 2)
    );
}

function getPistonfacingDirection(pistonPermutation){
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 0})) return "down"
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 1})) return "up"
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 2})) return "north"
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 3})) return "south"
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 4})) return "east"
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 5})) return "west"
}

function getPistonFacingCoordinate(pistonPermutation, pistonLocation, step){
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 0})) return {x:pistonLocation.x, y:pistonLocation.y-step, z:pistonLocation.z}
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 1})) return {x:pistonLocation.x, y:pistonLocation.y+step, z:pistonLocation.z}
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 2})) return {x:pistonLocation.x, y:pistonLocation.y, z:pistonLocation.z+step}
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 3})) return {x:pistonLocation.x, y:pistonLocation.y, z:pistonLocation.z-step}
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 4})) return {x:pistonLocation.x+step, y:pistonLocation.y, z:pistonLocation.z}
    if(pistonPermutation.matches("minecraft:piston", {'facing_direction': 5})) return {x:pistonLocation.x-step, y:pistonLocation.y, z:pistonLocation.z}

    if(pistonPermutation.matches("minecraft:sticky_piston", {'facing_direction': 0})) return {x:pistonLocation.x, y:pistonLocation.y-step, z:pistonLocation.z}
    if(pistonPermutation.matches("minecraft:sticky_piston", {'facing_direction': 1})) return {x:pistonLocation.x, y:pistonLocation.y+step, z:pistonLocation.z}
    if(pistonPermutation.matches("minecraft:sticky_piston", {'facing_direction': 2})) return {x:pistonLocation.x, y:pistonLocation.y, z:pistonLocation.z+step}
    if(pistonPermutation.matches("minecraft:sticky_piston", {'facing_direction': 3})) return {x:pistonLocation.x, y:pistonLocation.y, z:pistonLocation.z-step}
    if(pistonPermutation.matches("minecraft:sticky_piston", {'facing_direction': 4})) return {x:pistonLocation.x+step, y:pistonLocation.y, z:pistonLocation.z}
    if(pistonPermutation.matches("minecraft:sticky_piston", {'facing_direction': 5})) return {x:pistonLocation.x-step, y:pistonLocation.y, z:pistonLocation.z}
}

function getFreeArea(dimension, location){
    let l1 = {x:location.x-1+0.5,y:location.y,z:location.z+0.5}
    let l2 = {x:location.x+1+0.5,y:location.y,z:location.z+0.5}
    let l3 = {x:location.x+0.5,y:location.y,z:location.z-1+0.5}
    let l4 = {x:location.x+0.5,y:location.y,z:location.z+1+0.5}
    let l1_floor = {x:location.x-1+0.5,y:location.y-1,z:location.z+0.5}
    let l2_floor = {x:location.x+1+0.5,y:location.y-1,z:location.z+0.5}
    let l3_floor = {x:location.x+0.5,y:location.y-1,z:location.z-1+0.5}
    let l4_floor = {x:location.x+0.5,y:location.y-1,z:location.z+1+0.5}

    if(dimension.getBlock(l1) != undefined && dimension.getBlock(l2) != undefined && dimension.getBlock(l3) != undefined && dimension.getBlock(l4) != undefined){
        if(dimension.getBlock(l1).isAir && !dimension.getBlock(l1_floor).isAir) return l1
        if(dimension.getBlock(l2).isAir && !dimension.getBlock(l2_floor).isAir) return l2
        if(dimension.getBlock(l3).isAir && !dimension.getBlock(l3_floor).isAir) return l3
        if(dimension.getBlock(l4).isAir && !dimension.getBlock(l4_floor).isAir) return l4
        /*world.sendMessage(dimension.getBlock(l1).typeId)
        world.sendMessage(dimension.getBlock(l2).typeId)
        world.sendMessage(dimension.getBlock(l3).typeId)
        world.sendMessage(dimension.getBlock(l4).typeId)*/  
    }else{
        //Obstructed
    }
    return {x:location.x+0.5,y:location.y+0.5,z:location.z+0.5}
}