import * as mc from '@minecraft/server';
import * as lib from '../lib/lib';
import * as config from '../pb_scraft_config';

import { SPELL_PLAYERS } from '../pb_scraft_player';
import { locationIsValid } from '../lib/beam_utils';


export default class SpellTargetGround {

    player;
    cooldown;
    name;
    manacost;
    castTime;
    soundCast;
    particlesCast;
    particlesTarget;
    projectileData;

    amount = 1;
    radius = 0;
    shape = "circle";

    timeIndex = 0;
    isCharged = false;
    isActive = true;
    itemUse;

    totalTime;
    glyphPower = 0;

    constructor(player, spellData, itemUse) {
        this.player = player;
        this.itemUse = itemUse;

        this.name = spellData.identifier;
        this.manacost = spellData.manacost;;
        this.castTime = spellData.castTime;;
        this.cooldown = spellData.cooldown;
        this.soundCast = spellData.soundCast;
        this.particlesCast = spellData.particlesCast;
        this.particlesTarget = spellData.particlesTarget;

        if (this.amount != undefined) this.amount = spellData.amount;
        if (this.radius != undefined) this.radius = spellData.radius;
        if (this.shape != undefined) this.shape = spellData.shape;

        this.projectileData = config.EFFECT_LIST.find((f) => f.identifier == spellData.projectile);

        this.totalTime = this.castTime + this.cooldown;

        this.processItem(itemUse, spellData)
    }

    processItem(itemUse, spelldata) {
        const glyph_1 = itemUse.getDynamicProperty("pb_scraft_glyph_1");
        const glyph_2 = itemUse.getDynamicProperty("pb_scraft_glyph_2");

        let glyphMana = 1.0;
        let glyphCastTime = 1.0;

        if (glyph_1 != undefined) {
            if (glyph_1 == "pb_scraft:glyph_accelerate") glyphCastTime = glyphCastTime - 0.3;
            if (glyph_1 == "pb_scraft:glyph_alleviate") glyphMana = glyphMana - 0.2;
            if (glyph_1 == "pb_scraft:glyph_intensify") this.glyphPower = this.glyphPower + 10;
        }

        if (glyph_2 != undefined) {
            if (glyph_2 == "pb_scraft:glyph_accelerate") glyphCastTime = glyphCastTime - 0.3;
            if (glyph_2 == "pb_scraft:glyph_alleviate") glyphMana = glyphMana - 0.2;
            if (glyph_2 == "pb_scraft:glyph_intensify") this.glyphPower = this.glyphPower + 10;
        }

        const itemdata = config.CAST_ITEM_LIST.find((f) => f.identifier == itemUse.typeId);
        if (itemdata.element == spelldata.element) this.glyphPower += itemdata.spellPower;

        this.manacost = Math.floor(this.manacost * glyphMana);
        this.castTime = this.castTime * glyphCastTime;
    }

    cast() {
        this.player.playSound(this.soundCast, this.player.location);
    }

    stop() {
        this.isActive = false;
        lib.setCooldown(this.player, 0);
    }

    release() {
        this.trigger();
        this.isActive = false;
    }

    trigger() {
        lib.runCooldown(this.player);   //casting staff cooldown here
        const dimension = this.player.dimension;
        const view_vector = this.player.getViewDirection();
        const location = this.player.getHeadLocation();

        const range = this.projectileData.range;
        let distance = range;

        SPELL_PLAYERS.get(this.player.id).consumeMana(this.manacost);

        //check for first impact on block
        let block_raycast_hit = dimension.getBlockFromRay(location, view_vector, { includeLiquidBlocks: false, includePassableBlocks: true, maxDistance: range });
        if (block_raycast_hit != undefined) distance = Math.round(lib.getDistance(location, block_raycast_hit.block.location)) - 1;

        //Cast when not target ground? yes/no/why?
        let target_location = { x: (location.x + view_vector.x * distance), y: (location.y + view_vector.y * distance), z: (location.z + view_vector.z * distance) };
        if (block_raycast_hit != undefined) target_location = block_raycast_hit.block.above().bottomCenter();

        if(lib.locationIsValid(dimension, target_location) == false) return;

        if (this.particlesCast != undefined) {
            let mvm = new mc.MolangVariableMap();
            mvm.setFloat(`variable.direction`, -Math.round(this.player.getRotation().y));
            mvm.setFloat(`variable.direction_y`, -this.player.getRotation().x * Math.PI / 180);

            this.particlesCast.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, location, mvm); });
        }

        if (this.particlesTarget != undefined) {
            let mvm = new mc.MolangVariableMap();
            mvm.setFloat(`variable.direction`, -Math.round(this.player.getRotation().y));
            mvm.setFloat(`variable.direction_y`, -this.player.getRotation().x * Math.PI / 180);

            this.particlesTarget.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, target_location, mvm); });
        }

        if (this.shape == "circle") {
            let projectile_location;
            for (let index = 0; index < this.amount; index++) {
                projectile_location = lib.randomLocationDisc(target_location, this.radius);
                this.spawnEffectWithDelay(projectile_location, dimension, index * 3);
            }
        }

        if (this.shape == "wall") {
            const vector_left = lib.rotateVector2D(view_vector, 90);
            const step = 1.0;

            let projectile_location = { x: target_location.x - vector_left.x * step * this.amount / 2, y: target_location.y, z: target_location.z - vector_left.z * step * this.amount / 2, };
            for (let index = 0; index < this.amount; index++) {
                projectile_location = { x: projectile_location.x + vector_left.x * step, y: projectile_location.y, z: projectile_location.z + vector_left.z * step, };

                impactInstant(this.projectileData, projectile_location, dimension, this.player, this.glyphPower);
                const projectile = mc.world.getDimension(dimension.id).spawnEntity(this.projectileData.identifier, projectile_location);
                const component = projectile.getComponent("minecraft:tameable");
                component.tame(this.player);
                dimension.playSound(this.projectileData.soundLaunch, location);
            }
        }

        if (this.shape == "instant") {
            impactInstant(this.projectileData, target_location, dimension, this.player, this.glyphPower);
        }

        if (this.shape == "heal") {
            healInstant(this.projectileData, target_location, dimension, this.player, this.glyphPower);
        }

        if (this.shape == "self") {
            impactInstant(this.projectileData, this.player.location, dimension, this.player, this.glyphPower);
        }

        if (this.shape == "teleport") {
            impactInstant(this.projectileData, target_location, dimension, this.player, this.glyphPower);
            if (dimension.getBlock(target_location).isAir) this.player.teleport(target_location);
        }

        if (this.shape == "jump") {
            impactInstant(this.projectileData, this.player.location, dimension, this.player, this.glyphPower);
            const impulse = 2;
            lib.applyImpulsePlayer(this.player, {x: view_vector.x * impulse, y: view_vector.y + 0.2 * impulse, z: view_vector.z * impulse});
        }

    }

    tick() {
        if (this.isActive) {
            lib.runCooldown(this.player);
            if (this.timeIndex % this.castTime == 0) this.cast();
            if (this.timeIndex >= this.castTime) this.isCharged = true;
            this.timeIndex++;
        }
    }

    spawnEffectWithDelay(location, dimension, delay) {
        try {
            mc.system.runTimeout(() => {
                const projectile = mc.world.getDimension(dimension.id).spawnEntity(this.projectileData.identifier, location);
                impactInstant(this.projectileData, location, dimension, this.player, this.glyphPower);
                const component = projectile.getComponent("minecraft:tameable");
                component.tame(this.player);
                dimension.playSound(this.projectileData.soundLaunch, location);
            }, delay)
        }
        catch (error) { }
    }
}

