import { DimensionTypes, system, world } from "@minecraft/server";
import { generateTitle } from "./nameGen";
import { createVector, isWithinRadius, sendDebugMessage, vectorAdd, vectorToString } from "./JM_Utils";
import { ModalFormData } from "@minecraft/server-ui";
import { locomotiveFamily } from "./trainsAddOn";
import { getSlowdownRadius } from "./automatic";

const stationDataProperty = "lb_ta:station_data"
const blankProperty = {}
DimensionTypes.getAll().forEach(dimension => blankProperty[dimension.typeId] = {})
if (!world.getDynamicPropertyIds().includes(stationDataProperty)) world.setDynamicProperty(stationDataProperty,JSON.stringify(blankProperty))

const reverseDirection = {
    "north": "south",
    "east": "west",
    "south": "north",
    "west": "east"
}

world.beforeEvents.worldInitialize.subscribe(initEvent => {
    initEvent.blockComponentRegistry.registerCustomComponent("lb_ta:station",{
        onTick: tickEvent => {
            const stationLocaton = tickEvent.block.location
            const data = stationData(stationLocaton,tickEvent.dimension).get()
            const block = tickEvent.block
            // built-in redstone check
            let isPowered = block.getRedstonePower() > 0
            // non-supported redstone check (redstone block, torch, ...?)
            if (!isPowered) for(const direction of ["north", "east", "west", "south", "below", "above" ]) {
                try {
                    const testBlock = block[direction]()
                    if (testBlock) {
                        switch(testBlock.typeId) {
                            case "minecraft:redstone_block":
                                isPowered = true
                                break
                            case "minecraft:redstone_torch":
                                if (testBlock.permutation.getState("torch_facing_direction") !== reverseDirection[direction]) isPowered = true
                                break
                        }
                    }
                } catch (error) {
                    if (error.name !== "LocationOutOfWorldBoundariesError") sendDebugMessage(error)
                }
            }
            let blockId = block.typeId.split(":")[1]
            // check powered state and block type agree
            if (block.hasTag("lb_ta:load_station")) { // now irrelevant for timed station
                if (isPowered) {
                    if (!blockId.startsWith("powered_")) block.setType(`lifeboat_ta:powered_${blockId}`)
                } else {
                    if (blockId.startsWith("powered_")) {
                        blockId = blockId.slice(8)
                        block.setType(`lifeboat_ta:${blockId}`)
                    }
                }
            }
            // add to locomotive's list
            tickEvent.dimension.getEntities({
                families:[locomotiveFamily],
                location:stationLocaton,
                maxDistance:70
            }).forEach(locomotive => {
                // each locomotive has a list of all stations it is in proximity to.
                // the locomotive will analyze this list and pick the nearest one to use
                const nearby = nearbyStations(locomotive)
                const isTracked = nearby.contains(stationLocaton)
                if (block.typeId === "lifeboat_ta:station") {
                    // passenger station blocks only consider broadcasting if they don't require redstone, or are powered.
                    if (isWithinRadius(locomotive.location,stationLocaton, getSlowdownRadius(locomotive))) { // close enough to care?
                        if (!isTracked) nearby.add(stationLocaton)
                    } else {
                        if (isTracked) nearby.remove(stationLocaton)
                    }
                } else {
                    // load stations must always be in the list when in range!  They cannot be "disabled" like passenger stations
                    if (isWithinRadius(locomotive.location, stationLocaton, getSlowdownRadius(locomotive))) {
                        if (!isTracked) nearby.add(stationLocaton)
                    } else if (isTracked) nearby.remove(stationLocaton)
                }
            })
        },
        onPlayerInteract: interactEvent => {
            const typeId = interactEvent.block.typeId
            if (typeId === "lifeboat_ta:station" || typeId === "lifeboat_ta:powered_station") {
                stationForm(interactEvent.block.location, interactEvent.dimension).show(interactEvent.player).then(response => {
                    if (response.canceled) return
                    const [title, stopTime, eject, notifications, nearby] = response.formValues
                    const data = {}
                    data.title = title,
                    data.stopTime = stopTime === -1 ? -1 : stopTime * 20,
                    data.eject = eject
                    data.notifications = notifications,
                    data.nearby = nearby
                    stationData(interactEvent.block.location, interactEvent.dimension).set(data)
                })
            } else {
                loadStationForm(interactEvent.block.location, interactEvent.dimension).show(interactEvent.player).then(response => {
                    if (response.canceled) return
                    const [title, eject, notifications, nearby] = response.formValues
                    const data = {}
                    data.title = title,
                    data.eject = eject
                    data.stopTime = -1
                    data.notifications = notifications,
                    data.nearby = nearby
                    stationData(interactEvent.block.location, interactEvent.dimension).set(data)
                })
            }
        },
        onPlace: placeEvent => {
            const data = {...defaultStationData}
            if (placeEvent.block.typeId.includes("load_station")) {
                data.stopTime = -1
            }
            stationData(placeEvent.block.location, placeEvent.dimension).set(data)
            // sendDebugMessage(`§aplaced!`)
        },
        onPlayerDestroy: destroyEvent => {
            stationData(destroyEvent.block.location, destroyEvent.dimension).delete()
        },

    })
})

