import { SerializationMetadata } from "../ecs/components/SerializationMetadata.js";
import Timer from "../ecs/components/Timer.js";
import Entity from "../ecs/Entity.js";
import { Transform } from "../ecs/transform/Transform.js";
import { whenAllEntitiesDestroyed, whenEntityDestroyed } from "../ecs/util/EntityBuilderUtils.js";
import { removeComponentsExcept } from "../ecs/util/removeComponentsExcept.js";
import { createSound, createTimer } from "../EntityCreator.js";
import Mesh from "../graphics/ecs/mesh/Mesh.js";
import Trail2D from "../graphics/ecs/trail2d/Trail2D.js";
import { Trail2DFlags } from "../graphics/ecs/trail2d/Trail2DFlags.js";
import { ParticleEmitter } from "../graphics/particles/particular/engine/emitter/ParticleEmitter.js";
import { ParticleEmitterFlag } from "../graphics/particles/particular/engine/emitter/ParticleEmitterFlag.js";
import { SequenceBehavior } from "../intelligence/behavior/composite/SequenceBehavior.js";
import { BehaviorComponent } from "../intelligence/behavior/ecs/BehaviorComponent.js";
import { ActionBehavior } from "../intelligence/behavior/primitive/ActionBehavior.js";
import { DelayBehavior } from "../intelligence/behavior/util/DelayBehavior.js";
import { stopEntityAndNotifyWhenStopped } from "./AnimatedActions.js";
import AnimationTrack from "./keyed2/AnimationTrack.js";
import AnimationTrackPlayback from "./keyed2/AnimationTrackPlayback.js";
import { playAnimationTrack } from "./playAnimationTrack.js";
import TransitionFunctions from "./TransitionFunctions.js";

/**
 *
 * @param {number} entity
 * @param {EntityComponentDataset} ecd
 * @returns {Promise}
 */
export function stopTrailAndNotifyOnceFinished(entity, ecd) {
    return new Promise((resolve, reject) => {
        if (!ecd.entityExists(entity)) {
            resolve();
            return;
        }

        const trail = ecd.getComponent(entity, Trail2D);

        if (trail === undefined) {
            resolve();
            return;
        }

        trail.clearFlag(Trail2DFlags.Spawning);

        new Entity()
            .add(BehaviorComponent.from(SequenceBehavior.from([
                DelayBehavior.fromJSON({ value: trail.maxAge }),
                new ActionBehavior(resolve)
            ])))
            .build(ecd);
    });
}

/**
 *
 * @param {number} entity
 * @param {EntityComponentDataset} ecd
 * @returns {Promise}
 */
export function stopEmitterAndNotifyOnceFinished(entity, ecd) {
    return new Promise(function (resolve, reject) {
        if (!ecd.entityExists(entity)) {
            console.warn(`Entity ${entity} doesn't exist`);

            resolve();
            return;
        }

        /**
         *
         * @type {ParticleEmitter}
         */
        const emitter = ecd.getComponent(entity, ParticleEmitter);

        if (emitter === undefined) {
            console.warn(`Entity ${entity} doesn't have ParticleEmitter component`);

            resolve();
            return;
        }

        //stop emission
        emitter.clearFlag(ParticleEmitterFlag.Emitting);


        //figure out how long the emitter should stay alive
        const maxLife = emitter.computeMaxEmittingParticleLife();

        const entityBuilder = new Entity();
        //create a timer to remove emitter
        const timer = new Timer();
        timer.timeout = maxLife;
        timer.actions.push(function () {
            //confirm that entity still exists
            if (!ecd.entityExists(entity)) {
                //nothing to do
                return;
            }

            //confirm that it's the same entity
            const component = ecd.getComponent(entity, ParticleEmitter);

            if (component !== emitter) {
                //entity seems to have changed, do nothing
                return;
            }
        }, function () {
            //kill self
            entityBuilder.destroy();
            resolve();
        });

        entityBuilder.add(timer);

        entityBuilder.build(ecd);
    });
}


/**
 *
 * @param {number} entity
 * @param {EntityComponentDataset} ecd
 * @returns {Promise}
 */
export function shutdownParticleEmitter(entity, ecd) {
    const promise = stopEmitterAndNotifyOnceFinished(entity, ecd);


    const MAX_PARTICLE_LIFE = 10;

    /**
     *
     * @type {ParticleEmitter}
     */
    const particleEmitter = ecd.getComponent(entity, ParticleEmitter);

    if (particleEmitter !== undefined) {

        //destroy any particles that have very long lifespan

        particleEmitter.traverseLayers((layer) => {
            const maxParticleLife = layer.particleLife.max;

            if (layer.emissionRate <= 0 && layer.emissionImmediate <= 0) {
                //skip layers with no emission
                return;
            }

            if (maxParticleLife > MAX_PARTICLE_LIFE) {
                particleEmitter.destroyParticlesFromLayer(layer);
            }
        });
    }

    return promise;
}

/**
 *
 * @param entity
 * @param {EntityComponentDataset} ecd
 * @param {ParticleEmitter} emitter
 * @param {String} soundEffect
 * @param {number} [timeout]
 * @returns {Promise}
 */
export function removeEntityWithEffect({ entity, ecd, emitter, soundEffect, timeout }) {
    /**
     *
     * @type {Transform}
     */
    const transform = ecd.getComponent(entity, Transform);

    //prevent further interactions
    removeComponentsExcept(ecd, entity, [Mesh, ParticleEmitter, Trail2D, Transform]);

    //make entity volatile so it does not end up in game saves
    ecd.addComponentToEntity(entity, SerializationMetadata.Transient);

    return new Promise((resolve, reject) => {

        const entityStopped = stopEntityAndNotifyWhenStopped(entity, ecd);


        const animationTrack = new AnimationTrack(['scale']);
        animationTrack.addKey(0, [1]);
        animationTrack.addKey(0.05, [1]);
        animationTrack.addKey(0.27, [0]);
        animationTrack.addTransition(0, TransitionFunctions.EaseOut);
        animationTrack.addTransition(1, TransitionFunctions.EaseOut);


        const originalScale = transform.scale.clone();

        const trackPlayback = new AnimationTrackPlayback(animationTrack, function (s) {
            transform.scale.copy(originalScale.clone().multiplyScalar(s));
        }, null);

        trackPlayback.on.ended.add(() => {
            //remove mesh if it exists since scale is 0
            ecd.removeComponentFromEntity(entity, Mesh);
        });

        const eAnimation = playAnimationTrack(trackPlayback, ecd);

        const entityRemoved = Promise.all([
            whenEntityDestroyed(eAnimation),
            entityStopped
        ])
            .then(() => {

                //check that the entity exists
                if (ecd.entityExists(entity)) {
                    ecd.removeEntity(entity);
                }

            });

        const t = new Transform();
        t.copy(transform);

        const emitterBuilder = new Entity();
        emitterBuilder.add(t).add(emitter);
        emitterBuilder.build(ecd);

        const timer = createTimer({
            timeout: emitter.layers.reduce((s, l) => {
                return Math.max(s, l.particleLife.max)
            }, 0),
            action() {
                emitterBuilder.destroy()
            }
        });

        const sound = createSound({
            position: transform.position,
            url: soundEffect,
            positioned: true
        });

        Promise.all([
            entityRemoved,
            whenAllEntitiesDestroyed([timer, sound])
        ])
            .then(resolve, reject);

        timer.build(ecd);
        sound.build(ecd);
    });

}
