package content.global.bots

import core.game.interaction.DestinationFlag
import core.game.interaction.MovementPulse
import core.game.node.entity.skill.Skills
import core.game.node.item.Item
import core.game.system.task.Pulse
import core.game.world.map.Location
import core.game.world.map.zone.ZoneBorders
import core.tools.RandomFunction
import org.rs09.consts.Items
import core.game.bots.AIRepository
import core.game.bots.Script

/**
 * A bot that does various random slayer tasks throughout the game and sells the loot on the GE.
 * @author Ceikry
 */
class GenericSlayerBot : Script() {
    var state = State.GETTING_TASK
    var task = Task.CAVE_CRAWLER //default value to ensure non-nullability
    var assignment = Assignment("",0) //same as above
    var teleportFlag = false //have we teleported?
    val FOOD_ID = Items.LOBSTER_379
    val varrockBankBorders = ZoneBorders(3184, 3435,3187, 3441)



    /**
     * Per-tick actions (they don't run if the bot already has a pulse running, such as moving or combat.)
     */
    override fun tick() {
        scriptAPI.eat(FOOD_ID) //try to eat every tick (does nothing if health isn't low enough)

        when(state){

            /**
             * State that handles getting a new task
             */
            State.GETTING_TASK -> {
                populateTaskAndAssignment()
                state = State.GOING_TO_HUB //switch states
            }

            /**
             * State that handles going to the "hub" for the task. Such as fremmy slayer cave or slayer tower.
             */
            State.GOING_TO_HUB -> {
                when(task.hub){

                    TaskHub.FREMENNIK_CAVE -> {
                        if(!teleportFlag) {
                            scriptAPI.teleport(Location.create(2781, 3615, 0)) //tele -> fremennik fairy ring
                            teleportFlag = !teleportFlag //teleportFlag -> true
                        }
                        else {
                            val entrance = scriptAPI.getNearestNode(4499,true) //returns the object if within render distance, null if not.
                            if(entrance == null || !entrance.location.withinDistance(bot.location,2)) {
                                scriptAPI.walkTo(Location.create(2796, 3615, 0))
                            } else {
                                entrance.interaction.handle(bot,entrance.interaction[0]) //bot -> use interaction [0] of cave object
                                teleportFlag = !teleportFlag //teleportFlag -> false
                                state = State.KILLING_ENEMY //switch states
                            }
                        }
                    }
                    else -> {}
                }
            }


            /**
             * State used for combat. Handles moving the bot back into the zone for the monster and looting as well.
             */
            State.KILLING_ENEMY -> {
                val items = AIRepository.groundItems.get(bot) //retrieve drops that belong to this bot
                if(items != null && items.isNotEmpty()){ //if we have loot...
                    if(bot.inventory.freeSlots() == 0){
                        if(bot.inventory.contains(FOOD_ID,1)){
                            scriptAPI.forceEat(FOOD_ID)
                            return
                        }
                    }
                    scriptAPI.takeNearestGroundItem(items.get(0).id)
                    if(bot.inventory.getAmount(Item(FOOD_ID)) == 0){
                        state = State.GOING_TO_BANK
                    }
                    return
                }

                if(assignment.amount > 0) {
                    if (task.borders.insideBorder(bot)) { //make sure bot is within the borders of the task's zone.
                        scriptAPI.attackNpcInRadius(bot, task.npc_name, 20) //attack nearest NPC with given task name within radius.
                        assignment.amount--
                    } else {
                        scriptAPI.walkTo(task.borders.randomLoc)
                    }
                } else {
                    state = State.GOING_TO_BANK //switch states and bank.
                }
            }

            /**
             * State used to handle traveling to a bank. Due to the dynamic nature of where these bots might be
             * in this state, they simply teleport to Varrock and then walk to the west bank to bank.
             */
            State.GOING_TO_BANK -> {
                if(!teleportFlag){
                    scriptAPI.teleport(Location.create(3213, 3426, 0))
                    teleportFlag = !teleportFlag
                } else {
                    if(!varrockBankBorders.insideBorder(bot)) {
                        scriptAPI.walkTo(varrockBankBorders.randomLoc)
                    } else {
                        val bank = scriptAPI.getNearestNode("Bank Booth",true)
                        bank ?: return
                        /**
                         * Run a pulse that moves the bot to the bank booth and then faces it, and then swaps to the actual banking state
                         */
                        bot.pulseManager.run(object: MovementPulse(bot,bank, DestinationFlag.OBJECT){
                            override fun pulse(): Boolean {
                                bot.faceLocation(bank.location)
                                state = State.BANKING
                                teleportFlag = !teleportFlag
                                return true
                            }
                        })
                    }
                }
            }

            /**
             * State used to handle banking itself. Runs a pulse with a delay of 10 (to make it look like the bot is idling at the bank booth a bit.)
             * the pulse adds every item in the bot's inventory to its bank and then clears the bot's inventory. Re-adds lobsters to the inventory afterwords.
             */
            State.BANKING -> {
                bot.pulseManager.run(object: Pulse(10){
                    override fun pulse(): Boolean {
                        for(item in bot.inventory.toArray()){
                            item ?: continue
                            if(item.checkIgnored()) continue
                            bot.bank.add(item)
                        }
                        bot.inventory.clear()
                        for(item in inventory)
                            bot.inventory.add(item)
                        scriptAPI.withdraw(Items.LOBSTER_379,10)
                        bot.fullRestore()

                        if(assignment.amount <= 0){
                            state = State.GOING_TO_GE
                        } else {
                            state = State.GOING_TO_HUB
                        }

                        return true
                    }
                })
            }


            /**
             * State used to handle travelling to the GE.
             */
            State.GOING_TO_GE -> {
                if(bot.location != Location.create(3165, 3487, 0)) {
                    scriptAPI.walkTo(Location.create(3165, 3487, 0))
                } else {
                    state = State.SELLING
                }
            }

            /**
             * State used to handle selling items on the GE
             */
            State.SELLING -> {
                scriptAPI.sellAllOnGe()
                state = State.GETTING_TASK
            }

        }
    }


