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

import { deleteProjectile, registerProjectile } from '../lib/projectile_tracker';
import { SPELL_PLAYERS } from '../pb_scraft_player';
import { beamBetweenLocations } from '../lib/beam_utils';


export default class SpellChannel {

    player;
    spellPlayer;
    cooldown;
    name;
    manacost;
    castTime;
    soundCast;
    projectileData;

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

    totalTime;
    spellData;

    glyphPower = 0;

    constructor(player, spellData, itemUse) {
        this.player = player;
        this.spellPlayer = SPELL_PLAYERS.get(player.id)

        this.name = spellData.identifier;
        this.manacost = spellData.manacost;
        this.castTime = spellData.castTime;;
        this.cooldown = spellData.cooldown;
        this.soundCast = spellData.soundCast;
        this.projectileData = config.EFFECT_LIST.find((f) => f.identifier == spellData.projectile);

        this.spellData = spellData;
        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);
        if (this.isCharged) this.trigger();
    }

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

    release() {
        this.isActive = false;
        lib.runCooldown(this.player);
    }

    trigger() {
        if (this.spellPlayer.manaCurrent <= this.manacost) {
            this.release();
            return;
        }
        this.spellPlayer.consumeMana(this.manacost);

        const dimension = this.player.dimension;
        let view_vector = this.player.getViewDirection();
        const location = this.player.getHeadLocation();

        if (this.projectileData.type == "projectile") {

            const impulse = this.projectileData.impulse;
            const projectile = mc.world.getDimension(dimension.id).spawnEntity(this.projectileData.identifier, { x: location.x + view_vector.x * 2, y: location.y + 0.75, z: location.z + view_vector.z * 2 });
            if (this.projectileData.particle_track != undefined) registerProjectile(projectile, this.projectileData);

            const component = projectile.getComponent("minecraft:projectile");
            component.owner = this.player;

            projectile.addEffect('slow_falling', 2 * mc.TicksPerSecond, { amplifier: 1, showParticles: false });
            component.shoot({ x: view_vector.x * impulse, y: view_vector.y * impulse, z: view_vector.z * impulse });
            dimension.playSound(this.projectileData.soundLaunch, location);
            return;
        }
        if (this.projectileData.type == "cone") {

            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.projectileData.particle_cast.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, location, mvm); });

            let damage = this.projectileData.damage + this.projectileData.spellpower * this.spellPlayer.spellPower + this.projectileData.spellpower * this.glyphPower;
            damage = damage * config.SCALING;
            view_vector = { x: view_vector.x, y: 0, z: view_vector.z };

            try {
                for (let entity of lib.targetMobMonster(location, dimension.id, this.projectileData.radius)) {
                    if (lib.isEntityinCone(location, entity, view_vector, this.projectileData.angle)) {
                        entity.applyDamage(damage, { cause: mc.EntityDamageCause.contact, damagingEntity: this.player });
                        this.projectileData.particle_impact.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.location); });
                        dimension.playSound(this.projectileData.soundImpact, entity.location);
                        entity.applyKnockback(entity.location.x - location.x, entity.location.z - location.z, this.projectileData.knockback_horizontal, this.projectileData.knockback_vertical);
                    }
                }

                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: this.projectileData.radius })) {
                        if (entity != caster) {
                            if (lib.isEntityinCone(location, entity, view_vector, this.projectileData.angle)) {
                                entity.applyDamage(pvp_damage, { cause: mc.EntityDamageCause.contact, damagingEntity: this.player });
                                this.projectileData.particle_impact.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, entity.location); });
                                dimension.playSound(this.projectileData.soundImpact, entity.location);
                                entity.applyKnockback(entity.location.x - location.x, entity.location.z - location.z, this.projectileData.knockback_horizontal, this.projectileData.knockback_vertical);
                            }
                        }
                    }
                }
            }
            catch (error) { }
            return;
        }

        if (this.projectileData.type == "raycast") {

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

            //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;

            //check for first impact on entity
            let entity_raycast_hit = dimension.getEntitiesFromRay(location, view_vector, { families: ["mob"], excludeFamilies: ["projectile", "prop", "npc", "spell", "inanimate"], maxDistance: range });
            entity_raycast_hit = entity_raycast_hit.concat(dimension.getEntitiesFromRay(location, view_vector, { families: ["monster"], excludeFamilies: ["projectile", "prop", "npc", "spell", "inanimate"], maxDistance: range }));
            if (entity_raycast_hit.length > 0) {
                const distance_entity = entity_raycast_hit[0].distance;
                entity_hit = entity_raycast_hit[0].entity;
                if (distance > distance_entity) distance = distance_entity;
            }
            //check for first impact on player
            if (config.PVP.enabled) {
                let entity_raycast_hit_pvp = dimension.getEntitiesFromRay(location, view_vector, { families: ["player"], maxDistance: range });
                if (entity_raycast_hit_pvp.length > 0) {
                    if (entity_raycast_hit_pvp[0].entity != this.player) {
                        const distance_entity = entity_raycast_hit_pvp[0].distance;
                        entity_hit = entity_raycast_hit_pvp[0].entity;
                        if (distance > distance_entity) distance = distance_entity;
                    }
                }
            }
            const target_location = { x: (location.x + view_vector.x * distance), y: (location.y + view_vector.y * distance), z: (location.z + view_vector.z * distance) };

            //vfx
            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.projectileData.particle_track != undefined) {
                const vector_right = lib.rotateVector2D(view_vector, 90);
                const beamstartlocation = {x: location.x + vector_right.x*0.5, y: location.y+0.0, z: location.z + vector_right.z*0.5 };
                beamBetweenLocations(beamstartlocation, target_location, dimension.id, this.projectileData.particle_track);
            }

            if (this.projectileData.particle_impact != undefined) {
                this.projectileData.particle_impact.forEach(element => { mc.world.getDimension(dimension.id).spawnParticle(element, target_location); });
            }

            dimension.playSound(this.projectileData.soundLaunch, location, {pitch: 1.1});


            //deal damage
            let damage = this.projectileData.damage + this.projectileData.spellpower * this.spellPlayer.spellPower + this.projectileData.spellpower * this.glyphPower;
            damage = damage * config.SCALING;

            for (let entity of lib.targetMobMonster(target_location, dimension.id, 2)) {
                entity.applyDamage(damage, { cause: mc.EntityDamageCause.contact, damagingEntity: this.player });
                dimension.playSound(this.projectileData.soundImpact, entity.location);
                entity.applyKnockback(entity.location.x - location.x, entity.location.z - location.z, this.projectileData.knockback_horizontal, this.projectileData.knockback_vertical);

            }

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

    }

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