package org.academiadecodigo.enuminatti.irc;

import java.io.*;
import java.net.Socket;
import java.util.Scanner;

/**
 * MIT License
 * (c) 2017 Ricardo Constantino
 */

public class Client {

    private Socket socket;
    private MessageSender sender;

    public static void main(String[] args) {
        Client client = new Client();
        client.connect("192.168.1.25", 8081);
//        client.connect("localhost", 6667);
        client.waitForDisconnect();
    }

    private void waitForDisconnect() {
        MessageReceiver receiver = new MessageReceiver(socket);
        receiver.run();
        disconnect();
    }

    private void connect(String host, int port) {
        try {
            socket = new Socket(host, port);
            System.out.println("Connected to server");
            sender = new MessageSender(socket);
            Thread senderThread = new Thread(sender);
            senderThread.start();
        } catch (IOException e) {
            System.err.println(String.format("Couldn't connect to server %s:%d.", host, port));
            e.printStackTrace();
            System.exit(1);
        }
    }

    private void disconnect() {
        if (socket == null || socket.isClosed()) {
            return;
        }
        try {
            System.out.println(String.format("Disconnecting from %s.", socket.getInetAddress().getHostAddress()));
            socket.close();
            System.exit(0);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private boolean parseCommand(String message) {
        if (message.length() < 2) {
            return false;
        }

        String[] args = message.substring(1).split(" ", 2);

        UserCommand cmd = UserCommand.toCmd(args[0]);
        if (cmd == null)
            return false;

        String rest = args.length > 1 ? args[1] : null;

        switch (cmd) {
            case EXIT:
            case DISCONNECT:
                sendQuitMessage(rest);
                break;
            case NICK:
                sendNickChangeMessage(rest);
                break;
            case HELP:
                printHelp(rest);
                break;
            default:
                return false;
        }

        return true;
    }

    private void printHelp() {
        System.out.println("Available user commands:");
        for (UserCommand command : UserCommand.values()) {
            System.out.println(command.usage);
            System.out.println("\t"+command.helpMessage);
        }
    }

    private void printHelp(String rest) {
        if (rest == null) {
            printHelp();
            return;
        }

        rest = rest.split(" ")[0];
        UserCommand cmd = UserCommand.toCmd(rest);
        if (cmd == null) {
            printHelp();
            return;
        }

        System.out.println("Help on " + rest + ":");
        System.out.println(cmd.usage);
        System.out.println("\t"+cmd.helpMessage);
    }

    private void sendNickChangeMessage(String newNick) {
        if (newNick == null) {
            printHelp("nick");
        }
        sender.sendMessage("NICK " + newNick);
    }

    private void sendQuitMessage(String message) {
        sender.sendMessage(String.format("QUIT%s",
                message != null ? " " + message : ""));
    }

    private enum UserCommand {
        HELP("/help [command]", "Prints this or, if with command, only the respective help on that command."),
        EXIT("/exit [quit message]", "Disconnects from the server."),
        DISCONNECT("/disconnect [quit message]", "Same as /exit."),
        NICK("/nick <new nick>", "Changes nick in the server.");

        private String usage;
        private String helpMessage;

        UserCommand(String usage, String helpMessage) {
            this.usage = usage;
            this.helpMessage = helpMessage;
        }

        public static UserCommand toCmd(String cmd) {
            if (cmd == null) {
                return null;
            }

            cmd = cmd.toUpperCase();
            for (UserCommand validCmd : UserCommand.values()) {
                if (cmd.equals(validCmd.name())) {
                    return validCmd;
                }
            }
            return null;
        }
    }

    private class MessageReceiver implements Runnable {

        private BufferedReader reader;

        public MessageReceiver(Socket socket) {
            try {
                this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            try {
                String message = "";
                while ((message = reader.readLine()) != null) {
                    System.out.println(message);
                }
            } catch (IOException e) {
                System.out.println("server disconnected us");
            }
        }
    }

    private class MessageSender implements Runnable {

        private Scanner scanner;
        private PrintWriter writer;

        public MessageSender(Socket socket) {
            try {
                scanner = new Scanner(System.in);
                writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()), true);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void run() {
            while (!socket.isClosed()) {
                System.out.print("> ");
                String message = scanner.nextLine();
                if (!parseCommand(message))
                    sendMessage(message);
            }
        }

        public void sendMessage(String message) {
            if (message == null)
                return;
            writer.println(message);
        }
    }

}
