package com.ygames.ysoccer.server;

import com.badlogic.gdx.Gdx;
import com.badlogic.gdx.ScreenAdapter;
import com.esotericsoftware.kryonet.Connection;
import com.esotericsoftware.kryonet.Listener;
import com.esotericsoftware.kryonet.Server;
import com.ygames.ysoccer.framework.NetworkInputDevice;
import com.ygames.ysoccer.framework.Settings;
import com.ygames.ysoccer.match.Match;
import com.ygames.ysoccer.network.dto.InputDeviceDto;
import com.ygames.ysoccer.network.dto.MatchSetupDto;
import com.ygames.ysoccer.network.dto.MatchUpdateDto;
import com.ygames.ysoccer.network.mappers.InputDeviceMapper;
import com.ygames.ysoccer.network.mappers.MatchMapper;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class ServerScreen extends ScreenAdapter {

    private static final int REQUIRED_PLAYERS = 2;

    private final Server server;
    private final Match match;
    private final Map<Integer, Integer> connectionTeams = new HashMap<>();
    private boolean matchStarted;
    private volatile boolean readyToStart;
    private boolean matchEnded;

    public ServerScreen(Server server, Match match) {
        this.server = server;
        this.match = match;

        match.listener = new Match.MatchListener() {
            public void quitMatch(boolean matchCompleted) {
                quit(matchCompleted);
            }
        };

        server.addListener(new Listener() {
            public void connected(Connection connection) {
                synchronized (connectionTeams) {
                    if (matchStarted || connectionTeams.size() >= REQUIRED_PLAYERS) {
                        connection.close();
                        return;
                    }

                    int teamIndex = nextTeamIndex();
                    connectionTeams.put(connection.getID(), teamIndex);
                    readyToStart = connectionTeams.size() >= REQUIRED_PLAYERS;

                    MatchSetupDto matchSetupDto = MatchSetupDto.toDto(match);
                    server.sendToTCP(connection.getID(), matchSetupDto);
                    Gdx.app.log("Server", "Connection " + connection.getID() + " assigned to team " + teamIndex);
                }
            }

            public void disconnected(Connection connection) {
                synchronized (connectionTeams) {
                    connectionTeams.remove(connection.getID());
                    if (!matchStarted) {
                        readyToStart = connectionTeams.size() >= REQUIRED_PLAYERS;
                    }
                }
            }

            public void received(Connection connection, Object object) {
                if (object instanceof InputDeviceDto) {
                    Integer teamIndex;
                    synchronized (connectionTeams) {
                        teamIndex = connectionTeams.get(connection.getID());
                    }
                    if (teamIndex == null) {
                        return;
                    }

                    NetworkInputDevice inputDevice = (NetworkInputDevice) match.team[teamIndex].inputDevice;
                    inputDevice.update();

                    InputDeviceMapper.updateFromDto(inputDevice, (InputDeviceDto) object);
                }
            }
        });

        try {
            server.bind(Settings.tcpPort, Settings.udpPort);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        Gdx.app.log("Server", "Listening on TCP port " + Settings.tcpPort + " and UDP port " + Settings.udpPort);
        Gdx.app.log("Server", "Friend should use TCP " + Settings.tcpPort + " and UDP " + Settings.udpPort + " in Online Match Connect");
        server.start();
    }

    @Override
    public void render(float deltaTime) {
        if (!matchStarted && readyToStart) {
            match.start();
            matchStarted = true;
            Gdx.app.log("Server", "Match started");
        }

        if (matchStarted && !matchEnded) {
//            match.team[HOME].inputDevice.update();
            match.update(deltaTime);
            match.updateCurrentData();
            MatchUpdateDto matchUpdateDto = MatchMapper.toUpdateDto(match);
            server.sendToAllUDP(matchUpdateDto);
        }
    }

    private void quit(boolean matchCompleted) {
        matchEnded = true;
        Gdx.app.log("Server", "Match ended");
    }

    private int nextTeamIndex() {
        for (int teamIndex = 0; teamIndex < REQUIRED_PLAYERS; teamIndex++) {
            if (!connectionTeams.containsValue(teamIndex)) {
                return teamIndex;
            }
        }
        throw new IllegalStateException("No team slot available");
    }
}
