import { Container } from 'pixi.js';
import AnticipationFrame from '../entities/AnticipationFrame';
import ReelsMachine from '../slotMachines/ReelsMachine';
import AnimationProgressHook from '../utils/hooks/AnimationProgressHook';
import DelayHook from '../utils/hooks/DelayHook';
import HooksChain from '../utils/hooks/HooksChain';
import BaseSystem from './BaseSystem';

/** Configuration settings for the reel spinning behavior in the AnticipationSystem. */
export interface AnticipationAnimationsSystemSettings
{
    enabled: boolean,
    entityClass: typeof AnticipationFrame
    zIndex: number,
    parent: Container,
}

/** This system is responsible to process reels anticipation animations */
export default class AnticipationAnimationsSystem extends BaseSystem<AnticipationAnimationsSystemSettings>
{
    static defaultSettings: AnticipationAnimationsSystemSettings = {
        enabled: true,
        entityClass: AnticipationFrame,
        parent: null,
        zIndex: 0,
    };

    static override requiredFlags: string[] = ['isSpinning'];
    static override requiredMaps: string[] = ['anticipatingReels'];
    static override requiredInputs: string[] = [];
    static override requiredMethods: string[] = [];
    static override requiredUtils: string[] = ['grid'];
    static override sharedSystemData: string[] = [];

    private frames: AnticipationFrame[] = [];
    private isCreated: boolean = false;
    private activeFrames: boolean[] = [];
    private hooks: HooksChain[] = [];
    public state: 'active' | 'stopped';

    constructor(machine: ReelsMachine)
    {
        super(machine);
        this.setSettings(AnticipationAnimationsSystem.defaultSettings);
    }

    onEnter(): void
    {
        if (!this.isCreated)
        {
            this.createFrames();
        }
        this.activeFrames = new Array(this.frames.length).fill(false);
    }

    onLeave(): void
    {
        this.frames.forEach((frame) => frame.visible = false);
    }

    update(_dt: number): void
    {
        this.hooks = this.hooks.filter((h) =>
        {
            h.update(_dt);

            return !h.isComplete();
        });
        this.activeFrames.forEach((isActive, i) =>
        {
            if (!isActive && this.machine.maps.anticipatingReels[i])
            {
                this.handleStartAnticipation(i);
            }
            if (isActive && !this.machine.maps.anticipatingReels[i])
            {
                this.handleStopAnticipation(i);
            }
        });
        if (this.activeFrames.every((v) => !v))
        {
            this.state = 'stopped';
        }
        else
        {
            this.state = 'active';
        }
    }

    private handleStartAnticipation(frameIndex: number): void
    {
        this.frames[frameIndex].visible = true;
        this.frames[frameIndex].show();
        this.activeFrames[frameIndex] = true;
    }

    private handleStopAnticipation(frameIndex: number): void
    {
        const chain = new HooksChain()
            .addHook(new AnimationProgressHook(() =>
                this.frames[frameIndex].hide()))
            .addHook(new DelayHook(0.1))
            .setOnComplete(() =>
            {
                this.frames[frameIndex].visible = false;
            });

        this.activeFrames[frameIndex] = false;
        this.hooks.push(chain);
    }

    private createFrames()
    {
        const numberOfReels = this.machine.config.reels;

        for (let i = 0; i < numberOfReels; i++)
        {
            this.frames.push(this.createFrame(i));
        }
        this.isCreated = true;
    }

    private createFrame(reelIndex: number)
    {
        const frame = new this.settings.entityClass();

        this.getParent().addChild(frame);
        const sW = this.machine.utils.grid.getSlotWidth();
        const halfSW = sW * 0.5;
        const x = (sW * reelIndex) + halfSW;
        const y = this.machine.config.height * 0.5;

        frame.position.set(x, y);
        frame.zIndex = this.settings.zIndex;
        frame.visible = false;

        return frame;
    }

    private getParent()
    {
        return this.settings.parent || this.machine;
    }
}
