import { noop } from "../../../../../src/core/function/noop.js";
import ObservedValue from "../../../../../src/core/model/ObservedValue.js";
import {
    ParticleEmitterFlag
} from "../../../../../src/engine/graphics/particles/particular/engine/emitter/ParticleEmitterFlag.js";
import { ParticleLayer } from "../../../../../src/engine/graphics/particles/particular/engine/emitter/ParticleLayer.js";
import {
    SimulationStepDefinition
} from "../../../../../src/engine/graphics/particles/particular/engine/simulator/SimulationStepDefinition.js";
import {
    SimulationStepType
} from "../../../../../src/engine/graphics/particles/particular/engine/simulator/SimulationStepType.js";
import { BlendingType } from "../../../../../src/engine/graphics/texture/sampler/BlendingType.js";
import { NativeListController } from "../../../../../src/view/controller/controls/NativeListController.js";
import EmptyView from "../../../../../src/view/elements/EmptyView.js";
import DatGuiController from "../DatGuiController.js";
import ParticleLayerController from "./ParticleLayerController.js";


/**
 *
 * @param {*} value
 * @param {Object} enumerable
 * @returns {string}
 */
export function enumNameByValue(value, enumerable) {
    return Object.keys(enumerable)[Object.values(enumerable).indexOf(value)];
}

/**
 *
 * @param {ParticleEmitter} emitter
 * @param {ParticleEmitterSystem} system
 * @param {function} op
 * @param {*} [arg0]
 */
function applyEmitterChanges(emitter, system, op, arg0) {
    const particleEngine = system.particleEngine;

    const isSleeping = emitter.getFlag(ParticleEmitterFlag.Sleeping);

    //re-add the emitter
    particleEngine.remove(emitter);

    if (system.renderLayer.visibleSet.contains(emitter.mesh)) {
        //remove from visible layer if present
        system.renderLayer.visibleSet.forceRemove(emitter.mesh);
    }

    //execute operation
    op(emitter, arg0);

    //clear flags
    emitter.clearFlag(ParticleEmitterFlag.Built | ParticleEmitterFlag.Initialized);

    //update internal state
    emitter.registerLayerParameters();
    emitter.build();
    emitter.initialize();
    emitter.computeBoundingBox();

    particleEngine.add(emitter);

    emitter.writeFlag(ParticleEmitterFlag.Sleeping, isSleeping);
}

export class ParticleEmitterController extends EmptyView {
    /**
     *
     * @param {ParticleEmitterSystem} particleEmitterSystem
     * @constructor
     */
    constructor(particleEmitterSystem) {
        super();

        this.model = new ObservedValue(null);

        const self = this;


        function applyChanges() {
            const emitter = self.model.getValue();

            if (emitter !== null) {
                applyEmitterChanges(emitter, particleEmitterSystem, noop);
            }
        }

        /**
         *
         * @param {function(ParticleEmitter)} op
         * @param {v} [v]
         */
        function makeChange(op, v) {
            const emitter = self.model.getValue();

            if (emitter !== null) {
                applyEmitterChanges(emitter, particleEmitterSystem, op, v);
            }
        }

        /**
         *
         * @param {function(ParticleEmitter)} op
         * @returns {function(...[*]=)}
         */
        function mutator(op) {
            return function (arg0) {
                makeChange(op, arg0);
            }
        }


        /**
         *
         * @param {ParticleEmitter} emitter
         * @param oldEmitter
         */
        function modelSet(emitter, oldEmitter) {

            self.removeAllChildren();

            if (emitter !== null) {

                const emitterSurrogate = {
                    preWarm: emitter.getFlag(ParticleEmitterFlag.PreWarm),
                    depthRead: !emitter.getFlag(ParticleEmitterFlag.DepthReadDisabled),
                    depthSoft: !emitter.getFlag(ParticleEmitterFlag.DepthSoftDisabled),
                    velocityAlign: emitter.getFlag(ParticleEmitterFlag.AlignOnVelocity),
                    blendingMode: enumNameByValue(emitter.blendingMode, BlendingType),
                    update: function () {
                        applyChanges();
                    }
                };


                const dat = new DatGuiController();

                self.addChild(dat);


                dat.addControl(emitterSurrogate, 'preWarm').onChange(mutator((emitter, value) => {

                    /**
                     * @type {ParticleEmitter}
                     */
                    emitter.writeFlag(ParticleEmitterFlag.PreWarm, value);

                }));

                dat.addControl(emitterSurrogate, 'depthRead').onChange(mutator((emitter, value) => {

                    /**
                     * @type {ParticleEmitter}
                     */
                    emitter.writeFlag(ParticleEmitterFlag.DepthReadDisabled, !value);

                }));

                dat.addControl(emitterSurrogate, 'depthSoft').onChange(mutator((emitter, value) => {
                    /**
                     * @type {ParticleEmitter}
                     */
                    emitter.writeFlag(ParticleEmitterFlag.DepthSoftDisabled, !value);

                }));

                dat.addControl(emitterSurrogate, 'velocityAlign').onChange(mutator((emitter, value) => {

                    /**
                     * @type {ParticleEmitter}
                     */
                    emitter.writeFlag(ParticleEmitterFlag.AlignOnVelocity, value);

                }));

                dat.addControl(emitterSurrogate, 'blendingMode', Object.keys(BlendingType)).onChange(mutator((emitter, blendModeName) => {

                    emitter.blendingMode = BlendingType[blendModeName];

                }));

                dat.addControl(emitterSurrogate, 'update').name('Apply Changes');

                self.addChild(new NativeListController({
                    model: emitter.layers,
                    elementViewFactory(layer) {
                        const c = new ParticleLayerController(makeChange);

                        c.model.set(layer);

                        return c;
                    },
                    elementFactory() {
                        const r = new ParticleLayer();

                        r.steps.add(SimulationStepDefinition.from(SimulationStepType.FixedPhysics, []))

                        return r;
                    },
                    operationAdd(list, layer) {
                        makeChange(emitter => {
                            emitter.addLayer(layer);
                        });
                    }
                }))

            }
        }

        this.model.onChanged.add(modelSet);
    }
}
