package InnaIrcBot.Commanders;

import InnaIrcBot.ProvidersConsumers.StreamProvider;

import java.io.*;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.regex.Pattern;
                                                            //TODO: FLOOD, JOIN FLOOD
                                                            // TODO: @ configuration level: if in result we have empty string, no need to pass it to server
public class ChanelCommander implements Runnable {
    private BufferedReader reader;
    private String server;
    private String chanel;
                                                            //TODO: add timers
    private HashMap<String, String[]> joinMap;               // Mask(Pattern) ->, Action | Where Action[0] could be: raw
    private HashMap<String, String[]> msgMap;               // Mask(Pattern) ->, Action | Where Action[0] could be: raw
    private HashMap<String, String[]> nickMap;               // Mask(Pattern) ->, Action | Where Action[0] could be: raw

    private boolean joinFloodTrackNeed  = false;
    private JoinFloodHandler jfh;

    public ChanelCommander(BufferedReader streamReader, String serverName, String chan, String configFilePath){
        this.reader = streamReader;
        this.server = serverName;
        this.chanel = chan;

        this.joinMap = new HashMap<>();
        this.msgMap = new HashMap<>();
        this.nickMap = new HashMap<>();
        readConfing(configFilePath);
    }

    @Override
    public void run() {
        System.out.println("Thread for ChanelCommander started");                                                   // TODO:REMOVE DEBUG
        String data;
        String[] dataStrings;
        try {
            while ((data = reader.readLine()) != null) {
                dataStrings = data.split(" ",3);
                //event initiatorArg messageArg
                switch (dataStrings[0]) {
                    case "NICK":
                        nickCame(dataStrings[2]+dataStrings[1].replaceAll("^.+?!","!"));
                        break;              // todo: need to track join flood
                    case "JOIN":
                        if (joinFloodTrackNeed)
                            jfh.track(simplifyNick(dataStrings[1]));
                        joinCame(dataStrings[1]);
                        break;
                    case "PRIVMSG":
                        privmsgCame(dataStrings[1], dataStrings[2]);
                        break;
                        /*
                    case "PART":            // todo: need to track join flood? Fuck that. Track using JOIN
                        break;
                    case "QUIT":            // todo: need this?
                        break;
                    case "TOPIC":           // todo: need this?
                        break;
                    case "MODE":            // todo: need this?
                        break;
                    case "KICK":            // todo: need this?
                        break;              */
                    default:
                        break;
                }
            }
        } catch (java.io.IOException e){
            System.out.println("Internal issue: thread ChanelCommander->run() caused I/O exception:\n\t"+e);           // TODO: reconnect
        }
        System.out.println("Thread for ChanelCommander ended");                                                   // TODO:REMOVE DEBUG
    }

    // Do we need old nick?
    private void nickCame(String newNick){
        came(nickMap, newNick, newNick);
    }
    private void joinCame(String who){
        came(joinMap, who, who);
    }
    private void privmsgCame(String who, String what){
        came(msgMap, what, who);
    }

