/* Copyright (c) 2010 Daniel Doubrovkine, All Rights Reserved
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library 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
 * Lesser General Public License for more details.  
 */
package com.sun.jna.platform.win32;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.TreeMap;

import com.sun.jna.Memory;
import com.sun.jna.Native;
import com.sun.jna.platform.win32.WinNT.HANDLE;
import com.sun.jna.platform.win32.WinNT.HANDLEByReference;
import com.sun.jna.platform.win32.WinNT.PSID;
import com.sun.jna.platform.win32.WinNT.PSIDByReference;
import com.sun.jna.platform.win32.WinNT.SID_AND_ATTRIBUTES;
import com.sun.jna.platform.win32.WinNT.SID_NAME_USE;
import com.sun.jna.platform.win32.WinReg.HKEY;
import com.sun.jna.platform.win32.WinReg.HKEYByReference;
import com.sun.jna.ptr.IntByReference;
import com.sun.jna.ptr.PointerByReference;

/**
 * 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;
	}
	
	/**
	 * Retrieves the name of the user associated with the current thread.
	 * @return A user name.
	 */
	public static String getUserName() {
		char[] buffer = new char[128];
		IntByReference len = new IntByReference(buffer.length);
		boolean result = Advapi32.INSTANCE.GetUserNameW(buffer, len); 
		
		if (! result) {
			
			int rc = Kernel32.INSTANCE.GetLastError();

			switch(rc) {
			case W32Errors.ERROR_INSUFFICIENT_BUFFER:
				buffer = new char[len.getValue()];
				break;
			default:
				throw new Win32Exception(Native.getLastError());
			}
			
			result = Advapi32.INSTANCE.GetUserNameW(buffer, len);
		}
		
		if (! result) {
			throw new Win32Exception(Native.getLastError());
		}
		
		return Native.toString(buffer);
	}

	/**
	 * Retrieves a security identifier (SID) for the account on the current system.
	 * @param accountName Specifies the account name.
	 * @return A structure containing the account SID;
	 */
	public static Account getAccountByName(String accountName) {
		return getAccountByName(null, accountName);
	}
			
	/**
	 * Retrieves a security identifier (SID) for a given account.
	 * @param systemName Name of the system.
	 * @param accountName Account name.
	 * @return A structure containing the account SID.
	 */
	public static Account getAccountByName(String systemName, String accountName) {
		IntByReference pSid = new IntByReference(0);
		IntByReference cchDomainName = new IntByReference(0);
		PointerByReference peUse = new PointerByReference();
		
		if (Advapi32.INSTANCE.LookupAccountName(systemName, accountName, null, pSid, null, cchDomainName, peUse)) {
			throw new RuntimeException("LookupAccountNameW was expected to fail with ERROR_INSUFFICIENT_BUFFER");
		}
		
		int rc = Kernel32.INSTANCE.GetLastError();
		if (pSid.getValue() == 0 || rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
			throw new Win32Exception(rc);
		}

		Memory sidMemory = new Memory(pSid.getValue());
		PSID result = new PSID(sidMemory);
		char[] referencedDomainName = new char[cchDomainName.getValue() + 1]; 

		if (! Advapi32.INSTANCE.LookupAccountName(systemName, accountName, result, pSid, referencedDomainName, cchDomainName, peUse)) {
			throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
		}
		
		Account account = new Account();
		account.accountType = peUse.getPointer().getInt(0);
		account.name = accountName;

        String[] accountNamePartsBs = accountName.split("\\\\", 2);
        String[] accountNamePartsAt = accountName.split("@", 2);

        if (accountNamePartsBs.length == 2) {
        	account.name = accountNamePartsBs[1];
        } else if (accountNamePartsAt.length == 2) {
        	account.name = accountNamePartsAt[0];
        } else {
            account.name = accountName;
        }
		
		if (cchDomainName.getValue() > 0) {
			account.domain = Native.toString(referencedDomainName);
			account.fqn = account.domain + "\\" + account.name;
		} else {
			account.fqn = account.name;
		}
		
		account.sid = result.getBytes();
		account.sidString = convertSidToStringSid(new PSID(account.sid));
		return account;
	}

	/**
	 * Get the account by SID on the local system.
	 * 
	 * @param sid SID.
	 * @return Account.
	 */
	public static Account getAccountBySid(PSID sid) {
		return getAccountBySid(null, sid);
	}
	
	/**
	 * Get the account by SID.
	 * 
	 * @param systemName Name of the system.
	 * @param sid SID.
	 * @return Account.
	 */
	public static Account getAccountBySid(String systemName, PSID sid) {
    	IntByReference cchName = new IntByReference();
    	IntByReference cchDomainName = new IntByReference();
    	PointerByReference peUse = new PointerByReference();

    	if (Advapi32.INSTANCE.LookupAccountSid(null, sid, 
    			null, cchName, null, cchDomainName, peUse)) {
			throw new RuntimeException("LookupAccountSidW was expected to fail with ERROR_INSUFFICIENT_BUFFER");
    	}
    	
    	int rc = Kernel32.INSTANCE.GetLastError();
		if (cchName.getValue() == 0 || rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
			throw new Win32Exception(rc);
		}    	
		
		char[] domainName = new char[cchDomainName.getValue()];
		char[] name = new char[cchName.getValue()];
    	
		if (! Advapi32.INSTANCE.LookupAccountSid(null, sid, 
    			name, cchName, domainName, cchDomainName, peUse)) {
    		throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    	}
		
		Account account = new Account();
		account.accountType = peUse.getPointer().getInt(0);
		account.name = Native.toString(name);
		
		if (cchDomainName.getValue() > 0) {
			account.domain = Native.toString(domainName);
			account.fqn = account.domain + "\\" + account.name;
		} else {
			account.fqn = account.name;
		}
		
		account.sid = sid.getBytes();
		account.sidString = convertSidToStringSid(sid);
		return account;
	}
	
	/**
	 * Convert a security identifier (SID) to a string format suitable for display, 
	 * storage, or transmission.
	 * @param sid SID bytes.
	 * @return String SID.
	 */
	public static String convertSidToStringSid(PSID sid) {
		PointerByReference stringSid = new PointerByReference();
		if (! Advapi32.INSTANCE.ConvertSidToStringSid(sid, stringSid)) {
			throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
		}
		String result = stringSid.getValue().getString(0, true);
		Kernel32.INSTANCE.LocalFree(stringSid.getValue()); 
		return result;
	}
	
	/**
	 * Convert a string representation of a security identifier (SID) to 
	 * a binary format.
	 * @param sidString 
	 * 	String SID.
	 * @return SID bytes.
	 */
	public static byte[] convertStringSidToSid(String sidString) {
		PSIDByReference pSID = new PSIDByReference();
		if (! Advapi32.INSTANCE.ConvertStringSidToSid(sidString, pSID)) {
			throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
		}
		return pSID.getValue().getBytes();
	}
	
	/**
	 * Compares a SID to a well known SID and returns TRUE if they match.
	 * @param sidString
	 *  String representation of a SID.
	 * @param wellKnownSidType
	 *  Member of the WELL_KNOWN_SID_TYPE enumeration to compare with the SID at pSid.
	 * @return
	 *  True if the SID is of the well-known type, false otherwise.
	 */
	public static boolean isWellKnownSid(String sidString, int wellKnownSidType) {
		PSIDByReference pSID = new PSIDByReference();
		if (! Advapi32.INSTANCE.ConvertStringSidToSid(sidString, pSID)) {
			throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
		}
		return Advapi32.INSTANCE.IsWellKnownSid(pSID.getValue(), wellKnownSidType);
	}

	/**
	 * Compares a SID to a well known SID and returns TRUE if they match.
	 * @param sidBytes
	 *  Byte representation of a SID.
	 * @param wellKnownSidType
	 *  Member of the WELL_KNOWN_SID_TYPE enumeration to compare with the SID at pSid.
	 * @return
	 *  True if the SID is of the well-known type, false otherwise.
	 */
	public static boolean isWellKnownSid(byte[] sidBytes, int wellKnownSidType) {
		PSID pSID = new PSID(sidBytes);
		return Advapi32.INSTANCE.IsWellKnownSid(pSID, wellKnownSidType);		
	}
	
	/**
	 * Get an account name from a string SID on the local machine.
	 * 
	 * @param sidString SID.
	 * @return Account.
	 */
	public static Account getAccountBySid(String sidString) {
		return getAccountBySid(null, sidString); 
	}
	
	/**
	 * Get an account name from a string SID.
	 * 
	 * @param systemName System name.
	 * @param sidString SID.
	 * @return Account.
	 */
	public static Account getAccountBySid(String systemName, String sidString) {
		return getAccountBySid(systemName, new PSID(convertStringSidToSid(sidString))); 
	}
	
	/**
	 * This function returns the groups associated with a security token,
	 * such as a user token.
	 * 
	 * @param hToken Token.
	 * @return Token groups.
	 */
	public static Account[] getTokenGroups(HANDLE hToken) {
    	// get token group information size
        IntByReference tokenInformationLength = new IntByReference();
        if (Advapi32.INSTANCE.GetTokenInformation(hToken, 
        		WinNT.TOKEN_INFORMATION_CLASS.TokenGroups, null, 0, tokenInformationLength)) {
        	throw new RuntimeException("Expected GetTokenInformation to fail with ERROR_INSUFFICIENT_BUFFER");
        }
        int rc = Kernel32.INSTANCE.GetLastError();
        if (rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
        	throw new Win32Exception(rc);
        }
        // get token group information
		WinNT.TOKEN_GROUPS groups = new WinNT.TOKEN_GROUPS(tokenInformationLength.getValue());
        if (! Advapi32.INSTANCE.GetTokenInformation(hToken,
        		WinNT.TOKEN_INFORMATION_CLASS.TokenGroups, groups, 
        		tokenInformationLength.getValue(), tokenInformationLength)) {
        	throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }
        ArrayList<Account> userGroups = new ArrayList<Account>(); 
        // make array of names
    	for (SID_AND_ATTRIBUTES sidAndAttribute : groups.getGroups()) {
    		Account group = null;
    		try {
    			group = Advapi32Util.getAccountBySid(sidAndAttribute.Sid);
    		} catch(Exception e) {
    			group = new Account();
        		group.sid = sidAndAttribute.Sid.getBytes();
        		group.sidString = Advapi32Util.convertSidToStringSid(sidAndAttribute.Sid);
    			group.name = group.sidString;
    			group.fqn = group.sidString;
        		group.accountType = SID_NAME_USE.SidTypeGroup;
    		}
    		userGroups.add(group);
    	}
        return userGroups.toArray(new Account[0]);
	}

	/**
	 * This function returns the information about the user who owns a security token,
	 * 
	 * @param hToken Token.
	 * @return Token user.
	 */
	public static Account getTokenAccount(HANDLE hToken) {
    	// get token group information size
        IntByReference tokenInformationLength = new IntByReference();
        if (Advapi32.INSTANCE.GetTokenInformation(hToken, 
        		WinNT.TOKEN_INFORMATION_CLASS.TokenUser, null, 0, tokenInformationLength)) {
        	throw new RuntimeException("Expected GetTokenInformation to fail with ERROR_INSUFFICIENT_BUFFER");
        }
        int rc = Kernel32.INSTANCE.GetLastError();
        if (rc != W32Errors.ERROR_INSUFFICIENT_BUFFER) {
        	throw new Win32Exception(rc);
        }
        // get token user information
		WinNT.TOKEN_USER user = new WinNT.TOKEN_USER(tokenInformationLength.getValue());
        if (! Advapi32.INSTANCE.GetTokenInformation(hToken,
        		WinNT.TOKEN_INFORMATION_CLASS.TokenUser, user, 
        		tokenInformationLength.getValue(), tokenInformationLength)) {
        	throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
        }        
        return getAccountBySid(user.User.Sid);
	}
	
	/**
	 * Return the group memberships of the currently logged on user.
	 * @return An array of groups.
	 */
	public static Account[] getCurrentUserGroups() {		
    	HANDLEByReference phToken = new HANDLEByReference();    	
    	try {
    		// open thread or process token
        	HANDLE threadHandle = Kernel32.INSTANCE.GetCurrentThread();
        	if (! Advapi32.INSTANCE.OpenThreadToken(threadHandle, 
        			WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY, true, phToken)) {
            	if (W32Errors.ERROR_NO_TOKEN != Kernel32.INSTANCE.GetLastError()) {
            		throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
            	}        	
            	HANDLE processHandle = Kernel32.INSTANCE.GetCurrentProcess();
            	if (! Advapi32.INSTANCE.OpenProcessToken(processHandle, 
            			WinNT.TOKEN_DUPLICATE | WinNT.TOKEN_QUERY, phToken)) {
            		throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
            	}
        	}
        	return getTokenGroups(phToken.getValue());
    	} finally {
    		if (phToken.getValue() != WinBase.INVALID_HANDLE_VALUE) {
    			if (! Kernel32.INSTANCE.CloseHandle(phToken.getValue())) {
    				throw new Win32Exception(Kernel32.INSTANCE.GetLastError());
    			}
    		}
    	}		
	}

	/**
	 * 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 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.
	 */
	public static void registryCreateKey(HKEY hKey, String keyName) {
		HKEYByReference phkResult = new HKEYByReference();
    	int rc = Advapi32.INSTANCE.RegCreateKeyEx(hKey, keyName, 0, null, 0, 
    			WinNT.KEY_READ, null, phkResult, null);
		if (rc != W32Errors.ERROR_SUCCESS) {
			throw new Win32Exception(rc);
		}
		rc = Advapi32.INSTANCE.RegCloseKey(phkResult.getValue());
		if (rc != W32Errors.ERROR_SUCCESS) {
			throw new Win32Exception(rc);
		}		
	}

	/**
	 * Create a registry key.
	 * @param root
	 *  Root key.
	 * @param parentPath
	 *  Path to an existing registry key.
	 * @param keyName
	 *  Key name.
	 */
	public static void 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 {
			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
	 *  Name.
	 * @param value
	 *  Value.
	 */
	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
	 *  Name.
	 * @param value
	 *  Value.
	 */
	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
	 *  Name.
	 * @param value
	 *  Value.
	 */
	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 * 2);
		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
	 *  Name.
	 * @param value
	 *  Value.
	 */
	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);
			}
		}
	}
	
	/**
	 * 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<String>(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<String, Object>();
    	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);
        	}
        	switch(lpType.getValue()) {
        	case WinNT.REG_DWORD:
            	keyValues.put(Native.toString(name), 
            			((int)(data[0] & 0xff)) + 
            			(((int)(data[1] & 0xff)) << 8) + 
            			(((int)(data[2] & 0xff)) << 16) + 
            			(((int)(data[3] & 0xff)) << 24));
        		break;
        	case WinNT.REG_SZ:
            	try {
					keyValues.put(Native.toString(name),
							new String(data, 0, data.length - 2, "UTF-16LE"));
				} catch (UnsupportedEncodingException e) {
					throw new RuntimeException(e.getMessage());
				}
        		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);
			}
		}		
	}

}
