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

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.graphics.Color;
import com.badlogic.gdx.graphics.g2d.TextureAtlas;
import com.badlogic.gdx.scenes.scene2d.Actor;
import com.badlogic.gdx.scenes.scene2d.Group;
import com.badlogic.gdx.scenes.scene2d.Touchable;
import com.badlogic.gdx.scenes.scene2d.ui.Container;
import com.badlogic.gdx.scenes.scene2d.ui.Label;
import com.badlogic.gdx.scenes.scene2d.ui.TextButton;
import com.badlogic.gdx.scenes.scene2d.ui.VerticalGroup;
import com.badlogic.gdx.scenes.scene2d.utils.ChangeListener;
import com.badlogic.gdx.scenes.scene2d.utils.Drawable;
import com.badlogic.gdx.scenes.scene2d.utils.TextureRegionDrawable;
import com.badlogic.gdx.utils.Align;
import eu.jonahbauer.wizard.client.libgdx.GameAtlas;
import eu.jonahbauer.wizard.client.libgdx.UiskinAtlas;
import eu.jonahbauer.wizard.client.libgdx.WizardGame;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardActor;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardStack;
import eu.jonahbauer.wizard.client.libgdx.actors.game.CardsGroup;
import eu.jonahbauer.wizard.client.libgdx.actors.game.PadOfTruth;
import eu.jonahbauer.wizard.client.libgdx.actors.game.overlay.*;
import eu.jonahbauer.wizard.client.libgdx.util.Pair;
import eu.jonahbauer.wizard.common.messages.observer.*;
import eu.jonahbauer.wizard.common.messages.server.GameMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import eu.jonahbauer.wizard.common.model.Card;
import lombok.Getter;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static eu.jonahbauer.wizard.client.libgdx.actions.MyActions.*;

public class GameScreen extends MenuScreen {
    private final WizardGame game;

    @Getter
    private TextureAtlas atlas;
    @Getter
    private Drawable overlay;

    private final List<UUID> players;

    private final UUID self;
    private final UUID session;
    private final Map<UUID, String> names;
    private final Map<Integer, Map<UUID, Integer>> scores = new HashMap<>();

    private int round = -1;
    private final Map<UUID, List<Card>> hands = new HashMap<>();
    private final Map<UUID, Integer> predictions = new HashMap<>();
    private final Map<UUID, List<List<Card>>> tricks = new HashMap<>();
    private Card.Suit trumpSuit;
    private Card trumpCard;

    private int trick = -1;
    private final List<Pair<UUID, Card>> stack = new ArrayList<>();
    private Pair<UUID, UserInputMessage.Action> activePlayer;

    private CardsGroup handCards;
    private CardStack cardStack;
    private PadOfTruth padOfTruth;
    @Getter
    private CardActor trumpCardActor;
    private VerticalGroup messages;

    private final Map<UUID, Seat> seats = new HashMap<>();
    private final Map<UUID, Label> nameLabels = new HashMap<>();

    public GameScreen(WizardGame game) {
        super(game);
        this.game = game;

        //<editor-fold desc="Sample Data" defaultstate="collapsed">
        this.session = UUID.randomUUID();
        this.names = new HashMap<>();

        int playerCount = 6;
        var players = new UUID[playerCount];
        for (int i = 0; i < playerCount; i++) {
            players[i] = UUID.randomUUID();
            names.put(players[i], "Player " + i);
        }

        this.self = players[0];
        this.players = Stream.of(players).sorted().toList();
        //</editor-fold>
    }

    @Override
    public void show() {
        super.show();
        atlas = new TextureAtlas(Gdx.files.internal(GameAtlas.$PATH));
        overlay = new TextureRegionDrawable(game.data.uiskinAtlas.findRegion(UiskinAtlas.WHITE)).tint(new Color(0,0,0,0.5f));

        seat();
        prepareLabels();

        handCards = new CardsGroup(Collections.emptyList(), atlas);
        handCards.setOnClickListener(card -> playCard(self, card.getCard()));
        var container = new Container<>(handCards);
        container.setPosition(360, 75);
        container.setSize(1200, CardActor.PREF_HEIGHT);
        container.fillX();

        messages = new VerticalGroup().columnCenter().bottom();
        messages.setPosition(360, 85 + CardActor.PREF_HEIGHT);
        messages.setSize(1200, 0);
        messages.setTouchable(Touchable.disabled);

        padOfTruth = new PadOfTruth(game.data.skin, new TextureRegionDrawable(atlas.findRegion(GameAtlas.PAD_OF_TRUTH)));
        padOfTruth.setPosition(1910 - 636f, 10);
        padOfTruth.setOrigin(636f, 0);

        cardStack = new CardStack();
        cardStack.setHoverBounds(0.5f * WizardGame.WIDTH - 50, 0.5f * WizardGame.HEIGHT - 50, 100, 100);

        setNames();

        Gdx.input.setInputProcessor(game.data.stage);
        game.data.stage.addActor(container);
        game.data.stage.addActor(cardStack);
        game.data.stage.addActor(padOfTruth);
        game.data.stage.addActor(messages);
        game.data.stage.addActor(debugButtons());
    }

