package org.cutyou.ircbot.utils;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.dataformat.xml.JacksonXmlModule;
import com.fasterxml.jackson.dataformat.xml.XmlMapper;
import com.fasterxml.jackson.dataformat.xml.ser.ToXmlGenerator;
import com.google.common.base.Charsets;
import com.google.common.io.CharStreams;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.cutyou.ircbot.IrcBot;
import org.cutyou.ircbot.utils.payloads.Command;
import org.cutyou.ircbot.utils.payloads.Config;
import org.pircbotx.hooks.ListenerAdapter;
import org.pircbotx.hooks.events.MessageEvent;
import org.pircbotx.hooks.events.PrivateMessageEvent;

import java.io.*;
import java.util.List;

public class ConfigLoader extends ListenerAdapter {

    private static final Logger logger = LogManager.getLogger(ConfigLoader.class);

    private static ObjectMapper xmlMapper = new ObjectMapper();

    static {
        xmlMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, Boolean.FALSE);
        xmlMapper.configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, Boolean.TRUE);
        xmlMapper.configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, Boolean.TRUE);
        xmlMapper.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, Boolean.FALSE);
        xmlMapper.configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, Boolean.FALSE);
        xmlMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, Boolean.TRUE);
        xmlMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS, Boolean.TRUE);
    }

    /**
     * Sends a multi-line response to a channel message.
     *
     * @param event    MessageEvent to respond to.
     * @param response List<String> (multi-line) response to send.
     */
    private static void sendResponse(Object event, List<String> response) {
        if (event.getClass() == PrivateMessageEvent.class) {
            PrivateMessageEvent evt = (PrivateMessageEvent) event;
            for (String line : response) {
                evt.respondPrivateMessage(line);
            }
        }
        if (event.getClass() == MessageEvent.class) {
            MessageEvent evt = (MessageEvent) event;
            for (String line : response) {
                evt.respondChannel(line);
            }
        }
    }

    public static Config loadConfig(String path) {

        try (InputStream inputStream = new FileInputStream(path)) {

            String loadedConfigFile = CharStreams.toString(new InputStreamReader(
                    inputStream, Charsets.UTF_8));
            JacksonXmlModule configXmlModule = new JacksonXmlModule();
            configXmlModule.setDefaultUseWrapper(false);

            XmlMapper xmlMapper = new XmlMapper(configXmlModule);
            inputStream.close();
            try {
                return xmlMapper.readValue(loadedConfigFile, Config.class);

            } catch (Exception e) {
                logger.error(e);
                return new Config();
            }

        } catch (IOException e) {
        		logger.error("Failed to load config", e);
            return new Config();
        }
    }

    public static boolean saveConfig(String path, Config config) {
        try {
            XmlMapper xmlMapper = new XmlMapper();
            File file = new File(path);
            xmlMapper.writerWithDefaultPrettyPrinter();
            xmlMapper.enable(SerializationFeature.INDENT_OUTPUT);
            xmlMapper.enable(ToXmlGenerator.Feature.WRITE_XML_DECLARATION);
            xmlMapper.writerWithView(Config.class);
            xmlMapper.writeValue(file, config);
            return true;
        } catch (Exception e) {
            logger.debug(e);
            e.printStackTrace();
            return false;
        }
    }

    /**
     * Sends a single line response to a channel message.
     *
     * @param event    MessageEvent to respond to.
     * @param response String response to send.
     */
    private static void sendResponse(Object event, String response) {
        if (event.getClass() == PrivateMessageEvent.class) {
            PrivateMessageEvent evt = (PrivateMessageEvent) event;
            evt.respondPrivateMessage(response);
        }
        if (event.getClass() == MessageEvent.class) {
            MessageEvent evt = (MessageEvent) event;
            evt.respondChannel(response);
        }
    }

    // ---- Responses ---- //

    // Send response with lots of lines

    @Override
    public void onPrivateMessage(PrivateMessageEvent event) throws Exception {
        CommandParser cp = new CommandParser();
        Command com = cp.parseCommand(event);
        String command = com.getCommand();
        switch (command) {
            case (VALID_COMMANDS.SAVE_CONFIG):
                try {
                    String appPath = new File(".").getCanonicalPath();
                    String defaultFileName = "config.xml";
                    String configPath;
                    configPath = appPath + "/" + defaultFileName;
                    if (saveConfig(configPath, IrcBot.getConfig())) {
                        sendResponse(event, "Config saved.");
                    } else {
                        sendResponse(event, "Config save failed.");
                    }
                } catch (IOException ioe) {
                    logger.error(ioe);
                }
                break;

            case VALID_COMMANDS.RENAME:
                if (com.getUser().equals("")) {
                    sendResponse(event, "Temp message.");
                } else {
                    IrcBot.getBot().send().changeNick(com.getUser());
                    IrcBot.getConfig().getServer().setNick(com.getUser());
                    sendResponse(event, "Nick changed, .sc to save config.");
                }
        }
    }

    // Send single line private message response

    private abstract static class VALID_COMMANDS {
        private static final String SAVE_CONFIG = ".sc";
        private static final String RENAME = ".rename";
        private static final String ADD_CHANNEL = ".add";
    }
}