import { assert } from "../../../../core/assert.js";
import { BitSet } from "../../../../core/binary/BitSet.js";
import { BehaviorStatus } from "../BehaviorStatus.js";
import { CompositeBehavior } from "./CompositeBehavior.js";

/**
 *
 * @enum {number}
 */
export const ParallelBehaviorPolicy = {
    RequireOne: 0,
    RequireAll: 1
};

/**
 * Executes all contained behaviors in parallel.
 *
 * @example
 *  ParallelBehavior.from([
 *      ActionBehavior.from(()=> console.log("A")),
 *      ActionBehavior.from(()=> console.log("B")),
 *      ActionBehavior.from(()=> console.log("C")),
 *  ]); // will print A, B and C in console in a single tick
 *
 * @author Alex Goldring
 * @copyright Company Named Limited (c) 2026
 */
export class ParallelBehavior extends CompositeBehavior {
    /**
     *
     * @param {ParallelBehaviorPolicy} successPolicy
     * @param {ParallelBehaviorPolicy} failurePolicy
     */
    constructor(successPolicy, failurePolicy) {
        super();

        assert.enum(successPolicy, ParallelBehaviorPolicy, 'successPolicy');
        assert.enum(failurePolicy, ParallelBehaviorPolicy, 'failurePolicy');

        /**
         * @private
         * @type {ParallelBehaviorPolicy}
         */
        this.successPolicy = successPolicy;

        /**
         * @private
         * @type {ParallelBehaviorPolicy}
         */
        this.failurePolicy = failurePolicy;

        /**
         * Which child behaviors are currently running?
         * @readonly
         * @private
         * @type {BitSet}
         */
        this.activeSet = new BitSet();

        /**
         * @private
         * @type {number}
         */
        this.successCount = 0;
        /**
         * @private
         * @type {number}
         */
        this.failureCount = 0;
    }

    /**
     *
     * @return {ParallelBehaviorPolicy}
     */
    getSuccessPolicy() {
        return this.successPolicy;
    }

    /**
     *
     * @param {ParallelBehaviorPolicy|number} policy
     */
    setSuccessPolicy(policy) {
        assert.enum(policy, ParallelBehaviorPolicy, 'policy');
        this.successPolicy = policy;
    }

    /**
     *
     * @return {ParallelBehaviorPolicy}
     */
    getFailurePolicy() {
        return this.failurePolicy;
    }

    /**
     *
     * @param {ParallelBehaviorPolicy|number} policy
     */
    setFailurePolicy(policy) {
        assert.enum(policy, ParallelBehaviorPolicy, 'policy');
        this.failurePolicy = policy;
    }

    /**
     *
     * @param {number} timeDelta
     * @returns {BehaviorStatus|number}
     */
    tick(timeDelta) {

        const activeSet = this.activeSet;

        /**
         *
         * @type {Behavior[]}
         */
        const children = this.__children;

        const numChildren = children.length;

        let i;

        for (i = 0; i < numChildren; i++) {
            if (!activeSet.get(i)) {
                continue;
            }

            const child = children[i];

            const status = child.tick(timeDelta);

            if (status === BehaviorStatus.Succeeded) {
                activeSet.set(i, false);

                this.successCount++;

                child.finalize();

                if (this.successPolicy === ParallelBehaviorPolicy.RequireOne) {

                    this.__finalizeActiveChildren();

                    return BehaviorStatus.Succeeded;

                }

            } else if (status === BehaviorStatus.Failed) {
                activeSet.set(i, false);

                this.failureCount++;

                child.finalize();

                if (this.failurePolicy === ParallelBehaviorPolicy.RequireOne) {

                    this.__finalizeActiveChildren();

                    return BehaviorStatus.Failed;

                } else if (this.successPolicy === ParallelBehaviorPolicy.RequireAll) {

                    this.__finalizeActiveChildren();

                    return BehaviorStatus.Failed;

                }

            }
        }

        if (this.successCount === numChildren && this.successPolicy === ParallelBehaviorPolicy.RequireAll) {

            return BehaviorStatus.Succeeded;

        } else if (this.failureCount === numChildren && this.failurePolicy === ParallelBehaviorPolicy.RequireAll) {

            return BehaviorStatus.Failed;

        } else if ((this.failureCount + this.successCount) === numChildren) {

            // this should never happen, we should fail before this
            return BehaviorStatus.Failed;

        } else {

            return BehaviorStatus.Running;

        }
    }

    initialize(context) {
        this.successCount = 0;
        this.failureCount = 0;

        const children = this.__children;
        const numChildren = children.length;

        for (let i = 0; i < numChildren; i++) {
            const behavior = children[i];

            behavior.initialize(context);

            this.activeSet.set(i, true);
        }

        super.initialize(context);
    }

    /**
     *
     * @private
     */
    __finalizeActiveChildren() {
        const children = this.__children;

        const activeSet = this.activeSet;

        for (let i = activeSet.nextSetBit(0); i !== -1; i = activeSet.nextSetBit(i + 1)) {
            const behavior = children[i];

            behavior.finalize();
        }

    }


    finalize() {
        //finalize remaining active behaviours

        this.__finalizeActiveChildren();

        super.finalize();
    }

    /**
     * Default policies are fine for most use cases.
     * @param {Behavior[]} children
     * @param {ParallelBehaviorPolicy} [success] how should successful completion be determined?
     * @param {ParallelBehaviorPolicy} [failure] how should failing completion be determined
     * @returns {ParallelBehavior}
     */
    static from(
        children,
        success = ParallelBehaviorPolicy.RequireAll,
        failure = ParallelBehaviorPolicy.RequireOne
    ) {
        const r = new ParallelBehavior(success, failure);

        r.addChildren(children);

        return r;
    }
}

/**
 * @readonly
 * @type {boolean}
 */
ParallelBehavior.prototype.isParallelBehavior = true;

/**
 * @readonly
 * @type {string}
 */
ParallelBehavior.typeName = "ParallelBehavior";

