/*
 * Copyright (C) 2013 JPEXS
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.jpexs.decompiler.flash.gui.jna.platform.win32;

/**
 *
 * @author JPEXS
 */
import com.jpexs.decompiler.flash.gui.jna.platform.win32.WinNT.EVENTLOGRECORD;
import com.jpexs.decompiler.flash.gui.jna.platform.win32.WinReg.HKEY;
import com.jpexs.decompiler.flash.gui.jna.platform.win32.WinReg.HKEYByReference;
import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.ptr.IntByReference;
import java.util.ArrayList;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;

/**
 * Advapi32 utility API.
 *
 * @author dblock[at]dblock.org
 */
public abstract class Advapi32Util {

    /**
     * An account.
     */
    public static class Account {

        /**
         * Account name.
         */
        public String name;
        /**
         * Account domain.
         */
        public String domain;
        /**
         * Account SID.
         */
        public byte[] sid;
        /**
         * String representation of the account SID.
         */
        public String sidString;
        /**
         * Account type, one of SID_NAME_USE.
         */
        public int accountType;
        /**
         * Fully qualified account name.
         */
        public String fqn;
    }

    /**
     * Checks whether a registry key exists.
     *
     * @param root HKEY_LOCAL_MACHINE, etc.
     * @param key Path to the registry key.
     * @return True if the key exists.
     */
    public static boolean registryKeyExists(HKEY root, String key) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        switch (rc) {
            case W32Errors.ERROR_SUCCESS:
                Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
                return true;
            case W32Errors.ERROR_FILE_NOT_FOUND:
                return false;
            default:
                throw new Win32Exception(rc);
        }
    }

    /**
     * Checks whether a registry value exists.
     *
     * @param root HKEY_LOCAL_MACHINE, etc.
     * @param key Registry key path.
     * @param value Value name.
     * @return True if the value exists.
     */
    public static boolean registryValueExists(HKEY root, String key, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        try {
            switch (rc) {
                case W32Errors.ERROR_SUCCESS:
                    break;
                case W32Errors.ERROR_FILE_NOT_FOUND:
                    return false;
                default:
                    throw new Win32Exception(rc);
            }
            IntByReference lpcbData = new IntByReference();
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, (char[]) null, lpcbData);
            switch (rc) {
                case W32Errors.ERROR_SUCCESS:
                case W32Errors.ERROR_INSUFFICIENT_BUFFER:
                    return true;
                case W32Errors.ERROR_FILE_NOT_FOUND:
                    return false;
                default:
                    throw new Win32Exception(rc);
            }
        } finally {
            if (phkKey.getValue() != WinBase.INVALID_HANDLE_VALUE) {
                rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
                if (rc != W32Errors.ERROR_SUCCESS) {
                    throw new Win32Exception(rc);
                }
            }
        }
    }

    /**
     * Get a registry REG_SZ value.
     *
     * @param root Root key.
     * @param key Registry path.
     * @param value Name of the value to retrieve.
     * @return String value.
     */
    public static String registryGetStringValue(HKEY root, String key, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            IntByReference lpcbData = new IntByReference();
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, (char[]) null, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            if (lpType.getValue() != WinNT.REG_SZ) {
                throw new RuntimeException("Unexpected registry type " + lpType.getValue() + ", expected REG_SZ");
            }
            char[] data = new char[lpcbData.getValue()];
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, data, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            return Native.toString(data);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Get a registry REG_EXPAND_SZ value.
     *
     * @param root Root key.
     * @param key Registry path.
     * @param value Name of the value to retrieve.
     * @return String value.
     */
    public static String registryGetExpandableStringValue(HKEY root, String key, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            IntByReference lpcbData = new IntByReference();
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, (char[]) null, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            if (lpType.getValue() != WinNT.REG_EXPAND_SZ) {
                throw new RuntimeException("Unexpected registry type " + lpType.getValue() + ", expected REG_SZ");
            }
            char[] data = new char[lpcbData.getValue()];
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, data, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            return Native.toString(data);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Get a registry REG_MULTI_SZ value.
     *
     * @param root Root key.
     * @param key Registry path.
     * @param value Name of the value to retrieve.
     * @return String value.
     */
    public static String[] registryGetStringArray(HKEY root, String key, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            IntByReference lpcbData = new IntByReference();
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, (char[]) null, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            if (lpType.getValue() != WinNT.REG_MULTI_SZ) {
                throw new RuntimeException("Unexpected registry type " + lpType.getValue() + ", expected REG_SZ");
            }
            Memory data = new Memory(lpcbData.getValue());
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, data, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            ArrayList<String> result = new ArrayList<>();
            int offset = 0;
            while (offset < data.size()) {
                String s = data.getString(offset, true);
                offset += s.length() * Native.WCHAR_SIZE;
                offset += Native.WCHAR_SIZE;
                result.add(s);
            }
            return result.toArray(new String[0]);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Get a registry REG_BINARY value.
     *
     * @param root Root key.
     * @param key Registry path.
     * @param value Name of the value to retrieve.
     * @return String value.
     */
    public static byte[] registryGetBinaryValue(HKEY root, String key, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            IntByReference lpcbData = new IntByReference();
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, (char[]) null, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            if (lpType.getValue() != WinNT.REG_BINARY) {
                throw new RuntimeException("Unexpected registry type " + lpType.getValue() + ", expected REG_BINARY");
            }
            byte[] data = new byte[lpcbData.getValue()];
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, data, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            return data;
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Get a registry DWORD value.
     *
     * @param root Root key.
     * @param key Registry key path.
     * @param value Name of the value to retrieve.
     * @return Integer value.
     */
    public static int registryGetIntValue(HKEY root, String key, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, key, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            IntByReference lpcbData = new IntByReference();
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, (char[]) null, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            if (lpType.getValue() != WinNT.REG_DWORD) {
                throw new RuntimeException("Unexpected registry type " + lpType.getValue() + ", expected REG_SZ");
            }
            IntByReference data = new IntByReference();
            rc = Advapi32.INSTANCE.RegQueryValueEx(
                    phkKey.getValue(), value, 0, lpType, data, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS && rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
                throw new Win32Exception(rc);
            }
            return data.getValue();
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Create a registry key.
     *
     * @param hKey Parent key.
     * @param keyName Key name.
     * @return True if the key was created, false otherwise.
     */
    public static boolean registryCreateKey(HKEY hKey, String keyName) {
        HKEYByReference phkResult = new HKEYByReference();
        IntByReference lpdwDisposition = new IntByReference();
        int rc = Advapi32.INSTANCE.RegCreateKeyEx(hKey, keyName, 0, null, WinNT.REG_OPTION_NON_VOLATILE,
                WinNT.KEY_READ, null, phkResult, lpdwDisposition);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        rc = Advapi32.INSTANCE.RegCloseKey(phkResult.getValue());
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        return WinNT.REG_CREATED_NEW_KEY == lpdwDisposition.getValue();
    }

    /**
     * Create a registry key.
     *
     * @param root Root key.
     * @param parentPath Path to an existing registry key.
     * @param keyName Key name.
     * @return True if the key was created, false otherwise.
     */
    public static boolean registryCreateKey(HKEY root, String parentPath, String keyName) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, parentPath, 0, WinNT.KEY_CREATE_SUB_KEY, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            return registryCreateKey(phkKey.getValue(), keyName);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Set an integer value in registry.
     *
     * @param hKey Parent key.
     * @param name Value name.
     * @param value Value to write to registry.
     */
    public static void registrySetIntValue(HKEY hKey, String name, int value) {
        byte[] data = new byte[4];
        data[0] = (byte) (value & 0xff);
        data[1] = (byte) ((value >> 8) & 0xff);
        data[2] = (byte) ((value >> 16) & 0xff);
        data[3] = (byte) ((value >> 24) & 0xff);
        int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0, WinNT.REG_DWORD, data, 4);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Set an integer value in registry.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param name Value name.
     * @param value Value to write to registry.
     */
    public static void registrySetIntValue(HKEY root, String keyPath, String name, int value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registrySetIntValue(phkKey.getValue(), name, value);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Set a string value in registry.
     *
     * @param hKey Parent key.
     * @param name Value name.
     * @param value Value to write to registry.
     */
    public static void registrySetStringValue(HKEY hKey, String name, String value) {
        char[] data = Native.toCharArray(value);
        int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0, WinNT.REG_SZ,
                data, data.length * Native.WCHAR_SIZE);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Set a string value in registry.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param name Value name.
     * @param value Value to write to registry.
     */
    public static void registrySetStringValue(HKEY root, String keyPath, String name, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registrySetStringValue(phkKey.getValue(), name, value);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Set an expandable string value in registry.
     *
     * @param hKey Parent key.
     * @param name Value name.
     * @param value Value to write to registry.
     */
    public static void registrySetExpandableStringValue(HKEY hKey, String name, String value) {
        char[] data = Native.toCharArray(value);
        int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0, WinNT.REG_EXPAND_SZ,
                data, data.length * Native.WCHAR_SIZE);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Set a string value in registry.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param name Value name.
     * @param value Value to write to registry.
     */
    public static void registrySetExpandableStringValue(HKEY root, String keyPath, String name, String value) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registrySetExpandableStringValue(phkKey.getValue(), name, value);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Set a string array value in registry.
     *
     * @param hKey Parent key.
     * @param name Name.
     * @param arr Array of strings to write to registry.
     */
    public static void registrySetStringArray(HKEY hKey, String name, String[] arr) {
        int size = 0;
        for (String s : arr) {
            size += s.length() * Native.WCHAR_SIZE;
            size += Native.WCHAR_SIZE;
        }

        int offset = 0;
        Memory data = new Memory(size);
        for (String s : arr) {
            data.setString(offset, s, true);
            offset += s.length() * Native.WCHAR_SIZE;
            offset += Native.WCHAR_SIZE;
        }

        int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0, WinNT.REG_MULTI_SZ,
                data.getByteArray(0, size), size);

        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Set a string array value in registry.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param name Value name.
     * @param arr Array of strings to write to registry.
     */
    public static void registrySetStringArray(HKEY root, String keyPath, String name, String[] arr) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registrySetStringArray(phkKey.getValue(), name, arr);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Set a binary value in registry.
     *
     * @param hKey Parent key.
     * @param name Value name.
     * @param data Data to write to registry.
     */
    public static void registrySetBinaryValue(HKEY hKey, String name, byte[] data) {
        int rc = Advapi32.INSTANCE.RegSetValueEx(hKey, name, 0, WinNT.REG_BINARY, data, data.length);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Set a binary value in registry.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param name Value name.
     * @param data Data to write to registry.
     */
    public static void registrySetBinaryValue(HKEY root, String keyPath, String name, byte[] data) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registrySetBinaryValue(phkKey.getValue(), name, data);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Delete a registry key.
     *
     * @param hKey Parent key.
     * @param keyName Name of the key to delete.
     */
    public static void registryDeleteKey(HKEY hKey, String keyName) {
        int rc = Advapi32.INSTANCE.RegDeleteKey(hKey, keyName);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Delete a registry key.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param keyName Name of the key to delete.
     */
    public static void registryDeleteKey(HKEY root, String keyPath, String keyName) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registryDeleteKey(phkKey.getValue(), keyName);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Delete a registry value.
     *
     * @param hKey Parent key.
     * @param valueName Name of the value to delete.
     */
    public static void registryDeleteValue(HKEY hKey, String valueName) {
        int rc = Advapi32.INSTANCE.RegDeleteValue(hKey, valueName);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
    }

    /**
     * Delete a registry value.
     *
     * @param root Root key.
     * @param keyPath Path to an existing registry key.
     * @param valueName Name of the value to delete.
     */
    public static void registryDeleteValue(HKEY root, String keyPath, String valueName) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ | WinNT.KEY_WRITE, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            registryDeleteValue(phkKey.getValue(), valueName);
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Get names of the registry key's sub-keys.
     *
     * @param hKey Registry key.
     * @return Array of registry key names.
     */
    public static String[] registryGetKeys(HKEY hKey) {
        IntByReference lpcSubKeys = new IntByReference();
        IntByReference lpcMaxSubKeyLen = new IntByReference();
        int rc = Advapi32.INSTANCE.RegQueryInfoKey(hKey, null, null, null,
                lpcSubKeys, lpcMaxSubKeyLen, null, null, null, null, null, null);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        ArrayList<String> keys = new ArrayList<>(lpcSubKeys.getValue());
        char[] name = new char[lpcMaxSubKeyLen.getValue() + 1];
        for (int i = 0; i < lpcSubKeys.getValue(); i++) {
            IntByReference lpcchValueName = new IntByReference(lpcMaxSubKeyLen.getValue() + 1);
            rc = Advapi32.INSTANCE.RegEnumKeyEx(hKey, i, name, lpcchValueName,
                    null, null, null, null);
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
            keys.add(Native.toString(name));
        }
        return keys.toArray(new String[0]);
    }

    /**
     * Get names of the registry key's sub-keys.
     *
     * @param root Root key.
     * @param keyPath Path to a registry key.
     * @return Array of registry key names.
     */
    public static String[] registryGetKeys(HKEY root, String keyPath) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            return registryGetKeys(phkKey.getValue());
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Get a table of registry values.
     *
     * @param hKey Registry key.
     * @return Table of values.
     */
    public static TreeMap<String, Object> registryGetValues(HKEY hKey) {
        IntByReference lpcValues = new IntByReference();
        IntByReference lpcMaxValueNameLen = new IntByReference();
        IntByReference lpcMaxValueLen = new IntByReference();
        int rc = Advapi32.INSTANCE.RegQueryInfoKey(hKey, null, null, null, null,
                null, null, lpcValues, lpcMaxValueNameLen, lpcMaxValueLen, null, null);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        TreeMap<String, Object> keyValues = new TreeMap<>();
        char[] name = new char[lpcMaxValueNameLen.getValue() + 1];
        byte[] data = new byte[lpcMaxValueLen.getValue()];
        for (int i = 0; i < lpcValues.getValue(); i++) {
            IntByReference lpcchValueName = new IntByReference(lpcMaxValueNameLen.getValue() + 1);
            IntByReference lpcbData = new IntByReference(lpcMaxValueLen.getValue());
            IntByReference lpType = new IntByReference();
            rc = Advapi32.INSTANCE.RegEnumValue(hKey, i, name, lpcchValueName, null,
                    lpType, data, lpcbData);
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }

            String nameString = Native.toString(name);

            Memory byteData = new Memory(lpcbData.getValue());
            byteData.write(0, data, 0, lpcbData.getValue());

            switch (lpType.getValue()) {
                case WinNT.REG_DWORD: {
                    keyValues.put(nameString, byteData.getInt(0));
                    break;
                }
                case WinNT.REG_SZ:
                case WinNT.REG_EXPAND_SZ: {
                    keyValues.put(nameString, byteData.getString(0, true));
                    break;
                }
                case WinNT.REG_BINARY: {
                    keyValues.put(nameString, byteData.getByteArray(0, lpcbData.getValue()));
                    break;
                }
                case WinNT.REG_MULTI_SZ: {
                    Memory stringData = new Memory(lpcbData.getValue());
                    stringData.write(0, data, 0, lpcbData.getValue());
                    ArrayList<String> result = new ArrayList<>();
                    int offset = 0;
                    while (offset < stringData.size()) {
                        String s = stringData.getString(offset, true);
                        offset += s.length() * Native.WCHAR_SIZE;
                        offset += Native.WCHAR_SIZE;
                        result.add(s);
                    }
                    keyValues.put(nameString, result.toArray(new String[0]));
                    break;
                }
                default:
                    throw new RuntimeException("Unsupported type: " + lpType.getValue());
            }
        }
        return keyValues;
    }

    /**
     * Get a table of registry values.
     *
     * @param root Registry root.
     * @param keyPath Regitry key path.
     * @return Table of values.
     */
    public static TreeMap<String, Object> registryGetValues(HKEY root, String keyPath) {
        HKEYByReference phkKey = new HKEYByReference();
        int rc = Advapi32.INSTANCE.RegOpenKeyEx(root, keyPath, 0, WinNT.KEY_READ, phkKey);
        if (rc != W32Errors.ERROR_SUCCESS) {
            throw new Win32Exception(rc);
        }
        try {
            return registryGetValues(phkKey.getValue());
        } finally {
            rc = Advapi32.INSTANCE.RegCloseKey(phkKey.getValue());
            if (rc != W32Errors.ERROR_SUCCESS) {
                throw new Win32Exception(rc);
            }
        }
    }

    /**
     * Converts a map of environment variables to an environment block suitable
     * for {@link Advapi32#CreateProcessAsUser}. This environment block consists
     * of null-terminated blocks of null-terminated strings. Each string is in
     * the following form: name=value\0
     *
     * @param environment Environment variables
     * @return A environment block
     */
    public static String getEnvironmentBlock(Map<String, String> environment) {
        StringBuffer out = new StringBuffer();
        for (Entry<String, String> entry : environment.entrySet()) {
            if (entry.getValue() != null) {
                out.append(entry.getKey() + "=" + entry.getValue() + "\0");
            }
        }
        return out.toString() + "\0";
    }

    /**
     * Event log types.
     */
    public static enum EventLogType {

        Error,
        Warning,
        Informational,
        AuditSuccess,
        AuditFailure
    }

    /**
     * An event log record.
     */
    public static class EventLogRecord {

        private EVENTLOGRECORD _record = null;
        private String _source;
        private byte[] _data;
        private String[] _strings;

        /**
         * Raw record data.
         *
         * @return EVENTLOGRECORD.
         */
        public EVENTLOGRECORD getRecord() {
            return _record;
        }

        /**
         * Event Id.
         *
         * @return Integer.
         */
        public int getEventId() {
            return _record.EventID.intValue();
        }

        /**
         * Event source.
         *
         * @return String.
         */
        public String getSource() {
            return _source;
        }

        /**
         * Status code for the facility, part of the Event ID.
         *
         * @return Status code.
         */
        public int getStatusCode() {
            return _record.EventID.intValue() & 0xFFFF;
        }

        /**
         * Record number of the record. This value can be used with the
         * EVENTLOG_SEEK_READ flag in the ReadEventLog function to begin reading
         * at a specified record.
         *
         * @return Integer.
         */
        public int getRecordNumber() {
            return _record.RecordNumber.intValue();
        }

        /**
         * Record length, with data.
         *
         * @return Number of bytes in the record including data.
         */
        public int getLength() {
            return _record.Length.intValue();
        }

        /**
         * Strings associated with this event.
         *
         * @return Array of strings or null.
         */
        public String[] getStrings() {
            return _strings;
        }

        /**
         * Event log type.
         *
         * @return Event log type.
         */
        public EventLogType getType() {
            switch (_record.EventType.intValue()) {
                case WinNT.EVENTLOG_SUCCESS:
                case WinNT.EVENTLOG_INFORMATION_TYPE:
                    return EventLogType.Informational;
                case WinNT.EVENTLOG_AUDIT_FAILURE:
                    return EventLogType.AuditFailure;
                case WinNT.EVENTLOG_AUDIT_SUCCESS:
                    return EventLogType.AuditSuccess;
                case WinNT.EVENTLOG_ERROR_TYPE:
                    return EventLogType.Error;
                case WinNT.EVENTLOG_WARNING_TYPE:
                    return EventLogType.Warning;
                default:
                    throw new RuntimeException("Invalid type: " + _record.EventType.intValue());
            }
        }

        /**
         * Raw data associated with the record.
         *
         * @return Array of bytes or null.
         */
        public byte[] getData() {
            return _data;
        }

        public EventLogRecord(Pointer pevlr) {
            _record = new EVENTLOGRECORD(pevlr);
            _source = pevlr.getString(_record.size(), true);
            // data
            if (_record.DataLength.intValue() > 0) {
                _data = pevlr.getByteArray(_record.DataOffset.intValue(),
                        _record.DataLength.intValue());
            }
            // strings
            if (_record.NumStrings.intValue() > 0) {
                ArrayList<String> strings = new ArrayList<>();
                int count = _record.NumStrings.intValue();
                long offset = _record.StringOffset.intValue();
                while (count > 0) {
                    String s = pevlr.getString(offset, true);
                    strings.add(s);
                    offset += s.length() * Native.WCHAR_SIZE;
                    offset += Native.WCHAR_SIZE;
                    count--;
                }
                _strings = strings.toArray(new String[0]);
            }
        }
    }
}