function stationForm(stationLocation, dimension) {
    const form = new ModalFormData(),
        data = stationData(stationLocation, dimension).get()
    form.title({translate:"form.station.header"})
    form.textField({translate:"form.station.title"},data.title,data.title)
    form.slider({translate:"form.station.stop_seconds"},0,60,1,data.stopTime === -1 ? -1 : data.stopTime / 20)
    form.toggle({translate:"form.station.eject_riders"},data.eject)
    form.toggle({translate:"form.station.notifications.enabled"},data.notifications)
    form.toggle({translate:"form.station.notifications.only_nearby"},data.nearby)
    return form
}

function loadStationForm(stationLocation, dimension) {
    const form = new ModalFormData(),
        data = stationData(stationLocation, dimension).get()
        form.title({translate:"form.station.header"})
        form.textField({translate:"form.station.title"},data.title,data.title)
        form.toggle({translate:"form.station.eject_riders"},data.eject)
        form.toggle({translate:"form.station.notifications.enabled"},data.notifications)
        form.toggle({translate:"form.station.notifications.only_nearby"},data.nearby)
    return form
}

const defaultTitle = "DEFAULT_TITLE"
const defaultStationData = {
    title: defaultTitle,
    eject: false,
    stopTime: 5 * 20,
    notifications: true,
    nearby: true
}

function isStation(location, dimension) {
    const data = JSON.parse(world.getDynamicProperty(stationDataProperty))
    if (data[dimension.id] && data[dimension.id][vectorToString(location)]) return true
    return false
}

export function stationData(location, dimension) {
    const locationString = vectorToString(location)
    const dimensionId = dimension.id

    function getAllData() {
        return JSON.parse(world.getDynamicProperty(stationDataProperty))
    }
    
    function getSpecific() {
        let data = getAllData()[dimensionId][locationString]
        if (!data) {
            data = {...defaultStationData}
            // Object.keys(data).forEach(key => data[key] = undefined)
            data.title = "§k"
            for (let index = 0; index < Math.ceil(Math.random() * 15) + 5; index++) {
                data.title += "0"                
            }
        }
        return data
    }

    function setAllData(data) {
        world.setDynamicProperty(stationDataProperty,JSON.stringify(data))
    }

    function setSpecific(data) {
        const allData = getAllData()
        // data.stopTime = data.stopTime * 20
        // if somehow this dimension isn't yet represented in allData, add it
        if (!allData[dimensionId]) allData[dimensionId] = {}
        allData[dimensionId][locationString] = data
        setAllData(allData)
    }

    return {
        get: function() {
            return getSpecific()
        },
        // title: getSpecific().title,
        // radius: getSpecific().radius,
        // notifications: getSpecific().notifications,
        // nearby: getSpecific().nearby,
        set: function(data) {
            if (isStation(location,dimension) && data.title === defaultTitle) {
                // sendDebugMessage(`station at ${locationString} already exists, aborting override`)
                return true
            }
            data = {...data}
            if (data.title === defaultTitle) data.title = generateTitle()
            return setSpecific(data)
        },
        delete: function() {
            const allData = getAllData()
            delete allData[dimensionId][locationString]
            setAllData(allData)
            return true
        }
    }
}