    private Group debugButtons() {
        var trump = new TextButton("Trump", game.data.skin, "simple");
        trump.align(Align.left);
        trump.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                onMessage(new GameMessage(new TrumpMessage(
                        Card.values()[(int)(Math.random() * Card.values().length)],
                        Card.Suit.values()[(int)(Math.random() * Card.Suit.values().length)]
                )));
            }
        });

        var playCard = new TextButton("Play Card", game.data.skin, "simple");
        playCard.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                var hand = hands.get(self);
                if (hand != null && hand.size() > 0) {
                    onMessage(new GameMessage(new CardMessage(
                            self,
                            hand.get((int) (Math.random() * hand.size()))
                    )));
                }
            }
        });

        var deal = new TextButton("Deal", game.data.skin, "simple");
        deal.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                var hand = Stream.of(Card.values()).limit(20).toList();
                onMessage(new GameMessage(new HandMessage(self, hand)));
            }
        });

        var round = new TextButton("Round", game.data.skin, "simple");
        round.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                onMessage(new GameMessage(new StateMessage("starting_round")));
                round.setText("Round " + GameScreen.this.round);
            }
        });

        var scores = new TextButton("Scores", game.data.skin, "simple");
        scores.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                var scores = players.stream().collect(Collectors.toMap(p -> p, p -> (int) (Math.random() * 5) * 10));
                onMessage(new GameMessage(new ScoreMessage(scores)));
            }
        });

        var predictions = new TextButton("Predictions", game.data.skin, "simple");
        predictions.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                players.forEach(player -> onMessage(new GameMessage(new PredictionMessage(player, (int) (Math.random() * 5)))));
            }
        });

        var playOtherCard = new TextButton("Play Other Card", game.data.skin, "simple");
        playOtherCard.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                var card = Card.values()[(int) (Math.random() * Card.values().length)];
                UUID player;
                do {
                    player = players.get((int) (Math.random() * players.size()));
                } while (self.equals(player));
                onMessage(new GameMessage(new CardMessage(player, card)));
            }
        });

        var makePrediction = new TextButton("Make Prediction", game.data.skin, "simple");
        makePrediction.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                onMessage(new GameMessage(new UserInputMessage(self, UserInputMessage.Action.MAKE_PREDICTION, Long.MAX_VALUE)));
            }
        });

        var changePrediction = new TextButton("Change Prediction", game.data.skin, "simple");
        changePrediction.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                onMessage(new GameMessage(new UserInputMessage(self, UserInputMessage.Action.CHANGE_PREDICTION, Long.MAX_VALUE)));
            }
        });

        var chooseTrump = new TextButton("Choose Trump", game.data.skin, "simple");
        chooseTrump.addListener(new ChangeListener() {
            @Override
            public void changed(ChangeEvent event, Actor actor) {
                onMessage(new GameMessage(new UserInputMessage(self, UserInputMessage.Action.PICK_TRUMP, Long.MAX_VALUE)));
            }
        });

        var buttons = new VerticalGroup();
        buttons.top().left().columnLeft().setPosition(0, WizardGame.HEIGHT - buttons.getHeight());
        buttons.addActor(round);
        buttons.addActor(deal);
        buttons.addActor(trump);
        buttons.addActor(predictions);
        buttons.addActor(playCard);
        buttons.addActor(playOtherCard);
        buttons.addActor(scores);
        buttons.addActor(chooseTrump);
        buttons.addActor(makePrediction);
        buttons.addActor(changePrediction);
        return buttons;
    }

    @Override
    protected void renderBackground(float delta) {
        float scale = Math.max(
                game.data.extendViewport.getWorldWidth() / WizardGame.WIDTH,
                game.data.extendViewport.getWorldHeight() / WizardGame.HEIGHT
        );
        game.batch.setColor(1, 1, 1, 0.25f);
        game.batch.draw(game.data.background, 0,0, scale * WizardGame.WIDTH, scale * WizardGame.HEIGHT);
        game.batch.draw(
                game.data.title,
                (game.data.extendViewport.getWorldWidth() - game.data.title.getRegionWidth() * 0.75f) / 2f,
                (game.data.extendViewport.getWorldHeight() - game.data.title.getRegionHeight() * 0.75f) / 2f,
                0.75f * game.data.title.getRegionWidth(),
                0.75f * game.data.title.getRegionHeight()
        );
    }

    @Override
    protected void renderForeground(float delta) {}

    private void seat() {
        var count = players.size();
        Seat[] seats = switch (count) {
            case 3 -> new Seat[] {Seat.TOP_LEFT, Seat.TOP_RIGHT};
            case 4 -> new Seat[] {Seat.LEFT, Seat.TOP, Seat.RIGHT};
            case 5 -> new Seat[] {Seat.LEFT, Seat.TOP_LEFT, Seat.TOP_RIGHT, Seat.RIGHT};
            case 6 -> new Seat[] {Seat.LEFT, Seat.TOP_LEFT, Seat.TOP, Seat.TOP_RIGHT, Seat.RIGHT};
            default -> throw new AssertionError();
        };
        int index = players.indexOf(self);
        for (int i = 1; i < count; i++) {
            var player = players.get((index + i) % count);
            var seat = seats[i - 1];
            this.seats.put(player, seat);
        }
    }

    private void prepareLabels() {
        for (UUID player : players) {
            if (self.equals(player)) continue;
            var label = new Label("", game.data.skin);
            var seat = seats.get(player);
            label.setX(seat.getLabelX());
            label.setY(seat.getLabelY());
            label.setAlignment(seat.getLabelAlign());
            this.nameLabels.put(player, label);
            game.data.stage.addActor(label);
        }
    }

    private void setNames() {
        for (int i = 0; i < players.size(); i++) {
            var player = players.get(i);
            var name = names.get(player);
            padOfTruth.setName(i, name);
            if (!self.equals(player)) {
                nameLabels.get(player).setText(name);
            }
        }
    }

    private void startRound() {
        round++;
        tricks.clear();
        predictions.clear();

        trumpSuit = null;
        trumpCard = null;
        if (trumpCardActor != null) {
            trumpCardActor.remove();
        }

        trick = -1;

        handCards.clearChildren();
        hands.clear();

        cardStack.clearChildren();
        stack.clear();

        new StartRoundOverlay(this, round).show(game.data.stage);
    }

    private void startTrick() {
        trick++;

        setActivePlayer(null, null);

        cardStack.clearChildren();
        stack.clear();
    }

    private void finishTrick(UUID player, List<Card> cards) {
        stack.clear();
        tricks.computeIfAbsent(player, p -> new ArrayList<>()).add(cards);
        // TODO
    }

    private void setHand(UUID player, List<Card> cards) {
        var hand = hands.computeIfAbsent(player, p -> new ArrayList<>());
        hand.clear();
        hand.addAll(cards);

        if (self.equals(player)) {
            handCards.update(hand);
        }
    }

    private void setTrump(Card trumpCard, Card.Suit trumpSuit) {
        this.trumpCard = trumpCard;
        this.trumpSuit = trumpSuit;
        if (trumpCardActor != null) {
            trumpCardActor.remove();
        } else {
            trumpCardActor = new CardActor(Card.HIDDEN, atlas);
        }

        new TrumpOverlay(this, null, trumpCard, trumpSuit).show(game.data.stage);
    }

    private void addPrediction(UUID player, int prediction) {
        addMessage(names.get(player) + " predicted " + prediction + ".");
        predictions.put(player, prediction);
        padOfTruth.setPrediction(players.indexOf(player), round, prediction);
    }

    private void playCard(UUID player, Card card) {
        addMessage(names.get(player) + " played a card.");

        var handCard = switch (card) {
            case CHANGELING_JESTER, CHANGELING_WIZARD -> Card.CHANGELING;
            case CLOUD_BLUE, CLOUD_RED, CLOUD_GREEN, CLOUD_YELLOW -> Card.CLOUD;
            case JUGGLER_BLUE, JUGGLER_RED, JUGGLER_GREEN, JUGGLER_YELLOW -> Card.JUGGLER;
            default -> card;
        };

        if (hands.containsKey(player)) {
            hands.get(player).remove(handCard);
        }
        stack.add(Pair.of(player, card));

        CardActor actor;
        Seat seat;
        if (self.equals(player)) {
            actor = handCards.remove(handCard);
            seat = Seat.BOTTOM;
        } else {
            actor = new CardActor(card, atlas);
            seat = seats.get(player);
            actor.setPosition(seat.getX() - actor.getWidth() / 2, seat.getY() - actor.getHeight() / 2);
            actor.setRotation(seat.getAngle());
        }
        actor.setOrigin(actor.getWidth() / 2, actor.getHeight() / 2);
        cardStack.add(seat, actor);
    }

    private void addScores(Map<UUID, Integer> scores) {
        this.scores.put(round, scores);

        for (int i = 0; i < players.size(); i++) {
            UUID player = players.get(i);
            padOfTruth.setScore(i, round, scores.get(player));
        }
    }

    private void setActivePlayer(UUID player, UserInputMessage.Action action) {
        if (activePlayer != null && nameLabels.containsKey(activePlayer.getKey())) {
            nameLabels.get(activePlayer.getKey()).getStyle().fontColor = Color.WHITE;
        }
        if (player == null && action == null) {
            activePlayer = null;
        } else {
            activePlayer = Pair.of(player, action);
            if (nameLabels.containsKey(player)) {
                nameLabels.get(player).getStyle().fontColor = Color.RED;
            }
        }
    }

    private void addMessage(String text) {
        var label = new Label(text, game.data.skin);
        label.addAction(sequence(
                delay(1.5f),
                alpha(0, 0.25f),
                removeActor()
        ));
        messages.addActor(label);
    }

    @Override
    public void dispose() {
        super.dispose();
        if (atlas != null) atlas.dispose();
    }

    public void onMessage(ServerMessage serverMessage) {
        if (serverMessage instanceof GameMessage gameMessage) {
            var observerMessage = gameMessage.getObserverMessage();
            if (observerMessage instanceof CardMessage card) {
                playCard(card.getPlayer(), card.getCard());
            } else if (observerMessage instanceof TrickMessage trick) {
                finishTrick(trick.getPlayer(), trick.getCards());
            } else if (observerMessage instanceof HandMessage hand) {
                setHand(hand.getPlayer(), hand.getHand());
            } else if (observerMessage instanceof ScoreMessage score) {
                addScores(score.getPoints());
            } else if (observerMessage instanceof TrumpMessage trump) {
                setTrump(trump.getCard(), trump.getSuit());
            } else if (observerMessage instanceof PredictionMessage prediction) {
                addPrediction(prediction.getPlayer(), prediction.getPrediction());
            } else if (observerMessage instanceof UserInputMessage userInput) {
                setActivePlayer(userInput.getPlayer(), userInput.getAction());
                if (self.equals(userInput.getPlayer())) {
                    switch (userInput.getAction()) {
                        case PICK_TRUMP -> new PickTrumpOverlay(this, false).show(game.data.stage);
                        case MAKE_PREDICTION -> new MakePredictionOverlay(this, round).show(game.data.stage);
                        case CHANGE_PREDICTION -> new ChangePredictionOverlay(this, round, predictions.get(self)).show(game.data.stage);
                    }
                    // TODO do something
                }
            } else if (observerMessage instanceof StateMessage state) {
                switch (state.getState()) {
                    case "starting_round" -> startRound();
                    case "starting_trick" -> startTrick();
                    case "finished", "error" -> {
                        // TODO do something
                    }
                }
            }
        }
    }

    public WizardGame.Data getData() {
        return game.data;
    }

    @Getter
    public enum Seat {
        BOTTOM(WizardGame.WIDTH * 0.5f, 0, 0, 0, Align.bottom, WizardGame.WIDTH * 0.5f, 300),
        LEFT(0, WizardGame.HEIGHT * 0.5f, 50, WizardGame.HEIGHT * 0.5f + 110f, Align.bottomLeft, 117.5f, WizardGame.HEIGHT * 0.5f),
        RIGHT(WizardGame.WIDTH, WizardGame.HEIGHT * 0.5f, WizardGame.WIDTH - 50, WizardGame.HEIGHT * 0.5f + 110f, Align.bottomRight, WizardGame.WIDTH - 117.5f, WizardGame.HEIGHT * 0.5f),
        TOP_LEFT(WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.25f, WizardGame.HEIGHT - 200),
        TOP(WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.5f, WizardGame.HEIGHT - 200),
        TOP_RIGHT(WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT, WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT - 50, Align.top, WizardGame.WIDTH * 0.75f, WizardGame.HEIGHT - 200);

        private final float x;
        private final float y;
        private final float labelX;
        private final float labelY;
        private final int labelAlign;
        private final float angle;
        private final float cardX;
        private final float cardY;

        Seat(float x, float y, float labelX, float labelY, int labelAlign, float cardX, float cardY) {
            this.x = x;
            this.y = y;
            this.labelX = labelX;
            this.labelY = labelY;
            this.labelAlign = labelAlign;
            this.cardX = cardX;
            this.cardY = cardY;

            var deltaX = WizardGame.WIDTH * 0.5f - x;
            var deltaY = WizardGame.HEIGHT * 0.5f - y;
            this.angle = (float) Math.toDegrees(Math.atan2(deltaY, deltaX) + Math.PI / 2);
        }
    }
}
