import BaseMachine from '../slotMachines/BaseMachine';
import { CanBeHighlighted, CanBeLanded, Entity } from '../types/Entity';
import { ITrackEntry } from '../types/ITrackEntry';
import AnimationProgressHook from '../utils/hooks/AnimationProgressHook';
import HooksChain from '../utils/hooks/HooksChain';
import BaseSystem from './BaseSystem';

export type HighlightLandedSymbolSystemCompatibleEntity = Entity
& CanBeLanded
& CanBeHighlighted;

/** Configuration settings for the reel spinning behavior in the ReelsSpinSystem. */
export interface HighlightLandedSymbolSystemSettings
{
    key: string,
    loop: boolean,
}

export default class HighlightLandedSymbolSystem extends BaseSystem<HighlightLandedSymbolSystemSettings>
{
    private state: 'stopped' | 'watching' = 'stopped';
    private hooks: HooksChain[] = [];
    private animatingIds: string[] = [];

    static defaultSettings: HighlightLandedSymbolSystemSettings = {
        key: '',
        loop: false,
    };

    /** Static list of flags that the machine should provide. Subclasses can override to specify their own requirements. */
    static override requiredFlags: string[] = [];

    /** Static list of maps that the machine should provide. Subclasses can override to specify their own requirements. */
    static override requiredMaps: string[] = ['symbols'];

    /** Static list of inputs that the machine should provide. Subclasses can override to specify their own requirements. */
    static override requiredInputs: string[] = [];

    /** Static list of methods that the machine should provide. Subclasses can override to specify their own requirements. */
    static override requiredMethods: string[] = [];

    /** Static list of utils that the machine should provide. Subclasses can override to specify their own requirements. */
    static override requiredUtils: string[] = [];

    constructor(machine: BaseMachine)
    {
        super(machine);
        this.setSettings(HighlightLandedSymbolSystem.defaultSettings);
    }

    onEnter(): void
    {
        this.state = 'watching';
        this.hooks = [];
        this.animatingIds = [];
    }

    onLeave(): void
    {
        this.resetHighlightedFlags();
    }

    update(dt: number): void
    {
        if (this.state !== 'watching') return;
        if (this.isStopConditionMet())
        {
            this.state = 'stopped';

            return;
        }
        this.watchTargetSymbols();
        this.processHooks(dt);
    }

    private isStopConditionMet()
    {
        const everySymbolIsLanded = this.machine.maps.symbols.every((id: string) =>
        {
            if (!id) return false;
            const symbol: HighlightLandedSymbolSystemCompatibleEntity = this.machine.getChildByName(id);

            if (!symbol) return false;

            return symbol.isLanded;
        });

        const everyTargetSymbolIsHighlighted = this.machine.maps.symbols.every((id: string) =>
        {
            if (!id) return false;
            const symbol: HighlightLandedSymbolSystemCompatibleEntity = this.machine.getChildByName(id);

            if (!symbol) return false;
            if (symbol.key !== this.settings.key) return true;

            return symbol.isHighlighted;
        });

        const hooksAreCompleted = this.hooks.length === 0;

        return everyTargetSymbolIsHighlighted && everySymbolIsLanded && hooksAreCompleted;
    }

    private watchTargetSymbols()
    {
        this.machine.maps.symbols.forEach((id: string) =>
        {
            if (!id) return;
            const entity: HighlightLandedSymbolSystemCompatibleEntity = this.machine.getChildByName(id);

            if (!entity) return;
            if (entity.key !== this.settings.key) return;
            if (!entity.isLanded) return;
            if (entity.isHighlighted) return;
            if (this.animatingIds.includes(entity.name)) return;
            this.animatingIds.push(id);
            const chain = this.createChain(entity);

            this.hooks.push(chain);
        });
    }

    private createChain(entity: HighlightLandedSymbolSystemCompatibleEntity)
    {
        return new HooksChain()
            .addHook(new AnimationProgressHook((): ITrackEntry => entity.highlight()))
            .setOnComplete(() => entity.isHighlighted = true);
    }

    private processHooks(dt: number)
    {
        this.hooks = this.hooks.filter((hook) =>
        {
            hook.update(dt);

            return !hook.isComplete();
        });
    }

    private resetHighlightedFlags()
    {
        this.animatingIds.forEach((id: string) =>
        {
            const e: HighlightLandedSymbolSystemCompatibleEntity = this.machine.getChildByName(id);

            e.isHighlighted = false;
        });
    }
}
