/*
 * $Id: RegistryHandler.java 2530 2009-02-03 12:37:44Z jponge $
 * IzPack - Copyright 2001-2008 Julien Ponge, All Rights Reserved.
 * 
 * http://izpack.org/
 * http://izpack.codehaus.org/
 * 
 * Copyright 2005 Klaus Bartz
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 *     
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.izforge.izpack.util.os;

import com.coi.tools.os.win.MSWinConstants;
import com.coi.tools.os.win.NativeLibException;
import com.coi.tools.os.win.RegDataContainer;
import com.izforge.izpack.installer.AutomatedInstallData;
import com.izforge.izpack.installer.ResourceManager;
import com.izforge.izpack.util.Debug;
import com.izforge.izpack.util.OSClassHelper;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * This class represents a registry handler in a operating system independent way. OS specific
 * subclasses are used to implement the necessary mapping from this generic API to the classes that
 * reflect the system dependent AIP.
 *
 * @author Klaus Bartz
 */
public class RegistryHandler extends OSClassHelper implements MSWinConstants
{

    public static final String UNINSTALL_ROOT = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\";

    public static final Map<String, Integer> ROOT_KEY_MAP = new HashMap<String, Integer>();

    protected String uninstallName = null;

    private static final String UNINSTALLER_ICON = "UninstallerIcon";

    private static RegistryHandler defaultHandler = null;

    static
    {
        ROOT_KEY_MAP.put("HKCR", HKEY_CLASSES_ROOT);
        ROOT_KEY_MAP.put("HKEY_CLASSES_ROOT", HKEY_CLASSES_ROOT);
        ROOT_KEY_MAP.put("HKCU", HKEY_CURRENT_USER);
        ROOT_KEY_MAP.put("HKEY_CURRENT_USER", HKEY_CURRENT_USER);
        ROOT_KEY_MAP.put("HKLM", HKEY_LOCAL_MACHINE);
        ROOT_KEY_MAP.put("HKEY_LOCAL_MACHINE", HKEY_LOCAL_MACHINE);
        ROOT_KEY_MAP.put("HKU", HKEY_USERS);
        ROOT_KEY_MAP.put("HKEY_USERS", HKEY_USERS);
        ROOT_KEY_MAP.put("HKPD", HKEY_PERFORMANCE_DATA);
        ROOT_KEY_MAP.put("HKEY_PERFORMANCE_DATA", HKEY_PERFORMANCE_DATA);
        ROOT_KEY_MAP.put("HKCC", HKEY_CURRENT_CONFIG);
        ROOT_KEY_MAP.put("HKEY_CURRENT_CONFIG", HKEY_CURRENT_CONFIG);
        ROOT_KEY_MAP.put("HKDDS", HKEY_DYN_DATA);
        ROOT_KEY_MAP.put("HKEY_DYN_DATA", HKEY_DYN_DATA);

    }

    /**
     * Default constructor.
     */
    public RegistryHandler()
    {
        super();
    }

    /**
     * Creates an registry handler which uses an oblect of the given class as worker.
     *
     * @param className full qualified class name of the class which should be used as worker
     */
    public RegistryHandler(String className)
    {
        super(className);
        setDefault();
    }

    /**
     * Set this object as default handler if it is not done earlier.
     */
    private synchronized void setDefault()
    {
        if (defaultHandler == null)
        {
            defaultHandler = this;
        }
    }

    /**
     * Sets the given contents to the given registry value. If a sub key or the registry value does
     * not exist, it will be created. The return value is a String array which contains the names of
     * the keys and values which are created. REG_SZ is used as registry value type.
     *
     * @param key      the registry key which should be used or created
     * @param value    the registry value into which the contents should be set
     * @param contents the contents for the value
     * @throws NativeLibException
     */
    public void setValue(String key, String value, String contents) throws NativeLibException
    {
    }

    public void setValue(String key, String value, String[] contents) throws NativeLibException
    {
    }

    /**
     * Sets the given contents to the given registry value. If a sub key or the registry value does
     * not exist, it will be created. The return value is a String array which contains the names of
     * the keys and values which are created. REG_BINARY is used as registry value type.
     *
     * @param key      the registry key which should be used or created
     * @param value    the registry value into which the contents should be set
     * @param contents the contents for the value
     * @throws NativeLibException
     */
    public void setValue(String key, String value, byte[] contents) throws NativeLibException
    {
    }

