package eu.jonahbauer.wizard.client.libgdx;

import eu.jonahbauer.wizard.client.libgdx.screens.ErrorScreen;
import eu.jonahbauer.wizard.client.libgdx.state.ClientState;
import eu.jonahbauer.wizard.client.libgdx.state.Menu;
import eu.jonahbauer.wizard.common.machine.TimeoutContext;
import eu.jonahbauer.wizard.common.messages.client.ClientMessage;
import eu.jonahbauer.wizard.common.messages.server.ServerMessage;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;

import java.util.Optional;
import java.util.function.BiFunction;

@Log4j2
public class Client extends TimeoutContext<ClientState, Client> {
    
    @Getter
    private final WizardGame game;
    
    @Getter
    private ClientSocket socket;
    
    @Getter
    @Setter
    private boolean error = false;
    
    public Client(WizardGame game) {
        super(new Menu());
        this.game = game;
    }
    
    @Override
    protected void handleError(Throwable t) {
        // TODO better error handling
        log.error("An error occurred.", t);

        game.setScreen(new ErrorScreen(game, t.getMessage()));
        error = true;

        var menu = new Menu();
        forceTransition(menu);
        menu.onEnter(this);
    }
    
    public void setSocket(ClientSocket socket) {
        this.socket = socket;
        if (socket != null) {
            this.socket.setAttachment(this);
        }
    }
    
    public void onOpen() {
        try {
            execute(s -> s.onOpen(this));
        } catch (Throwable t) {
            handleError(t);
        }
    }
    
    public void onClose(int code, String reason, boolean remote) {
        try {
            execute(s -> s.onClose(this, code, reason, remote));
        } catch (Throwable t) {
            handleError(t);
        }
    }
    
    public void onMessage(ServerMessage message) {
        try {
            execute(s -> s.onMessage(this, message));
        } catch (Throwable t) {
            handleError(t);
        }
    }
    
    @Override
    protected void onTransition(ClientState from, ClientState to) {
        log.debug("Transition from {} to {}.", from.getClass().getSimpleName(), to.getClass().getSimpleName());
    }
    
    public void send(ClientMessage message) {
        getSocket().send(message.toString());
    }

    public <T extends ClientState> void execute(Class<T> stateClass, BiFunction<T, Client, Optional<ClientState>> transition) {
        execute(s -> {
            if (stateClass.isInstance(s)) {
                return transition.apply(stateClass.cast(s), this);
            } else {
                return Optional.empty();
            }
        });
    }
}