package be.iminds.ilabt.jfed.ssh_terminal_tool.putty;

import be.iminds.ilabt.jfed.util.IOUtils;
import be.iminds.ilabt.jfed.util.JFedUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * User: twalcari
 * Date: 8/26/2016
 * Time: 15:35
 */
public class WinRegistry {
    private static final Logger LOG = LoggerFactory.getLogger(WinRegistry.class);
    protected static final String REG_SZ = "REG_SZ";
    protected static final String REG_EXPAND_SZ = "REG_EXPAND_SZ";
    protected static final String REG_DWORD = "REG_DWORD";
    protected static final String REG_MULTI_SZ = "REG_MULTI_SZ";


    private static File tmpFile(String baseName, String ext) {
        File tmpDir = new File(JFedUtils.getUserDataDirectoryFile(), "tmp");
        if (!tmpDir.exists()) //noinspection ResultOfMethodCallIgnored
            tmpDir.mkdir();

        File file = null;
        try {
            file = File.createTempFile(baseName, ext, tmpDir);
        } catch (IOException e) {
            LOG.error("Error while creating temporary file. Doing fallback");

            long nr = new Date().getTime();
            file = new File(tmpDir, baseName + nr + "." + ext);
        }
        file.deleteOnExit();
        return file;
    }

    /**
     * make sure the file has windows line termination! \r\n
     */
    public static void importInRegistry(File regFile) throws IOException {
        assert regFile.exists();

        LOG.trace("Importing {} in registry", regFile.getPath());
        executeCommand("reg", "IMPORT", "\"" + regFile.getPath() + "\"");
    }

    private static String executeCommand(String... cmdarray) throws IOException {
        Process p = Runtime.getRuntime().exec(cmdarray);

        String output = IOUtils.streamToString(p.getInputStream(), Charset.defaultCharset().toString());

        if (LOG.isTraceEnabled()) {
            LOG.trace("reg output: {}", output);
            LOG.trace("reg error: {}", IOUtils.streamToString(p.getErrorStream(), Charset.defaultCharset().toString()));
        }

        try {
            p.waitFor();
        } catch (InterruptedException ignored) {
        }

        if (LOG.isTraceEnabled())
            LOG.trace("reg completed");

        return output;
    }

    public static void importInRegistry(String contents) throws IOException {
        File tmpFile = tmpFile("jFed", ".reg");
        IOUtils.stringToFile(tmpFile, contents.replace("\n", "\r\n"));

        importInRegistry(tmpFile);
        //noinspection ResultOfMethodCallIgnored
        tmpFile.delete();
    }

    public static void setValue(String parentAndKey, String value) throws IOException {
        LOG.debug("Starting reg for {}={}", parentAndKey, value);

        executeCommand("reg", "ADD", '"' + parentAndKey + '"', "/d", '"' + value + '"', "/f");
    }

    public static void setValue(String parent, String key, String value) throws IOException {
        LOG.debug("Starting reg for {}\\{}={}", parent, key, value);

        executeCommand("reg", "ADD", '"' + parent + '"', "/v", '"' + key + '"', "/d", '"' + value + '"', "/f");
    }

    private static final Pattern REGEX_TOKEN = Pattern.compile("^\\s*(.*?)\\s+(REG_\\w+)\\s+(.*)");
    private static final Pattern REGEX_PARENT_PATH = Pattern.compile("[a-zA-Z_ ]+\\\\.*");

    public static Map<String, Object> getValues(String parent) throws IOException {
        LOG.trace("Performing reg QUERY for all keyvalue-pairs in {}", parent);

        String output = executeCommand("reg", "QUERY", '"' + parent + '"');

        StringTokenizer tokenizer = new StringTokenizer(output, "\n\r");
        //first line will be the name of the parent. Skip this

        boolean headerFound = false;

        while (tokenizer.hasMoreTokens() && !headerFound) {
            String tok = tokenizer.nextToken().trim();
            headerFound = REGEX_PARENT_PATH.matcher(tok).matches();
        }

        Map<String, Object> result = new HashMap<>();

        while (tokenizer.hasMoreTokens()) {
            String line = tokenizer.nextToken().trim();
            if (line.isEmpty())
                continue;

            Matcher m = REGEX_TOKEN.matcher(line);
            if (m.matches()) {
                final String name = m.group(1);
                final String type = m.group(2);
                final String value = m.group(3);

                switch (type) {
                    case REG_SZ:
                    case REG_EXPAND_SZ:
                        result.put(name, value);
                        break;
                    case REG_DWORD:
                        result.put(name, Integer.parseInt(value, 16));
                        break;
                    case REG_MULTI_SZ:
                        result.put(name, value.split("\0"));
                        break;
                    default:
                        LOG.error("Unsupported registry type {} with name {}", type, name);
                        break;
                }
            } else if (!REGEX_PARENT_PATH.matcher(line).matches()) {
                LOG.warn("Could not parse line {}", line);
            } else {
                LOG.info("Ignoring child {}", line);
            }
        }

        return result;
    }

    public static Collection<String> getChildren(String parent) throws IOException {
        LOG.trace("Performing reg QUERY for children of {}", parent);

        String output = executeCommand("reg", "QUERY", '"' + parent + '"');

        StringTokenizer tokenizer = new StringTokenizer(output, "\n\r");

        final String matchableParent = parent.substring(parent.indexOf('\\') + 1).toLowerCase();

        Collection<String> result = new ArrayList<>();

        while (tokenizer.hasMoreTokens()) {
            String line = tokenizer.nextToken().trim();

            if (line.isEmpty())
                continue;

            if (line.toLowerCase().contains(matchableParent) && REGEX_PARENT_PATH.matcher(line).matches()) {
                result.add(line);
            } else if (!REGEX_TOKEN.matcher(line).matches()) {
                LOG.warn("Unmatched line while searching for children: {}", line);
            }
        }

        return result;
    }


    public static String getValue(String parent, String key) throws IOException, RegistryException {
        LOG.trace("Performing reg QUERY for {}\\{}", parent, key);

        String output = executeCommand("reg", "QUERY", '"' + parent + '"', "/v", '"' + key + '"');

        StringTokenizer tokenizer = new StringTokenizer(output, "\n\r");

        boolean headerFound = false;
        while (tokenizer.hasMoreTokens() && !headerFound) {
            String tok = tokenizer.nextToken().trim();
            headerFound = REGEX_PARENT_PATH.matcher(tok).matches();
        }

        while (tokenizer.hasMoreTokens()) {
            String line = tokenizer.nextToken().trim();
            if (line.isEmpty())
                continue;

            Matcher m = REGEX_TOKEN.matcher(line);
            if (m.matches()) {
                final String name = m.group(1);
                final String type = m.group(2);
                final String value = m.group(3);

                if (!Objects.equals(REG_SZ, type) && !Objects.equals(REG_EXPAND_SZ, type)) {
                    throw new RegistryException("Value of " + name + " is not a String, but of type " + type);
                } else {
                    assert Objects.equals(name, key);
                    return value;
                }
            }
        }

        return null;
    }

    public static class RegistryException extends Exception {
        public RegistryException() {
        }

        public RegistryException(String message) {
            super(message);
        }

        public RegistryException(String message, Throwable cause) {
            super(message, cause);
        }

        public RegistryException(Throwable cause) {
            super(cause);
        }

        public RegistryException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
            super(message, cause, enableSuppression, writableStackTrace);
        }
    }
}