    /**
     * Sets the given contents to the given registry value. If a sub key or the registry value does
     * not exist, it will be created. The return value is a String array which contains the names of
     * the keys and values which are created. REG_DWORD is used as registry value type.
     *
     * @param key      the registry key which should be used or created
     * @param value    the registry value into which the contents should be set
     * @param contents the contents for the value
     * @throws NativeLibException
     */
    public void setValue(String key, String value, long contents) throws NativeLibException
    {
    }

    /**
     * Returns the contents of the key/value pair if value exist, else the given default value.
     *
     * @param key        the registry key which should be used
     * @param value      the registry value from which the contents should be requested
     * @param defaultVal value to be used if no value exist in the registry
     * @return requested value if exist, else the default value
     * @throws NativeLibException
     */
    public RegDataContainer getValue(String key, String value, RegDataContainer defaultVal) throws NativeLibException
    {
        return (null);
    }

    /**
     * Returns whether a key exist or not.
     *
     * @param key key to be evaluated
     * @return whether a key exist or not
     * @throws NativeLibException
     */
    public boolean keyExist(String key) throws NativeLibException
    {
        return (false);
    }

    /**
     * Returns whether a the given value under the given key exist or not.
     *
     * @param key   key to be used as path for the value
     * @param value value name to be evaluated
     * @return whether a the given value under the given key exist or not
     * @throws NativeLibException
     */
    public boolean valueExist(String key, String value) throws NativeLibException
    {
        return (false);
    }

    /**
     * Returns all keys which are defined under the given key.
     *
     * @param key key to be used as path for the sub keys
     * @return all keys which are defined under the given key
     * @throws NativeLibException
     */
    public String[] getSubkeys(String key) throws NativeLibException
    {
        return (null);
    }

    /**
     * Returns all value names which are defined under the given key.
     *
     * @param key key to be used as path for the value names
     * @return all value names which are defined under the given key
     * @throws NativeLibException
     */
    public String[] getValueNames(String key) throws NativeLibException
    {
        return (null);
    }

    /**
     * Returns the contents of the key/value pair if value exist, else an exception is raised.
     *
     * @param key   the registry key which should be used
     * @param value the registry value from which the contents should be requested
     * @return requested value if exist, else an exception
     * @throws NativeLibException
     */
    public RegDataContainer getValue(String key, String value) throws NativeLibException
    {
        return (null);
    }

    /**
     * Creates the given key in the registry.
     *
     * @param key key to be created
     * @throws NativeLibException
     */
    public void createKey(String key) throws NativeLibException
    {
    }

    /**
     * Deletes the given key if exist, else throws an exception.
     *
     * @param key key to be deleted
     * @throws NativeLibException
     */
    public void deleteKey(String key) throws NativeLibException
    {
    }

    /**
     * Deletes a key under the current root if it is empty, else do nothing.
     *
     * @param key key to be deleted
     * @throws NativeLibException
     */
    public void deleteKeyIfEmpty(String key) throws NativeLibException
    {
    }

    /**
     * Deletes a value.
     *
     * @param key   key of the value which should be deleted
     * @param value value name to be deleted
     * @throws NativeLibException
     */
    public void deleteValue(String key, String value) throws NativeLibException
    {
    }

    /**
     * Sets the root for the next registry access.
     *
     * @param i an integer which refers to a HKEY
     * @throws NativeLibException
     */
    public void setRoot(int i) throws NativeLibException
    {
    }

    /**
     * Return the root as integer (HKEY_xxx).
     *
     * @return the root as integer
     * @throws NativeLibException
     */
    public int getRoot() throws NativeLibException
    {
        return (0);
    }

    /**
     * Sets up whether or not previous contents of registry values will
     * be logged by the 'setValue()' method.  When registry values are
     * overwritten by repeated installations, the desired behavior can
     * be to have the registry value removed rather than rewound to the
     * last-set contents (acheived via 'false').  If this method is not
     * called then the flag wll default to 'true'.
     *
     * @param flagVal true to have the previous contents of registry
     * values logged by the 'setValue()' method.
     */
    public void setLogPrevSetValueFlag(boolean flagVal)
    {
    }

    /**
     * Determines whether or not previous contents of registry values
     * will be logged by the 'setValue()' method.
     *
     * @return true if the previous contents of registry values will be
     * logged by the 'setValue()' method.
     */
    public boolean getLogPrevSetValueFlag()
    {
        return (true);
    }

