/*
 * This file is part of LiquidBounce (https://github.com/CCBlueX/LiquidBounce)
 *
 * Copyright (c) 2015 - 2026 CCBlueX
 *
 * LiquidBounce is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * LiquidBounce is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with LiquidBounce. If not, see <https://www.gnu.org/licenses/>.
 */
package net.ccbluex.liquidbounce.features.module.modules.exploit

import net.ccbluex.liquidbounce.config.types.group.ToggleableValueGroup
import net.ccbluex.liquidbounce.config.types.list.Tagged
import net.ccbluex.liquidbounce.event.tickHandler
import net.ccbluex.liquidbounce.features.module.ClientModule
import net.ccbluex.liquidbounce.features.module.ModuleCategories
import net.ccbluex.liquidbounce.features.module.modules.exploit.ModuleTimeShift.Health.incrementSpeed
import net.ccbluex.liquidbounce.features.module.modules.exploit.ModuleTimeShift.Health.speed
import net.ccbluex.liquidbounce.features.module.modules.exploit.ModuleTimeShift.Health.triggerHealth
import net.ccbluex.liquidbounce.utils.network.MovePacketType
import net.ccbluex.liquidbounce.utils.client.Timer
import net.ccbluex.liquidbounce.utils.client.chat
import net.ccbluex.liquidbounce.utils.client.isOlderThanOrEqual1_8
import net.ccbluex.liquidbounce.utils.client.markAsError
import net.ccbluex.liquidbounce.utils.entity.moving
import net.ccbluex.liquidbounce.utils.kotlin.Priority
import net.minecraft.network.protocol.game.ServerboundMovePlayerPacket
import net.minecraft.world.effect.MobEffectCategory
import net.minecraft.world.effect.MobEffects
import kotlin.math.max
import kotlin.math.min

/**
 * Time Shift module
 * Formerly known as Regen, Zoot or any other module that abuses Minecraft 1.8 client-sided mechanics
 *
 * Regen and Zoot are combined into this module to keep the code clean and organized
 * and to prevent multiple modules executing time shift at the same time.
 *
 * FastUse on the other hand is a separate module because it has a few options that are not
 * related to time shift.
 */
object ModuleTimeShift : ClientModule("TimeShift", ModuleCategories.EXPLOIT, disableOnQuit = true,
    aliases = listOf("Regen", "Zoot")
) {

    private object Health : ToggleableValueGroup(this, "Health", true) {

        /**
         * The health to trigger the speed
         */
        private val triggerHealth by int("Health", 16, 0..20)

        /**
         * Calculate the speed to reach the desired health
         *
         * ([triggerHealth] - [player.health]) * [incrementSpeed] = [speed]
         */
        private val incrementSpeed by int("Increment", 20, 0..60)

        val speed: Int
            get() = max(0, ((triggerHealth - player.health) * incrementSpeed).toInt())

    }

    init {
        tree(Health)
    }

    private val badEffects by boolean("BadEffects", false)
    private val fire by boolean("Fire", false)

    private val maximumSpeed by int("MaximumSpeed", 100, 5..200)
    private val timer by float("Timer", 1.0f, 0.1f..10f)

    private val conditions by multiEnumChoice("Conditions", Conditions.NOT_IN_THE_AIR)

    /**
     * The move packet type to send.
     *
     * @see MovePacketType
     * @see ServerboundMovePlayerPacket
     *
     * [MovePacketType.FULL] is the most likely to bypass, since vanilla 1.17+ clients
     * typically send those when using items. Most anticheats have excluded 1.17+ clients
     * from timer checks.
     *
     * AntiCheat: Grim (Tested 2.5.34), Vulcan, Vanilla
     * Tested on: eu.loyisa.cn, anticheat-test.com
     * Usable Client version: 1.17-1.20.4
     * Usable Server version: >=1.8.9
     * Q: Why this works?
     * A: Legacy servers depend on the clientside tickrate to calculate eating speed.
     * Grim exemption: https://github.com/GrimAnticheat/Grim/blob/9660021d024a54634605fbcdf7ce1d631b442da1/src/main/java/ac/grim/grimac/checks/impl/movement/TimerCheck.java#L99
     */
    private val packetType by enumChoice("PacketType", MovePacketType.FULL)

    override fun onEnabled() {
        if (!isOlderThanOrEqual1_8) {
            chat(markAsError(message("warningNot1_8")))
        }
        super.onEnabled()
    }

    val repeatable = tickHandler {
        // Check if the player is in creative mode or dead, if so, skip the time shift
        if (player.abilities.instabuild || player.isDeadOrDying) {
            return@tickHandler
        }

        if (conditions.any { it.test() }) {
            return@tickHandler
        }

        // Calculate the tick speed
        var tickSpeed = 0

        if (Health.enabled) {
            tickSpeed = max(tickSpeed, Health.speed)
        }

        if (fire && player.isOnFire && !player.isInWaterOrRain) {
            tickSpeed = max(tickSpeed, 9)
        }

        if (badEffects) {
            player.activeEffectsMap
                .filter { (effect, status) -> effect.value().category == MobEffectCategory.HARMFUL
                    && !status.isInfiniteDuration
                }
                .maxByOrNull { it.value.duration }?.let { (_, status) ->
                    tickSpeed = max(tickSpeed, min(status.duration / 20, maximumSpeed))
                }
        }

        if (tickSpeed > 0) {
            Timer.requestTimerSpeed(timer, Priority.IMPORTANT_FOR_USAGE_1, this@ModuleTimeShift)

            repeat(tickSpeed) {
                network.send(packetType.generatePacket())
            }
        }
    }

    @Suppress("unused")
    private enum class Conditions(
        override val tag: String,
        val test: () -> Boolean
    ) : Tagged {
        NOT_IN_THE_AIR("NotInTheAir", {
            !player.onGround()
        }),
        NOT_DURING_MOVE("NotDuringMove", {
            player.moving
        }),
        NOT_DURING_REGENERATION("NotDuringRegeneration", {
            player.hasEffect(MobEffects.REGENERATION)
        }),
        DO_NOT_CAUSE_HUNGER("DoNotCauseHunger", {
            player.foodData.foodLevel < 20
        })
    }
}
