/*
 * 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.coi.tools.os.win;

import java.util.*;

/**
 * A exception class which will be used from the native part of system dependent classes to signal
 * exceptions. The native methods writes only symbolic error messages, the language dependant
 * mapping will be done in this class.
 *
 * @author Klaus Bartz
 */
public class NativeLibException extends Exception
{

    private static final long serialVersionUID = 3257002172494721080L;

    /**
     * Map of founded resource bundles which contains the localized error messages.
     */
    private final static HashMap<String, ResourceBundle> messageResourceBundles = new HashMap<String, ResourceBundle>();

    /**
     * Internal error as number.
     */
    private int libErr;

    /**
     * OS error as number.
     */
    private int osErr;

    /**
     * Internal error message; contains most the symbolic error name.
     */
    private String libErrString;

    /**
     * OS error string; if possible localized.
     */
    private String osErrString;

    /**
     * Additional arguments.
     */
    private ArrayList<String> args = new ArrayList<String>();

    static
    {
        // add the first resource bundle
        addResourceBundle("com.coi.tools.os.win.resources.NativeLibErr");
    }

    /**
     * Adds a resource bundle which contains localized error messages. The bundlePath should contain
     * a string with which the bundle is loadable with ResourceBundle.getBundle, may be the full
     * class path to a ListResourceBundle. The localize is done by getBundle, therefore the path
     * should not contain the locale substring. At a call to getMessage the bundle is searched with
     * the libErrString as key. If it exist, the value of it is used by getMessage, else the
     * libErrString self.
     *
     * @param bundlePath path of bundle without locale
     */
    public static void addResourceBundle(String bundlePath)
    {
        ResourceBundle bd = null;
        if (messageResourceBundles.containsKey(bundlePath))
        {
            return;
        }
        try
        {
            bd = ResourceBundle.getBundle(bundlePath);
        }
        catch (MissingResourceException mre)
        {
            mre.printStackTrace();
        }
        messageResourceBundles.put(bundlePath, bd);

    }

    /**
     * The constructor.
     */
    public NativeLibException()
    {
        super();
    }

    /**
     * Creates a NativeLibException with the given message.
     *
     * @param message to be used
     */
    public NativeLibException(String message)
    {
        super(message);
    }

    /**
     * Creates a NativeLibException with the given cause.
     *
     * @param cause to be used
     */
    public NativeLibException(Throwable cause)
    {
        super(cause);
    }

    /**
     * Creates a NativeLibException with the given message and cause.
     *
     * @param message message to be used
     * @param cause   cause to be used
     */
    public NativeLibException(String message, Throwable cause)
    {
        super(message, cause);
    }

    /**
     * Creates a NativeLibException with the given values.
     *
     * @param libErr    identifier of the internal handled error
     * @param osErr     system error number
     * @param libString message for the internal handled error
     * @param osString  system error message
     */
    public NativeLibException(int libErr, int osErr, String libString, String osString)
    {
        super();
        this.libErr = libErr;
        this.osErr = osErr;
        libErrString = libString;
        osErrString = osString;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.lang.Throwable#getMessage()
     */
    public String getMessage()
    {
        StringBuffer retval = new StringBuffer();
        boolean next = false;
        if (libErrString != null)
        {
            retval.append(getLocalizedLibMessage());
            next = true;
        }
        else if (libErr != 0)
        {
            if (next)
            {
                retval.append("\n");
            }
            next = true;
            retval.append(getMsg("libErrNumber." + Integer.toString(libErr)));
        }
        if (osErr != 0)
        {
            if (next)
            {
                retval.append("\n");
            }
            next = true;
            retval.append(getMsg("libInternal.OsErrNumPraefix")).append(Integer.toString(osErr));
        }
        if (osErrString != null)
        {
            if (next)
            {
                retval.append("\n");
            }
            next = true;
            // Message self should be localized in the native part
            retval.append(getMsg("libInternal.OsErrStringPraefix")).append(getOsMessage());
        }
        if (retval.length() > 0)
        {
            return (reviseMsgWithArgs(retval.toString()));
        }
        return null;
    }

    /**
     * Returns the number of the internal handled error.
     *
     * @return the number of the internal handled error
     */
    public int getLibErr()
    {
        return libErr;
    }

    /**
     * Returns the message of the internal handled error.
     *
     * @return the messager of the internal handled error
     */
    public String getLibMessage()
    {
        return libErrString;
    }

    /**
     * Returns the localized message of the internal handled error.
     *
     * @return the localized message of the internal handled error
     */
    public String getLocalizedLibMessage()
    {
        return (getMsg(libErrString));
    }

    /**
     * Returns the number of the system error.
     *
     * @return the number of the system error
     */
    public int getOsErr()
    {
        return (osErr);
    }

    /**
     * Returns the message of the system error.
     *
     * @return the messager of the system error
     */
    public String getOsMessage()
    {
        return (osErrString);
    }

    /**
     * Adds a string to the internal argument list.
     *
     * @param arg string to be added to the internal argument list
     */
    public void addArgument(String arg)
    {
        args.add(arg);
    }

    /**
     * Returns the internal argument list.
     *
     * @return the internal argument list
     */
    public ArrayList<String> getArguments()
    {
        return (args);
    }

    /**
     * Revise placeholder in the given message with the setted arguments
     *
     * @param msg message to be revised
     * @return revised message
     */
    public String reviseMsgWithArgs(String msg)
    {
        for (int i = 0; i < args.size(); ++i)
        {
            String key = "{" + Integer.toString(i) + "}";
            msg = replaceString(msg, key, args.get(i));
        }
        return (msg);
    }

    /**
     * Searches the resource bundles for a string which coresponds to the given string as key.
     *
     * @param s string which should be used as keys for the resource bundle
     * @return the founded message as int value
     */

    private String getMsg(String s)
    {
        Iterator<ResourceBundle> it = messageResourceBundles.values().iterator();
        while (it.hasNext())
        {
            try
            {
                return ((it.next()).getString(s));
            }
            catch (MissingResourceException missingresourceexception)
            { // do not throw, else look in next bundle.
            }
        }
        return (s);
    }

    /**
     * Returns a string resulting from replacing all occurrences of what in this string with with.
     * In opposite to the String.replaceAll method this method do not use regular expression or
     * other methods which are only available in JRE 1.4 and later.
     *
     * @param destination string for which the replacing should be performed
     * @param what        what string should be replaced
     * @param with        with what string what should be replaced
     * @return a new String object if what was found in the given string, else the given string self
     */
    private static String replaceString(String destination, String what, String with)
    {
        if (destination.indexOf(what) >= 0)
        { // what found, with (placeholder) not included in destination ->
            // perform changing.
            StringBuffer buf = new StringBuffer();
            int last = 0;
            int current = destination.indexOf(what);
            int whatLength = what.length();
            while (current >= 0)
            { // Do not use Methods from JRE 1.4 and higher ...
                if (current > 0)
                {
                    buf.append(destination.substring(last, current));
                }
                buf.append(with);
                last = current + whatLength;
                current = destination.indexOf(what, last);
            }
            if (destination.length() > last)
            {
                buf.append(destination.substring(last));
            }
            return buf.toString();
        }
        return destination;
    }

}
