/**
 *
 * Copyright 2014 Vyacheslav Blinov, 2017 Florian Schmaus.
 *
 * 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 org.jivesoftware.smack.debugger;

import java.lang.reflect.Constructor;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;

public final class ReflectionDebuggerFactory implements SmackDebuggerFactory {
    private static final Logger LOGGER = Logger.getLogger(ReflectionDebuggerFactory.class.getName());
    private static final String DEBUGGER_CLASS_PROPERTY_NAME = "smack.debuggerClass";

    public static final ReflectionDebuggerFactory INSTANCE = new ReflectionDebuggerFactory();

    private ReflectionDebuggerFactory() {
    }

    /**
     * Possible default debugger implementations. The order of enumeration is the one in which we try
     * to instantiate these.
     */
    private static final String[] DEFAULT_DEBUGGERS = new String[] {
            "org.jivesoftware.smackx.debugger.EnhancedDebugger",
            "org.jivesoftware.smackx.debugger.android.AndroidDebugger",
            "org.jivesoftware.smack.debugger.ConsoleDebugger",
            "org.jivesoftware.smack.debugger.LiteDebugger",
            "org.jivesoftware.smack.debugger.JulDebugger",
            };

    /**
     * Sets custom debugger class to be created by this factory.
     * @param debuggerClass class to be used by this factory
     */
    public static void setDebuggerClass(Class<? extends SmackDebugger> debuggerClass) {
        if (debuggerClass == null) {
            System.clearProperty(DEBUGGER_CLASS_PROPERTY_NAME);
        } else {
            System.setProperty(DEBUGGER_CLASS_PROPERTY_NAME, debuggerClass.getCanonicalName());
        }
    }

    /**
     * Returns debugger class used by this factory.
     * @return debugger class that will be used for instantiation by this factory
     */
    @SuppressWarnings("unchecked")
    public static Class<SmackDebugger> getDebuggerClass() {
        String customDebuggerClassName = getCustomDebuggerClassName();
        if (customDebuggerClassName == null) {
            return getOneOfDefaultDebuggerClasses();
        } else {
            try {
                return (Class<SmackDebugger>) Class.forName(customDebuggerClassName);
            } catch (Exception e) {
                LOGGER.log(Level.WARNING, "Unable to instantiate debugger class " + customDebuggerClassName, e);
            }
        }
        // no suitable debugger class found - give up
        return null;
    }

    @Override
    public SmackDebugger create(XMPPConnection connection) throws IllegalArgumentException {
        Class<SmackDebugger> debuggerClass = getDebuggerClass();
        if (debuggerClass != null) {
            // Create a new debugger instance using 3arg constructor
            try {
                Constructor<SmackDebugger> constructor = debuggerClass
                        .getConstructor(XMPPConnection.class);
                return constructor.newInstance(connection);
            } catch (Exception e) {
                throw new IllegalArgumentException("Can't initialize the configured debugger!", e);
            }
        }
        return null;
    }

    private static String getCustomDebuggerClassName() {
        try {
            // Use try block since we may not have permission to get a system
            // property (for example, when an applet).
            return System.getProperty(DEBUGGER_CLASS_PROPERTY_NAME);
        } catch (Throwable t) {
            // Ignore.
            return null;
        }
    }

    @SuppressWarnings("unchecked")
    private static Class<SmackDebugger> getOneOfDefaultDebuggerClasses() {
        for (String debugger : DEFAULT_DEBUGGERS) {
            if (SmackConfiguration.isDisabledSmackClass(debugger)) {
                continue;
            }
            try {
                return (Class<SmackDebugger>) Class.forName(debugger);
            } catch (ClassNotFoundException cnfe) {
                LOGGER.fine("Did not find debugger class '" + debugger + "'");
            } catch (ClassCastException ex) {
                LOGGER.warning("Found debugger class that does not appears to implement SmackDebugger interface");
            } catch (Exception ex) {
                LOGGER.warning("Unable to instantiate either Smack debugger class");
            }
        }
        // did not found any of default debuggers - give up
        return null;
    }
}