    /**
     * Called when a bot needs to be regenerated, like when a bot dies.
     */
    override fun newInstance(): Script {
        TODO("Not yet implemented")
    }

    /**
     * Populate task and assignment vars
     */
    fun populateTaskAndAssignment() {
        task = Task.values().random() //get a new random task from the enum
        assignment = Assignment(task.npc_name, RandomFunction.random(task.minAmt, task.maxAmt + 1)) //produce a new Assignment class with values from our Task.
    }


    /**
     * Item extension function to check for ignored items when banking.
     */
    fun Item.checkIgnored(): Boolean {
        if(name.toLowerCase().contains("charm")) return true
        if(name.toLowerCase().contains("lobster")) return true
        if(name.toLowerCase().contains("clue")) return true
        if(!definition.isTradeable) return true
        return when(id){
            Items.BONES_2530 -> true
            995 -> true
            else -> false
        }
    }

    /**
     * The state enums
     */
    enum class State {
        GETTING_TASK,
        GOING_TO_HUB,
        KILLING_ENEMY,
        GOING_TO_BANK,
        BANKING,
        GOING_TO_GE,
        SELLING
    }

    /**
     * Assign default skills and initial inventory
     */
    init {
        skills[Skills.SLAYER] = 99
        inventory.add(Item(Items.LOBSTER_379,10))

    }

    /**
     * List of tasks that this bot can receive
     */
    enum class Task(val npc_name: String, val minAmt: Int, val maxAmt: Int, val hub: TaskHub, val borders: ZoneBorders) {
        CAVE_CRAWLER("Cave crawler",20,100, TaskHub.FREMENNIK_CAVE, ZoneBorders(2778, 9988,2798, 10002))
    }

    /**
     * The task hubs
     */
    enum class TaskHub {
        FREMENNIK_CAVE,
        SLAYER_TOWER,
        TAVERLY_DUNGEON
    }

    class Assignment(val npc_name: String, var amount: Int) //class used to keep track of current assignment and how many are left.
}