/*
 * Copyright The WildFly Authors
 * SPDX-License-Identifier: Apache-2.0
 */
package org.jboss.as.weld.ejb;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import org.jboss.as.ee.component.ViewDescription;
import org.jboss.as.ejb3.component.EJBComponentDescription;
import org.jboss.as.ejb3.component.EJBViewDescription;
import org.jboss.as.ejb3.component.session.SessionBeanComponentDescription;
import org.jboss.as.ejb3.component.stateful.StatefulComponentDescription;
import org.jboss.as.server.deployment.reflect.ClassReflectionIndex;
import org.jboss.as.server.deployment.reflect.DeploymentReflectionIndex;
import org.jboss.metadata.ejb.spec.MethodInterfaceType;
import org.jboss.msc.service.ServiceName;
import org.jboss.weld.bootstrap.spi.BeanDeploymentArchive;
import org.jboss.weld.ejb.spi.BusinessInterfaceDescriptor;
import org.jboss.weld.ejb.spi.EjbDescriptor;
import org.jboss.weld.resources.spi.ResourceLoader;

/**
 * Implementation of EjbDescriptor
 *
 * @author Stuart Douglas
 */
public class EjbDescriptorImpl<T> implements EjbDescriptor<T> {

    private final ServiceName baseName;
    private final Set<BusinessInterfaceDescriptor<?>> localInterfaces;
    private final Set<BusinessInterfaceDescriptor<?>> remoteInterfaces;
    private final Map<Class<?>, ServiceName> viewServices;
    private final Set<Method> removeMethods;
    private final Class<T> ejbClass;
    private final String ejbName;


    private final boolean stateless;
    private final boolean stateful;
    private final boolean singleton;
    private final boolean messageDriven;
    private final boolean passivationCapable;

    public EjbDescriptorImpl(EJBComponentDescription componentDescription, BeanDeploymentArchive beanDeploymentArchive, final DeploymentReflectionIndex reflectionIndex) {
        final SessionBeanComponentDescription description = componentDescription instanceof SessionBeanComponentDescription ? (SessionBeanComponentDescription) componentDescription : null;
        final Set<BusinessInterfaceDescriptor<?>> localInterfaces = new HashSet<BusinessInterfaceDescriptor<?>>();
        final Set<BusinessInterfaceDescriptor<?>> remoteInterfaces = new HashSet<BusinessInterfaceDescriptor<?>>();
        final ResourceLoader loader = beanDeploymentArchive.getServices().get(ResourceLoader.class);

        ejbClass = (Class<T>) loader.classForName(componentDescription.getEJBClassName());

        if (componentDescription.getViews() != null) {
            for (ViewDescription view : componentDescription.getViews()) {

                if (description == null || getMethodIntf(view) == MethodInterfaceType.Local) {
                    final String viewClassName = view.getViewClassName();
                    localInterfaces.add(new BusinessInterfaceDescriptorImpl<Object>(beanDeploymentArchive, viewClassName));
                } else if (getMethodIntf(view) == MethodInterfaceType.Remote) {
                    remoteInterfaces.add(new BusinessInterfaceDescriptorImpl<Object>(beanDeploymentArchive, view.getViewClassName()));
                }
            }
        }
        if(componentDescription instanceof StatefulComponentDescription) {
            Set<Method> removeMethods = new HashSet<Method>();
            final Collection<StatefulComponentDescription.StatefulRemoveMethod> methods = ((StatefulComponentDescription) componentDescription).getRemoveMethods();
            for(final StatefulComponentDescription.StatefulRemoveMethod method : methods) {
                Class<?> c = ejbClass;
                while (c != null && c != Object.class) {
                    ClassReflectionIndex index = reflectionIndex.getClassIndex(c);
                    Method m = index.getMethod(method.getMethodIdentifier());
                    if(m != null) {
                        removeMethods.add(m);
                    }
                    c = c.getSuperclass();
                }
            }
            this.removeMethods = Collections.unmodifiableSet(removeMethods);
        } else {
            removeMethods = Collections.emptySet();
        }


        this.ejbName = componentDescription.getEJBName();
        this.localInterfaces = localInterfaces;
        this.remoteInterfaces = remoteInterfaces;
        this.baseName = componentDescription.getServiceName();
        this.stateless = componentDescription.isStateless();
        this.messageDriven = componentDescription.isMessageDriven();
        this.stateful = componentDescription.isStateful();
        this.singleton = componentDescription.isSingleton();

        this.passivationCapable = componentDescription.isPassivationApplicable();

        final Map<Class<?>, ServiceName> viewServices = new HashMap<Class<?>, ServiceName>();

        Map<Class<?>, ServiceName> viewServicesMap = new HashMap<Class<?>, ServiceName>();
        for (ViewDescription view : componentDescription.getViews()) {
            viewServicesMap.put(loader.classForName(view.getViewClassName()), view.getServiceName());
        }

        for (Map.Entry<Class<?>, ServiceName> entry : viewServicesMap.entrySet()) {
            final Class<?> viewClass = entry.getKey();
            if (viewClass != null) {
                //see WELD-921
                //this is horrible, but until it is fixed there is not much that can be done

                final Set<Class<?>> seen = new HashSet<Class<?>>();
                final Set<Class<?>> toProcess = new HashSet<Class<?>>();

                toProcess.add(viewClass);

                while (!toProcess.isEmpty()) {
                    Iterator<Class<?>> it = toProcess.iterator();
                    final Class<?> clazz = it.next();
                    it.remove();
                    seen.add(clazz);
                    viewServices.put(clazz, entry.getValue());
                    final Class<?> superclass = clazz.getSuperclass();
                    if (superclass != Object.class && superclass != null && !seen.contains(superclass)) {
                        toProcess.add(superclass);
                    }
                    for (Class<?> iface : clazz.getInterfaces()) {
                        if (!seen.contains(iface)) {
                            toProcess.add(iface);
                        }
                    }
                }
            }
        }
        this.viewServices = viewServices;
    }

    private MethodInterfaceType getMethodIntf(final ViewDescription view) {
        if (view instanceof EJBViewDescription) {
            final EJBViewDescription ejbView = (EJBViewDescription) view;
            return ejbView.getMethodIntf();
        }

        return null;
    }

    @Override
    public Class<T> getBeanClass() {
        return ejbClass;
    }

    @Override
    public Collection<BusinessInterfaceDescriptor<?>> getLocalBusinessInterfaces() {
        return localInterfaces;
    }

    @Override
    public Collection<BusinessInterfaceDescriptor<?>> getRemoteBusinessInterfaces() {
        return remoteInterfaces;
    }

    @Override
    public String getEjbName() {
        return ejbName;
    }

    @Override
    public Collection<Method> getRemoveMethods() {
        return removeMethods;
    }

    @Override
    public boolean isStateless() {
        return stateless;
    }

    @Override
    public boolean isSingleton() {
        return singleton;
    }

    @Override
    public boolean isStateful() {
        return stateful;
    }

    @Override
    public boolean isMessageDriven() {
        return messageDriven;
    }

    public ServiceName getBaseName() {
        return baseName;
    }

    public ServiceName getCreateServiceName() {
        return baseName.append("CREATE");
    }

    public ServiceName getStartServiceName() {
        return baseName.append("START");
    }

    public Map<Class<?>, ServiceName> getViewServices() {
        return viewServices;
    }

    @Override
    public boolean isPassivationCapable() {
        return passivationCapable;
    }

}