world.afterEvents.pistonActivate.subscribe(event => {
    // note that getAttatchedBLocksLocations will only return affected blocks - e.g., non-sticky pistons will not affect any blocks while retracting
    event.piston.getAttachedBlocksLocations().forEach(location => {
        const oldData = stationData(location, event.dimension).get()
        if (!oldData) return
        // this is a station block!
        const facingState = event.block.permutation.getState("facing_direction")
        const expanding = event.isExpanding
        const offset = {x:0,y:0,z:0}
        switch(facingState) {
            case 0: //down
                if (expanding) { //expanding downwards
                    offset.y = -1
                } else { //retracting upwards
                    offset.y = 1
                }
                break
            case 1: //up
                if (expanding) { //expanding upwards
                    offset.y = 1
                } else { //retracting downwards
                    offset.y = -1
                }
                break
            case 2: //south
                if (expanding) { //expanding south
                    offset.z = 1
                } else { //retracting north
                    offset.z = -1
                }
                break
            case 3: //north
                if (expanding) { //expanding north
                    offset.z = -1
                } else { //retracting south
                    offset.z = 1
                }
                break
            case 4: //east
                if (expanding) { //expanding east
                    offset.x = 1
                } else { //retracting west
                    offset.x = -1
                }
                break
            case 5: //west
                if (expanding) { //expanding west
                    offset.x = -1
                } else { //retracting east
                    offset.x = 1
                }
                break
            default:
                sendDebugMessage(`unhandled piston state ${facingState}`)
                return
        }
        stationData(vectorAdd(location,offset), event.dimension).set(oldData)
        stationData(location, event.dimension).delete()
    })
})

system.runInterval(() => {
    const data = JSON.parse(world.getDynamicProperty(stationDataProperty))
    Object.keys(data).forEach(dimensionId => {
        const stationLocations = Object.keys(data[dimensionId])
        if (stationLocations.length === 0) return
        const dimension = world.getDimension(dimensionId)
        for (const locationString of stationLocations) {
            const location = createVector(locationString)
            try {
                const block = dimension.getBlock(location)
                if (block && !isStationBlock(block)) {
                    system.runTimeout(() => {
                        try {
                            const checkBlock = dimension.getBlock(location)
                            if (checkBlock && !isStationBlock(checkBlock)) {
                                stationData(location,dimension).delete()
                                sendDebugMessage(`block ${locationString} deleted!`)
                            }
                        } catch (error) {
                            sendDebugMessage(error)
                        }
                    },4)
                }
            } catch (error) {
                sendDebugMessage(error)
            }
        }
    })
},100)

world.afterEvents.blockExplode.subscribe(explodeEvent => {
    const dataFunc = stationData(explodeEvent.block.location, explodeEvent.dimension)
    const data = dataFunc.get()
    if (data) dataFunc.delete()
})

export function nearbyStations(locomotive) {
    const property = "lb_ta:nearby_stations"
    if (!locomotive.getDynamicPropertyIds().includes(property)) set({})

    function set(data) {
        locomotive.setDynamicProperty(property,JSON.stringify(data))
    }

    function get() {
        return JSON.parse(locomotive.getDynamicProperty(property))
    }

    return {
        get: function() {
            return get()
        },
        getFromDimension: function () {
            return get()[locomotive.dimension.id]
        },
        contains: function(location) {
            if (!get()[locomotive.dimension.id]) return false
            return Object.keys(get()[locomotive.dimension.id]).includes(vectorToString(location))
        },
        add: function(location) {
            const newData = get()
            if (!newData[locomotive.dimension.id]) newData[locomotive.dimension.id] = {}
            newData[locomotive.dimension.id][vectorToString(location)] = true
            set(newData)
        },
        remove: function(location) {
            const newData = get()
            delete newData[locomotive.dimension.id][vectorToString(location)]
            set(newData)
        }
    }
}

export function isStationBlock(block) {
    if (isPoweredStationBlock(block) || isUnpoweredStationBlock(block)) return true
    return false
}

export function isPoweredStationBlock(block) {
    return block.typeId === "lifeboat_ta:powered_station" || block.typeId === "lifeboat_ta:powered_load_station"
}

function isUnpoweredStationBlock(block) {
    return block.typeId === "lifeboat_ta:station" || block.typeId === "lifeboat_ta:load_station"
}