package eu.jonahbauer.wizard.client.libgdx.actors;

import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.*;
import eu.jonahbauer.wizard.client.libgdx.util.AnimationTimings;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.screens.GameScreen;
import eu.jonahbauer.wizard.common.model.Card;
import lombok.Data;

import java.util.*;

import static com.badlogic.gdx.scenes.scene2d.actions.Actions.*;

public class CardStack extends Group {
    private static final float EXPANDED_ROTATION_DEVIATION = 10;
    private static final float COLLAPSED_ROTATION_DEVIATION = 60;
    private static final float COLLAPSED_POSITION_DEVIATION = 15;

    private final Random random = new Random();
    private final List<Entry> cards = new ArrayList<>();

    private final Actor hover = new Actor();
    private boolean expanded;

    public CardStack() {
        this.addActor(hover);
        this.addListener(new InputListener() {

            @Override
            public void exit(InputEvent event, float x, float y, int pointer, Actor toActor) {
                if (pointer == -1 && event.getTarget() == hover && expanded) {
                    expanded = false;
                    cards.forEach(Entry::collapse);
                }
            }

            @Override
            public void enter(InputEvent event, float x, float y, int pointer, Actor fromActor) {
                if (pointer == -1 && event.getTarget() == hover && !expanded) {
                    expanded = true;
                    cards.forEach(Entry::expand);
                }
            }
        });
    }

    public void add(GameScreen.Seat seat, CardActor card) {
        var entry = new Entry(
                card,
                seat,
                (float) random.nextGaussian((WizardGame.WIDTH - card.getWidth()) / 2, COLLAPSED_POSITION_DEVIATION),
                (float) random.nextGaussian((WizardGame.HEIGHT - card.getHeight()) / 2, COLLAPSED_POSITION_DEVIATION),
                (float) random.nextGaussian(0, COLLAPSED_ROTATION_DEVIATION),
                (float) random.nextGaussian(0, EXPANDED_ROTATION_DEVIATION)
        );
        super.addActor(card);
        if (expanded) entry.expand();
        else entry.collapse();
        cards.add(entry);
    }

    public void add(GameScreen.Seat seat, Card card, TextureAtlas atlas) {
        var actor = new CardActor(card, atlas);

        var x = (float) random.nextGaussian((WizardGame.WIDTH - actor.getWidth()) / 2, COLLAPSED_POSITION_DEVIATION);
        var y = (float) random.nextGaussian((WizardGame.HEIGHT - actor.getHeight()) / 2, COLLAPSED_POSITION_DEVIATION);
        var collapsedRotation = (float) random.nextGaussian(0, COLLAPSED_ROTATION_DEVIATION);
        var expandedRotation = (float) random.nextGaussian(0, EXPANDED_ROTATION_DEVIATION);

        if (expanded) {
            actor.setPosition(seat.getFrontX(), seat.getFrontY());
            actor.setRotation(expandedRotation);
        } else {
            actor.setPosition(x, y);
            actor.setRotation(collapsedRotation);
        }

        var entry = new Entry(actor, seat, x, y, collapsedRotation, expandedRotation);
        super.addActor(actor);
        cards.add(entry);
    }

    @Override
    protected void childrenChanged() {
        hover.toFront();
    }

    @Override
    public void clearChildren(boolean unfocus) {
        super.clearChildren(unfocus);
        cards.clear();
        addActor(hover);
    }

    public List<CardActor> removeAll() {
        var out = cards.stream().map(Entry::getActor).toList();
        clearChildren(true);
        return out;
    }

    @Override
    public Actor removeActorAt(int index, boolean unfocus) {
        var actor = super.removeActorAt(index, unfocus);
        cards.remove(index);
        return actor;
    }

    public void setHoverBounds(float x, float y, float width, float height) {
        hover.setBounds(x, y, width, height);
    }

    @Override
    @Deprecated
    public void addActor(Actor actor) {
        super.addActor(actor);
    }

    @Override
    @Deprecated
    public void addActorAfter(Actor actorAfter, Actor actor) {
        super.addActorAfter(actorAfter, actor);
    }

    @Override
    @Deprecated
    public void addActorAt(int index, Actor actor) {
        super.addActorAt(index, actor);
    }

    @Override
    @Deprecated
    public void addActorBefore(Actor actorBefore, Actor actor) {
        super.addActorBefore(actorBefore, actor);
    }

    @Data
    private static class Entry {
        private final CardActor actor;
        private final GameScreen.Seat seat;
        private final float x;
        private final float y;
        private final float rotation;
        private final float expandedRotation;
        private Action action;

        public void expand() {
            if (action != null) actor.removeAction(action);

            action = parallel(
                    moveTo(
                            seat.getFrontX() - actor.getWidth() / 2,
                            seat.getFrontY() - actor.getHeight() / 2,
                           AnimationTimings.STACK_EXPAND
                    ),
                    rotateTo(expandedRotation, AnimationTimings.STACK_EXPAND)
            );

            actor.addAction(action);
        }

        public void collapse() {
            if (action != null) actor.removeAction(action);

            action = parallel(
                    moveTo(x, y, AnimationTimings.STACK_COLLAPSE),
                    rotateTo(rotation, AnimationTimings.STACK_COLLAPSE)
            );

            actor.addAction(action);
        }
    }
}