    private void came(HashMap<String, String[]> map, String arg1, String arg2){
        for (String pattern : map.keySet())
            if (Pattern.matches(pattern, arg1)){       // NOTE: validation based on new nick    //TODO: parse here
                String[] cmdOrMsg = map.get(pattern);
                for (int i = 0; i<cmdOrMsg.length;) {
                    //switch (map.get(pattern)[0]){
                    ArrayList<String> whatToSend;
                    switch (cmdOrMsg[i]) {
                        case "\\chanmsg":
                            whatToSend = new ArrayList<>();
                            for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
                                whatToSend.add(cmdOrMsg[i]);
                            msgAction(whatToSend.toArray(new String[0]), arg2, false);
                            break;
                        case "\\privmsg":
                            whatToSend = new ArrayList<>();
                            for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
                                whatToSend.add(cmdOrMsg[i]);
                            msgAction(whatToSend.toArray(new String[0]), arg2, true);
                            break;
                        case "\\ban":
                            banAction(arg2);
                            i++;
                            break;
                        case "\\kick":
                            whatToSend = new ArrayList<>();
                            for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
                                whatToSend.add(cmdOrMsg[i]);
                            kickAction(whatToSend.toArray(new String[0]), arg2);
                            break;
                        case "\\kickban":
                            whatToSend = new ArrayList<>();
                            for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
                                whatToSend.add(cmdOrMsg[i]);
                            banAction(arg2);
                            kickAction(whatToSend.toArray(new String[0]), arg2);
                            break;
                        case "\\raw":
                            StringBuilder whatToSendRaw = new StringBuilder();
                            for (i++; (i < cmdOrMsg.length) && !(cmdOrMsg[i].startsWith("\\")); i++)
                                whatToSendRaw.append(cmdOrMsg[i]);
                            StreamProvider.writeToStream(server, whatToSendRaw.toString()); //TODO
                            break;  //todo: add script
                        default:
                            i++;
                    }
                }
            }
    }
    /////////   /////////
    private void msgAction(String[] messages, String who, boolean sendToPrivate){
        StringBuilder executiveStr = new StringBuilder();
        executiveStr.append("PRIVMSG ");
        if(sendToPrivate) {
            executiveStr.append(simplifyNick(who));
            executiveStr.append(" :");
        }
        else {
            executiveStr.append(chanel);
            executiveStr.append(" :");
            executiveStr.append(simplifyNick(who));
            executiveStr.append(": ");
        }

        for (int i = 1; i<messages.length; i++){
            if ( ! messages[i].startsWith("\\"))
                executiveStr.append(messages[i]);
            else if (messages[i].equals("\\time"))
                executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        }
        //System.out.println(executiveStr);                               //TODO: debug
        StreamProvider.writeToStream(server, executiveStr.toString());
    }
    private void banAction(String whom){
        StreamProvider.writeToStream(server, "MODE "+chanel+" +b "+simplifyNick(whom)+"*!*@*");
        StreamProvider.writeToStream(server, "MODE "+chanel+" +b "+"*!*@"+whom.replaceAll("^.+@",""));
    }
    private void kickAction(String[] messages, String whom){
        StringBuilder executiveStr = new StringBuilder();
        executiveStr.append("KICK ");
        executiveStr.append(chanel);
        executiveStr.append(" ");
        executiveStr.append(simplifyNick(whom));
        executiveStr.append(" :");

        for (int i = 1; i<messages.length; i++){
            if ( ! messages[i].startsWith("\\"))
                executiveStr.append(messages[i]);
            else if (messages[i].equals("\\time"))
                executiveStr.append(LocalTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        }
        StreamProvider.writeToStream(server, executiveStr.toString());

    }
    // TSV
    private void parse(String[] directive){
        if (directive.length >= 3 && directive[0] != null && !directive[0].startsWith("#") && directive[1] != null && directive[2] != null){
        //System.out.println(Arrays.toString(directive));         // TODO:debug
            switch (directive[0].toLowerCase()){
                case "join":
                    joinMap.put(directive[1], Arrays.copyOfRange(directive, 2, directive.length));
                    break;
                case "msg":
                    msgMap.put(directive[1], Arrays.copyOfRange(directive, 2, directive.length));
                    break;
                case "nick":
                    nickMap.put(directive[1], Arrays.copyOfRange(directive, 2, directive.length));
                    break;
                case "joinfloodcontrol":
                    if (!directive[1].isEmpty() && !directive[2].isEmpty() && Pattern.matches("^[0-9]+?$", directive[1].trim()) && Pattern.matches("^[0-9]+?$", directive[2].trim())) {
                        int events = Integer.valueOf(directive[1].trim());
                        int timeFrame = Integer.valueOf(directive[2].trim());
                        if (events > 0 && timeFrame > 0) {
                            jfh = new JoinFloodHandler(events, timeFrame, server, chanel);
                            joinFloodTrackNeed = true;
                        } else {
                            System.out.println("Internal issue: thread ChanelCommander->parse(): 'Number of events' and/or 'Time Frame in seconds' should be greater then 0");
                        }
                    }
                    else
                        System.out.println("Internal issue: thread ChanelCommander->parse(): 'Number of events' and/or 'Time Frame in seconds' should be numbers greater then 0");
                    break;
            }
        }
    }
    private String simplifyNick(String nick){ return nick.replaceAll("!.*$",""); }

    private void readConfing(String confFilesPath){
        if (!confFilesPath.endsWith(File.separator))
            confFilesPath += File.separator;
        File file = new File(confFilesPath+server+chanel+".csv");  // TODO: add/search for filename
        if (!file.exists())
            return;
        try {
            BufferedReader br = new BufferedReader(new FileReader(file));
            String line;
            while ((line = br.readLine()) != null) {
                parse(line.split("\t"));
            }
        } catch (FileNotFoundException e) {
            System.out.println("Internal issue: thread ChanelCommander->readConfig() can't find file:\t\n"+e);
        }  catch (IOException e){
            System.out.println("Internal issue: thread ChanelCommander->readConfig() I/O exception:\t\n"+e);
        }
    }
}