/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */

package org.jboss.as.naming.util;

import javax.naming.CannotProceedException;
import javax.naming.Context;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.NotContextException;
import org.jboss.as.naming.logging.NamingLogger;

import java.util.Collection;
import java.util.Iterator;

/**
 * Common utility functions used by the naming implementation.
 *
 * @author John E. Bailey
 */
public class NamingUtils {
    private NamingUtils() {}

    /**
     * Create a subcontext including any intermediate contexts.
     * @param ctx the parent JNDI Context under which value will be bound
     * @param name the name relative to ctx of the subcontext.
     * @return The new or existing JNDI subcontext
     * @throws NamingException on any JNDI failure
     */
    private static Context createSubcontext(Context ctx, final Name name) throws NamingException {
        Context subctx = ctx;
        for (int pos = 0; pos < name.size(); pos++) {
            final String ctxName = name.get(pos);
            try {
                subctx = (Context) ctx.lookup(ctxName);
            }
            catch (NameNotFoundException e) {
                subctx = ctx.createSubcontext(ctxName);
            }
            // The current subctx will be the ctx for the next name component
            ctx = subctx;
        }
        return subctx;
    }

    /**
     * Get the last component of a name.
     *
     * @param name the name
     * @return the last component
     */
    public static String getLastComponent(final Name name) {
        if(name.size() > 0)
            return name.get(name.size() - 1);
        return "";
    }

    /**
     * Determine if a name is empty, or if ot contains only one component which is the empty string.
     *
     * @param name the name
     * @return {@code true} if the name is empty or contains one empty component
     */
    public static boolean isEmpty(final Name name) {
        return name.isEmpty() || (name.size() == 1 && "".equals(name.get(0)));
    }

    /**
     * Determine whether the last component is empty.
     *
     * @param name the name to test
     * @return {@code true} if the last component is empty, or if the name is empty
     */
    public static boolean isLastComponentEmpty(final Name name) {
        return name.isEmpty() || getLastComponent(name).equals("");
    }

    /**
     * Create a name-not-found exception.
     *
     * @param name the name
     * @param contextName the context name
     * @return the exception
     */
    public static NameNotFoundException nameNotFoundException(final String name, final Name contextName) {
        return NamingLogger.ROOT_LOGGER.nameNotFoundInContext(name, contextName);
    }

    /**
     * Create a name-already-bound exception.
     *
     * @param name the name
     * @return the exception
     */
    public static NameAlreadyBoundException nameAlreadyBoundException(final Name name) {
        return new NameAlreadyBoundException(name.toString());
    }

    /**
     * Create an invalid name exception for an empty name.
     *
     * @return the exception
     */
    public static InvalidNameException emptyNameException() {
        return NamingLogger.ROOT_LOGGER.emptyNameNotAllowed();
    }

    /**
     * Return a not-context exception for a name.
     *
     * @param name the name
     * @return the exception
     */
    public static NotContextException notAContextException(Name name) {
        return new NotContextException(name.toString());
    }

    /**
     * Return a general naming exception with a root cause.
     *
     * @param message the message
     * @param cause the exception cause, or {@code null} for none
     * @return the exception
     */
    public static NamingException namingException(final String message, final Throwable cause) {
        final NamingException exception = new NamingException(message);
        if (cause != null) exception.initCause(cause);
        return exception;
    }

    /**
     * Return a general naming exception with a root cause and a remaining name field.
     *
     * @param message the message
     * @param cause the exception cause, or {@code null} for none
     * @param remainingName the remaining name
     * @return the exception
     */
    public static NamingException namingException(final String message, final Throwable cause, final Name remainingName) {
        final NamingException exception = namingException(message, cause);
        exception.setRemainingName(remainingName);
        return exception;
    }

    /**
     * Return a cannot-proceed exception.
     *
     * @param resolvedObject the resolved object
     * @param remainingName the remaining name
     * @return the exception
     */
    public static CannotProceedException cannotProceedException(final Object resolvedObject, final Name remainingName) {
        final CannotProceedException cpe = new CannotProceedException();
        cpe.setResolvedObj(resolvedObject);
        cpe.setRemainingName(remainingName);
        return cpe;
    }

    /**
     * Return a naming enumeration over a collection.
     *
     * @param collection the collection
     * @param <T> the member type
     * @return the enumeration
     */
    public static <T> NamingEnumeration<T> namingEnumeration(final Collection<T> collection) {
        final Iterator<T> iterator = collection.iterator();
        return new NamingEnumeration<T>() {
            public T next() {
                return nextElement();
            }

            public boolean hasMore() {
                return hasMoreElements();
            }

            public void close() {
            }

            public boolean hasMoreElements() {
                return iterator.hasNext();
            }

            public T nextElement() {
                return iterator.next();
            }
        };
    }

    /**
     * Rebind val to name in ctx, and make sure that all intermediate contexts exist
     *
     * @param ctx the parent JNDI Context under which value will be bound
     * @param name the name relative to ctx where value will be bound
     * @param value the value to bind.
     * @throws NamingException for any error
     */
    public static void rebind(final Context ctx, final String name, final Object value) throws NamingException {
       final Name n = ctx.getNameParser("").parse(name);
       rebind(ctx, n, value);
    }

    /**
     * Rebind val to name in ctx, and make sure that all intermediate contexts exist
     *
     * @param ctx the parent JNDI Context under which value will be bound
     * @param name the name relative to ctx where value will be bound
     * @param value the value to bind.
     * @throws NamingException for any error
     */
    public static void rebind(final Context ctx, final Name name, final Object value) throws NamingException {
       final int size = name.size();
       final String atom = name.get(size - 1);
       final Context parentCtx = createSubcontext(ctx, name.getPrefix(size - 1));
       parentCtx.rebind(atom, value);
    }

    /**
     * Unbinds a name from ctx, and removes parents if they are empty
     *
     * @param ctx  the parent JNDI Context under which the name will be unbound
     * @param name The name to unbind
     * @throws NamingException for any error
     */
    public static void unbind(Context ctx, String name) throws NamingException {
        unbind(ctx, ctx.getNameParser("").parse(name));
    }

    /**
     * Unbinds a name from ctx, and removes parents if they are empty
     *
     * @param ctx  the parent JNDI Context under which the name will be unbound
     * @param name The name to unbind
     * @throws NamingException for any error
     */
    public static void unbind(Context ctx, Name name) throws NamingException {
        ctx.unbind(name); //unbind the end node in the name
        int sz = name.size();
        // walk the tree backwards, stopping at the domain
        while (--sz > 0) {
            Name pname = name.getPrefix(sz);
            try {
                ctx.destroySubcontext(pname);
            }
            catch (NamingException e) {
                //log.trace("Unable to remove context " + pname, e);
                break;
            }
        }
    }
}
