package com.insa.app.controller;

import com.insa.app.Resources;
import com.insa.app.database.Database;
import com.insa.app.database.DatabaseInterface;
import com.insa.app.model.Message;
import com.insa.app.model.User;
import com.insa.app.network.NetworkMessage;
import com.insa.app.network.NetworkService;
import javafx.application.Platform;
import javafx.collections.ObservableList;

import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Controller {

    private ConversationController convController;
    private UserController userController;
    private GuiController guiController;
    private NetworkService network;
    private DatabaseInterface database;


    public void loadDatabase() {

        for (Map.Entry<User,List<NetworkMessage>> entry : database.getAllConversations().entrySet()) {
                addUserUnsynchronized(entry.getKey(), false);
        }
        for (Map.Entry<User,List<NetworkMessage>> entry : database.getAllConversations().entrySet()) {
            for (NetworkMessage message : entry.getValue()) {
                addDatabaseMessage(message);
            }
        }

        userController.setUserName(database.getMe().getName());

    }

    public void closeDatabase() {
        database.closeDatabase();
    }

    private void addDatabaseMessage(NetworkMessage message) {
        boolean outgoing = message.getEmitterID().equals(getMe().getId());
        if (outgoing) {
            convController.addMessage(new Message(getMe().getName(), message.getContent(),message.getTimestamp()), message.getRecipientID());
        } else {
            String username = userController.getUserById(message.getEmitterID()).getName();
            convController.addMessage(new Message( username, message.getContent(),message.getTimestamp()), message.getEmitterID());
        }
    }

    public Controller(GuiController controller, List<String> parameters) {


        convController = new ConversationController();
        guiController = controller;
        database = new Database(this);
        userController = new UserController(database.getMe());
        guiController.setUserName(userController.getMyUserName());
        guiController.setUsers(userController.getUserList());
        database.storeMe(userController.getMe());

        try {
            String ip = Resources.getMyIp();
            userController.setMyIp(ip + ":" + Resources.clientPort);
        } catch (Exception e) {
            Logger.getAnonymousLogger().log(Level.WARNING, "" + e);
        }

        Logger.getAnonymousLogger().log(Level.INFO, "I am " + getMe() + " with ID : " + getMe().getId());
        loadDatabase();
    }

    public void setNetwork(NetworkService network) {
        this.network = network;
    }

    public ConversationController getConversations() {
        return convController;
    }

    public void sendMessage(User recipient, String message) {
        try {
            NetworkMessage mess = new NetworkMessage(message, userController.getMe().getId(), recipient.getId());
            addOutcomingMessage(mess);
            network.sendMessage(mess, recipient);
        } catch (Exception e) {
            guiController.showErrorPopup("No recipient");
            Logger.getAnonymousLogger().log(Level.WARNING, "Error while sending message : " + e);
        }
    }

    public void disconnectFromPresenceServer() {
        network.disconnectFromPresenceServer();
    }

    public User getMe() {
        return userController.getMe();
    }

    private Message toMessage(NetworkMessage mess) {
        User emitter = userController.getUserById(mess.getEmitterID());
        return new Message(emitter.getName(),mess.getContent());
    }

    public void addIncomingMessage(NetworkMessage mess) {
        convController.addMessage(toMessage(mess),mess.getEmitterID());
        database.storeMessage(mess);
    }

    public void addOutcomingMessage(NetworkMessage mess) {
        convController.addMessage(toMessage(mess),mess.getRecipientID());
        database.storeMessage(mess);
    }

    public ObservableList<String> retrieveConversation(User user) {
        return getConversations().getConversation(user);
    }

    public void connectToUser(String ip) {
        try {
            network.sendConnectRequest(ip + ":" + Resources.clientPort);
        } catch (Exception e) {
            guiController.showErrorPopup(e.toString());
        }
    }

    public void updateUser(User newUser) {
        newUser.setConnected();
        userController.replaceOrInsert(newUser);
        database.storeUser(newUser);
    }

    public void changeUserName(String name) {
        userController.setUserName(name);
        User me = userController.getMe();
        for (User recipient : userController.getOtherUsers()) {
            Logger.getAnonymousLogger().log(Level.INFO, "Sending to " + recipient + " that I am now called " + me.getName());
            network.sendNameChangeNotify(me, recipient);
        }
        if (network.isConnectedToPresenceServer()) {
            network.sendChangeNamePresenceServer();
        }
        guiController.setUserName(name);
        database.setUserName(name);
    }


    private void addUserUnsynchronized(User user, boolean connected) {
        userController.addUser(user);
        database.storeUser(user);
        if (!user.equals(getMe())) {
            NetworkMessage mess = new NetworkMessage(userController.getMe().getName() + " parle maintenant avec " + user.getName(), user.getId(), userController.getMe().getId());
            Logger.getAnonymousLogger().log(Level.INFO, "Creating new conversation between " + user.getName() + "@" + user.getIpAddress() + " and " + getMe().getName() + "@" + getMe().getIpAddress());
            convController.addMessage(toMessage(mess), user.getId());
        }
        if (!connected) {
            deleteUser(user);
        }
    }

    public void addUser(User user, boolean connected) {
        Runnable runnable = () -> {
            synchronized (userController) {
                synchronized (convController) {
                   addUserUnsynchronized(user,connected);
                }
            }
        };
        Platform.runLater(runnable);
    }

    public void deleteUser(User user) {
        userController.deleteUser(user);
        guiController.userLeft(user);
    }

    public User getUserByIp(String ip) throws RuntimeException {
        return userController.getUserByIp(ip);
    }

    public User getUserById(UUID id) throws RuntimeException {
        return userController.getUserById(id);
    }

    public void connectToPresenceServer(String ip) {
        network.connectToPresenceServer(ip);
    }

    public boolean isConnected(User user) {
        return userController.isConnected(user);
    }

}