    /**
     * Activates logging of registry changes.
     *
     * @throws NativeLibException
     */
    public void activateLogging() throws NativeLibException
    {
    }

    /**
     * Suspends logging of registry changes.
     *
     * @throws NativeLibException
     */
    public void suspendLogging() throws NativeLibException
    {
    }

    /**
     * Resets logging of registry changes.
     *
     * @throws NativeLibException
     */
    public void resetLogging() throws NativeLibException
    {
    }

    public List<Object> getLoggingInfo() throws NativeLibException
    {
        return (null);
    }

    public void setLoggingInfo(List info) throws NativeLibException
    {
    }

    public void addLoggingInfo(List info) throws NativeLibException
    {
    }

    public void rewind() throws NativeLibException
    {
    }

    public String getUninstallName()
    {
        if (uninstallName != null)
        {
            return (uninstallName);
        }
        if (installdata == null)
        {
            return (null);
        }
        return (installdata.getVariable("APP_NAME") + " " + installdata.getVariable("APP_VER"));
    }

    public boolean isProductRegistered() throws NativeLibException
    {
        String uninstallName = getUninstallName();
        if (uninstallName == null)
        {
            return (false);
        }
        String keyName = UNINSTALL_ROOT + uninstallName;
        int oldVal = getRoot();
        setRoot(HKEY_LOCAL_MACHINE);
        boolean retval = keyExist(keyName);
        setRoot(HKEY_CURRENT_USER);
        retval = retval || keyExist(keyName);
        setRoot(oldVal);
        return (retval);
    }

    public void setUninstallName(String name)
    {
        uninstallName = name;
    }

    public void registerUninstallKey() throws NativeLibException
    {
        String uninstallName = getUninstallName();
        if (uninstallName == null)
        {
            return;
        }
        String keyName = UNINSTALL_ROOT + uninstallName;
        String cmd = "\"" + installdata.getVariable("JAVA_HOME") + "\\bin\\javaw.exe\" -jar \""
                + installdata.getVariable("INSTALL_PATH") + "\\uninstaller\\uninstaller.jar\"";
        String appVersion = installdata.getVariable("APP_VER");
        String appUrl = installdata.getVariable("APP_URL");

        int oldVal = getRoot();
        try
        {
            setRoot(HKEY_LOCAL_MACHINE);
            setValue(keyName, "DisplayName", uninstallName);
        }
        catch (NativeLibException exception)
        { // Users without administrative rights should be able to install the app for themselves
            Debug.trace("Failed to register uninstaller in HKEY_LOCAL_MACHINE hive, trying HKEY_CURRENT_USER: " + exception.getMessage());
            setRoot(HKEY_CURRENT_USER);
            setValue(keyName, "DisplayName", uninstallName);
        }
        setValue(keyName, "UninstallString", cmd);
        setValue(keyName, "DisplayVersion", appVersion);
        if (appUrl != null && appUrl.length() > 0)
        {
            setValue(keyName, "HelpLink", appUrl);
        }
        // Try to write the uninstaller icon out.
        try
        {
            InputStream input = ResourceManager.getInstance().getInputStream(UNINSTALLER_ICON);
            String iconPath = installdata.getVariable("INSTALL_PATH") + File.separator
                    + "Uninstaller" + File.separator + "UninstallerIcon.ico";
            FileOutputStream out = new FileOutputStream(iconPath);
            byte[] buffer = new byte[5120];
            long bytesCopied = 0;
            int bytesInBuffer;
            while ((bytesInBuffer = input.read(buffer)) != -1)
            {
                out.write(buffer, 0, bytesInBuffer);
                bytesCopied += bytesInBuffer;
            }
            input.close();
            out.close();
            setValue(keyName, "DisplayIcon", iconPath);
        }
        catch (Exception exception)
        { // May be no icon resource defined; ignore it
            Debug.trace(exception);
        }
        setRoot(oldVal);
    }

    /**
     * @param idata
     */
    public boolean verify(AutomatedInstallData idata) throws Exception
    {
        super.verify(idata);
        return (true);

    }

    /**
     * Returns whether an action with this handler should be performed or not.
     *
     * @return always true
     */
    public boolean doPerform()
    {
        return true;
    }

    /**
     * Returns the default handler which is the first created registry handler.
     *
     * @return Returns the default handler.
     */
    public RegistryHandler getDefaultHandler()
    {
        return defaultHandler;
    }

}
