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

import eu.jonahbauer.wizard.client.libgdx.Client;
import eu.jonahbauer.wizard.client.libgdx.screens.SessionScreen;
import eu.jonahbauer.wizard.common.messages.client.LeaveSessionMessage;
import eu.jonahbauer.wizard.common.messages.client.ReadyMessage;
import eu.jonahbauer.wizard.common.messages.data.PlayerData;
import eu.jonahbauer.wizard.common.messages.server.*;
import eu.jonahbauer.wizard.common.model.Configuration;
import lombok.Getter;
import lombok.extern.log4j.Log4j2;

import java.util.*;

@Log4j2
@Getter
public final class Session extends BaseState {
    private SessionScreen sessionScreen;
    
    private final UUID self;

    private final UUID session;
    private final String sessionName;
    private final Configuration configuration;
    private final LinkedHashMap<UUID, PlayerData> players = new LinkedHashMap<>();
    private final boolean dontSwitchScreen;

    private boolean sending;

    public Session(UUID session, String name, Configuration configuration, Collection<PlayerData> players, UUID self) {
        this(session, name, configuration, players, self, false);
    }

    public Session(UUID session, String name, Configuration configuration, Collection<PlayerData> players, UUID self, boolean dontSwitchScreen) {
        this.session = session;
        this.sessionName = name;
        this.configuration = configuration;
        players.forEach(p -> this.players.put(p.getUuid(), p));
        this.dontSwitchScreen = dontSwitchScreen;

        this.self = self;
    }

    @Override
    public Optional<ClientState> onEnter(Client client) {
        if (!dontSwitchScreen) {
            showInfoScreen(client);
        }
        return super.onEnter(client);
    }

    @Override
    public Optional<ClientState> onErrorDismissed(Client client) {
        showInfoScreen(client);
        return Optional.empty();
    }

    @Override
    public Optional<ClientState> onMessage(Client client, ServerMessage message) {
        if (message instanceof PlayerJoinedMessage join) {
            var player = join.getPlayer();
            log.info("Player {} joined the session.", player.getName());
            players.put(player.getUuid(), player);
            if (sessionScreen != null) sessionScreen.addPlayer(player);
            return Optional.empty();
        } else if (message instanceof PlayerLeftMessage leave) {
            var uuid = leave.getPlayer();
            var player = players.remove(uuid);
            log.info("Player {} left the session.", player.getName());
            if (sessionScreen != null) sessionScreen.removePlayer(uuid);
            return Optional.empty();
        } else if (message instanceof PlayerModifiedMessage modified) {
            var player = modified.getPlayer();
            log.info("Player {} was modified.", player.getName());
            players.put(player.getUuid(), player);
            if (sessionScreen != null) {
                sessionScreen.modifyPlayer(player);
                if (self.equals(player.getUuid())) {
                    sessionScreen.setReady(player.isReady());
                }
            }

            return Optional.empty();
        } else if (message instanceof StartingGameMessage) {
            var players = new LinkedHashMap<UUID, String>();
            this.players.forEach((uuid, player) -> players.put(uuid, player.getName()));
            return Optional.of(new Game(self, session, sessionName, configuration, players));
        } else if (sending && message instanceof NackMessage nack) {
            // TODO display error
            log.error(nack.getMessage());
            sending = false;
            if (sessionScreen != null) sessionScreen.setSending(false);
            return Optional.empty();
        } else if (sending && message instanceof AckMessage) {
            sending = false;
            if (sessionScreen != null) sessionScreen.setSending(false);
            return Optional.empty();
        } else {
            return unexpectedMessage(client, message);
        }
    }

    public Optional<ClientState> setReady(Client client, boolean ready) {
        if (sending) {
            log.warn("Please slow down");
        } else {
            sending = true;
            if (sessionScreen != null) sessionScreen.setSending(true);
            client.send(new ReadyMessage(ready));
        }
        return Optional.empty();
    }

    public Optional<ClientState> toggleReady(Client client) {
        return setReady(client, !isReady());
    }

    public Optional<ClientState> leave(Client client) {
        client.send(new LeaveSessionMessage());
        return Optional.of(new AwaitingJoinLobby());
    }

    public Optional<ClientState> showInfoScreen(Client client) {
        sessionScreen = new SessionScreen(client.getGame());
        client.getGame().setScreen(sessionScreen);
        sessionScreen.setPlayers(players.values().toArray(new PlayerData[0]));
        sessionScreen.setReady(players.get(self).isReady());
        sessionScreen.setPlayerName(getName());
        sessionScreen.setSession(session, sessionName, configuration);
        sessionScreen.setSending(sending);
        return Optional.empty();
    }

    private boolean isReady() {
        return players.get(self).isReady();
    }

    private String getName() {
        return players.get(self).getName();
    }
}