export function impactInstant(projectileData, location, dimension, caster, glyphPower) {

    projectileData.particle_impact.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, location); });
    dimension.playSound(projectileData.soundLaunch, location, { volume: 100 });

    let damage = projectileData.damage;
    if (caster != undefined) damage = damage + projectileData.spellpower * (SPELL_PLAYERS.get(caster.id).spellPower) + projectileData.spellpower * glyphPower;
    damage = damage * config.SCALING;

    for (let entity of lib.targetMobMonster(location, dimension.id, projectileData.radius)) {
        entity.applyDamage(damage, { cause: mc.EntityDamageCause.contact, damagingEntity: caster });
        entity.applyKnockback(entity.location.x - location.x, entity.location.z - location.z, projectileData.knockback_horizontal, projectileData.knockback_vertical);

        if (projectileData.particle_hit != undefined) projectileData.particle_hit.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.getHeadLocation()) });
    }

    if (config.PVP.enabled) {
        const pvp_damage = damage * config.PVP.scaling;
        for (let entity of mc.world.getDimension(dimension.id).getEntities({ location: location, families: ["player"], maxDistance: projectileData.radius })) {
            if (entity != caster) {
                entity.applyDamage(pvp_damage, { cause: mc.EntityDamageCause.contact, damagingEntity: caster });
                entity.applyKnockback(entity.location.x - location.x, entity.location.z - location.z, projectileData.knockback_horizontal, projectileData.knockback_vertical);

                if (projectileData.particle_hit != undefined) projectileData.particle_hit.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.getHeadLocation()) });
            }
        }
    }
}

export function healInstant(projectileData, location, dimension, caster, glyphPower) {

    projectileData.particle_impact.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, location); });
    dimension.playSound(projectileData.soundLaunch, location, { volume: 100 });

    let damage = projectileData.damage;
    if (caster != undefined) damage = damage + projectileData.spellpower * (SPELL_PLAYERS.get(caster.id).spellPower) + projectileData.spellpower * glyphPower;
    damage = damage * config.SCALING;

    for (let entity of mc.world.getDimension(dimension.id).getEntities({ location: location, families: ["undead"], excludeFamilies: ["projectile", "prop", "npc", "inanimate"], maxDistance: projectileData.radius })) {
        entity.applyDamage(damage, { cause: mc.EntityDamageCause.contact, damagingEntity: caster });
        entity.applyKnockback(entity.location.x - location.x, entity.location.z - location.z, projectileData.knockback_horizontal, projectileData.knockback_vertical);

        if (projectileData.particle_hit != undefined) projectileData.particle_hit.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.getHeadLocation()) });
    }

    for (let entity of mc.world.getDimension(dimension.id).getEntities({ location: location, families: ["mob"], excludeFamilies: ["projectile", "prop", "npc", "inanimate", "undead"], maxDistance: projectileData.radius })) {
        increaseHealth(entity, damage);
        if (projectileData.particle_hit != undefined) projectileData.particle_hit.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.getHeadLocation()) });
    }

    for (let entity of mc.world.getDimension(dimension.id).getEntities({ location: location, families: ["player"], excludeFamilies: ["projectile", "prop", "npc", "inanimate"], maxDistance: projectileData.radius })) {
        increaseHealth(entity, damage);
        if (projectileData.particle_hit != undefined) projectileData.particle_hit.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.getHeadLocation()) });
    }
}

function increaseHealth(entity, amount) {
    let health = entity.getComponent('health');
    if (health.currentValue < health.defaultValue) health.setCurrentValue(health.currentValue + amount);
}