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

package org.wildfly.iiop.openjdk.naming.jndi;

import javax.naming.CannotProceedException;
import javax.naming.CompositeName;
import javax.naming.ContextNotEmptyException;
import javax.naming.InvalidNameException;
import javax.naming.Name;
import javax.naming.NameAlreadyBoundException;
import javax.naming.NameNotFoundException;
import javax.naming.NamingException;
import javax.naming.RefAddr;
import javax.naming.Reference;
import javax.naming.spi.NamingManager;

import org.omg.CosNaming.NameComponent;
import org.omg.CosNaming.NamingContext;
import org.omg.CosNaming.NamingContextPackage.AlreadyBound;
import org.omg.CosNaming.NamingContextPackage.CannotProceed;
import org.omg.CosNaming.NamingContextPackage.InvalidName;
import org.omg.CosNaming.NamingContextPackage.NotEmpty;
import org.omg.CosNaming.NamingContextPackage.NotFound;
import org.omg.CosNaming.NamingContextPackage.NotFoundReason;
import org.wildfly.iiop.openjdk.logging.IIOPLogger;

/**
 * A convenience class to map the COS Naming exceptions to the JNDI exceptions.
 *
 * @author Raj Krishnamurthy
 */

public final class ExceptionMapper {
    private ExceptionMapper() {
    } // ensure no instance

    private static final boolean debug = false;

    public static NamingException mapException(Exception e, CNCtx ctx, NameComponent[] inputName) throws NamingException {
        if (e instanceof NamingException) {
            return (NamingException) e;
        }

        if (e instanceof RuntimeException) {
            throw (RuntimeException) e;
        }

        NamingException ne;
        if (e instanceof NotFound) {
            if (ctx.federation) {
                return tryFed((NotFound) e, ctx, inputName);

            } else {
                ne = new NameNotFoundException();
            }

        } else if (e instanceof CannotProceed) {

            ne = new CannotProceedException();
            NamingContext nc = ((CannotProceed) e).cxt;
            NameComponent[] rest = ((CannotProceed) e).rest_of_name;

            // %%% We assume that rest returns *all* unprocessed components.
            // Don't' know if that is a good assumption, given
            // NotFound doesn't set rest as expected. -RL
            if (inputName != null && (inputName.length > rest.length)) {
                NameComponent[] resolvedName =
                        new NameComponent[inputName.length - rest.length];
                System.arraycopy(inputName, 0, resolvedName, 0, resolvedName.length);
                // Wrap resolved NamingContext inside a CNCtx
                // Guess that its name (which is relative to ctx)
                // is the part of inputName minus rest_of_name
                ne.setResolvedObj(new CNCtx(ctx._orb, nc, ctx._env, ctx.makeFullName(resolvedName)));
            } else {
                ne.setResolvedObj(ctx);
            }

            ne.setRemainingName(CNNameParser.cosNameToName(rest));

        } else if (e instanceof InvalidName) {
            ne = new InvalidNameException();
        } else if (e instanceof AlreadyBound) {
            ne = new NameAlreadyBoundException();
        } else if (e instanceof NotEmpty) {
            ne = new ContextNotEmptyException();
        } else {
            ne = new NamingException();
        }

        ne.setRootCause(e);
        return ne;
    }

    private static NamingException tryFed(NotFound e, CNCtx ctx,
                                                NameComponent[] inputName) throws NamingException {
        NameComponent[] rest = ((NotFound) e).rest_of_name;

        if (debug) {
        }

        // %%% Using 1.2 & 1.3 Sun's tnameserv, 'rest' contains only the first
        // component that failed, not *rest* as advertized. This is useless
        // because what if you have something like aa/aa/aa/aa/aa.
        // If one of those is not found, you get "aa" as 'rest'.
        if (rest.length == 1 && inputName != null) {
            // Check that we're not talking to 1.2/1.3 Sun tnameserv
            NameComponent lastIn = inputName[inputName.length - 1];
            if (rest[0].id.equals(lastIn.id) &&
                    rest[0].kind != null &&
                    rest[0].kind.equals(lastIn.kind)) {
                // Might be legit
            } else {
                // Due to 1.2/1.3 bug that always returns single-item 'rest'
                NamingException ne = new NameNotFoundException();
                ne.setRemainingName(CNNameParser.cosNameToName(rest));
                ne.setRootCause(e);
                throw ne;
            }
        }
        // Fixed in 1.4; perform calculations based on correct (1.4) behavior

        // Calculate the components of the name that has been resolved
        NameComponent[] resolvedName = null;
        int len = 0;
        if (inputName != null && (inputName.length >= rest.length)) {

            if (e.why == NotFoundReason.not_context) {
                // First component of rest is found but not a context; keep it
                // as part of resolved name
                len = inputName.length - (rest.length - 1);

                // Remove resolved component from rest
                if (rest.length == 1) {
                    // No more remaining
                    rest = null;
                } else {
                    NameComponent[] tmp = new NameComponent[rest.length - 1];
                    System.arraycopy(rest, 1, tmp, 0, tmp.length);
                    rest = tmp;
                }
            } else {
                len = inputName.length - rest.length;
            }

            if (len > 0) {
                resolvedName = new NameComponent[len];
                System.arraycopy(inputName, 0, resolvedName, 0, len);
            }
        }

        // Create CPE and set common fields
        CannotProceedException cpe = new CannotProceedException();
        cpe.setRootCause(e);
        if (rest != null && rest.length > 0) {
            cpe.setRemainingName(CNNameParser.cosNameToName(rest));
        }
        cpe.setEnvironment(ctx._env);

        // Lookup resolved name to get resolved object
        final Object resolvedObj =
                (resolvedName != null) ? ctx.callResolve(resolvedName) : ctx;

        if (resolvedObj instanceof javax.naming.Context) {
            // obj is a context and child is not found
            // try getting its nns dynamically by constructing
            // a Reference containing obj.
            RefAddr addr = new RefAddr("nns") {
                public Object getContent() {
                    return resolvedObj;
                }

                private static final long serialVersionUID =
                        669984699392133792L;
            };
            Reference ref = new Reference("java.lang.Object", addr);

            // Resolved name has trailing slash to indicate nns
            CompositeName cname = new CompositeName();
            cname.add(""); // add trailing slash

            cpe.setResolvedObj(ref);
            cpe.setAltName(cname);
            cpe.setAltNameCtx((javax.naming.Context) resolvedObj);

            return cpe;
        } else {
            // Not a context, use object factory to transform object.

            Name cname = CNNameParser.cosNameToName(resolvedName);
            Object resolvedObj2;
            try {
                resolvedObj2 = NamingManager.getObjectInstance(resolvedObj,
                        cname, ctx, ctx._env);
            } catch (NamingException ge) {
                throw ge;
            } catch (Exception ge) {
                NamingException ne = IIOPLogger.ROOT_LOGGER.errorGeneratingObjectViaFactory();
                ne.setRootCause(ge);
                throw ne;
            }

            // If a context, continue operation with context
            if (resolvedObj2 instanceof javax.naming.Context) {
                cpe.setResolvedObj(resolvedObj2);
            } else {
                // Add trailing slash
                cname.add("");
                cpe.setAltName(cname);

                // Create nns reference
                final Object rf2 = resolvedObj2;
                RefAddr addr = new RefAddr("nns") {
                    public Object getContent() {
                        return rf2;
                    }

                    private static final long serialVersionUID =
                            -785132553978269772L;
                };
                Reference ref = new Reference("java.lang.Object", addr);
                cpe.setResolvedObj(ref);
                cpe.setAltNameCtx(ctx);
            }
            return cpe;
        }
    }
}
