package fr.univ.lyon1.client;

import fr.univ.lyon1.common.Channel;
import fr.univ.lyon1.common.ChatSSL;
import fr.univ.lyon1.common.Message;
import fr.univ.lyon1.common.command.Command;
import fr.univ.lyon1.common.command.CommandType;
import fr.univ.lyon1.common.exception.ChatException;
import fr.univ.lyon1.common.exception.UnknownCommand;

import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLParameters;
import javax.net.ssl.SSLSocket;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class Client {
    private final int port;
    private final String address;
    private final String username;
    private final String password;
    protected final Socket socket;
    protected final ObjectOutputStream out;
    private ObjectInputStream in;
    private List<Channel> channels = new ArrayList<>();
    protected boolean started = false;


    public Client(String address, int port, String username, String password) throws IOException {
        this.address = address;
        this.port = port;
        this.username = username;
        this.password = password;
        socket = initSSL();
        out = new ObjectOutputStream(socket.getOutputStream());
        getIn();
    }

    private Socket initSSL() throws IOException {
        SSLContext ctx = ChatSSL.getSSLContext();

        SocketFactory factory = ctx.getSocketFactory();

        Socket connection = factory.createSocket(address, port);
        ((SSLSocket) connection).setEnabledProtocols(new String[] {ChatSSL.tlsVersion});
        SSLParameters sslParams = new SSLParameters();
        sslParams.setEndpointIdentificationAlgorithm("HTTPS");
        ((SSLSocket) connection).setSSLParameters(sslParams);
        return connection;
    }

    public void disconnectedServer() throws IOException {
        socket.close();
        out.close();
        if (in != null)
            in.close();

        System.exit(0);
    }

    public String sendMessage(String content) {

        try {
            out.writeObject(new Command(CommandType.message, List.of(new Message(content, channels.get(0)))));
            out.flush();
        } catch (IOException e) {
            System.err.println("Fail to send message !");
            e.printStackTrace();
        }

        return content;
    }

    public void action(Object data) throws IOException {
        if (data instanceof Command)
            command((Command) data);
        else if (data instanceof ChatException)
            ((ChatException) data).printStackTrace();
        else {
            out.writeObject(new UnknownCommand());
            out.flush();
        }
    }

    private void command(Command cmd) throws IOException {
        switch (cmd.getType()) {
            case login -> commandLogin();
            case message -> commandMessage(cmd);
            case list -> commandList(cmd);
            case join -> commandJoin(cmd);
        }
    }

    private void commandLogin() throws IOException {
        out.writeObject(new Command(CommandType.list, null));
        out.flush();
        out.writeObject(new Command(CommandType.join, List.of("general")));
        out.flush();
    }

    protected void commandMessage(Command cmd) {
        System.out.println();
        System.out.println(cmd.getArgs().get(0));
    }

    private void commandList(Command cmd) {
        List<Object> users = cmd.getArgs();
        for (Object u : users) {
            System.out.println(u);
        }
    }

    private void commandJoin(Command cmd) {
        Channel chan = (Channel) cmd.getArgs().get(0);
        channels.add(chan);
        System.out.println("You join "+chan);
    }

    public void run() throws InterruptedException, IOException {
        if (started)
            return;

        Thread clientSendThread = new Thread(new ClientSend(this, out, socket));
        clientSendThread.start();

        Thread clientReceiveThread = new Thread(new ClientReceive(this, socket));
        clientReceiveThread.start();

        started = true;

        out.writeObject(new Command(CommandType.login, List.of(username, password)));
        out.flush();

        clientSendThread.join();
        socket.close();
        clientReceiveThread.interrupt();
    }

    public ObjectInputStream getIn() throws IOException {
        if (in == null)
            in = new ObjectInputStream(socket.getInputStream());
        return in;
    }
}