/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.openejb.config;

import org.apache.openejb.OpenEJBException;
import org.apache.openejb.jee.EnterpriseBean;
import org.apache.openejb.jee.EntityBean;
import org.apache.openejb.jee.ManagedBean;
import org.apache.openejb.jee.PersistenceType;
import org.apache.openejb.jee.oejb3.EjbDeployment;
import org.apache.openejb.jee.oejb3.OpenejbJar;
import org.apache.openejb.loader.SystemInstance;
import org.apache.openejb.util.LogCategory;
import org.apache.openejb.util.Logger;
import org.apache.openejb.util.StringTemplate;

import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

public class InitEjbDeployments implements DynamicDeployer {
    public static Logger logger = Logger.getInstance(LogCategory.OPENEJB, "org.apache.openejb.util.resources");

    private final StringTemplate deploymentIdTemplate;
    private static final String DEPLOYMENT_ID_FORMAT = "openejb.deploymentId.format";

    public InitEjbDeployments() {
        final String format = SystemInstance.get().getOptions().get(DEPLOYMENT_ID_FORMAT, "{ejbName}");
        this.deploymentIdTemplate = new StringTemplate(format);
    }

    public synchronized AppModule deploy(final AppModule appModule) throws OpenEJBException {

        final Set<String> abstractSchemaNames = new HashSet<String>();
        for (final EjbModule ejbModule : appModule.getEjbModules()) {
            for (final EnterpriseBean bean : ejbModule.getEjbJar().getEnterpriseBeans()) {
                if (isCmpEntity(bean)) {
                    final EntityBean entity = (EntityBean) bean;
                    final String name = entity.getAbstractSchemaName();
                    if (name != null) {
                        abstractSchemaNames.add(name);
                    }
                }
            }
        }


        final Map<String, String> contextData = new HashMap<String, String>();
        contextData.put("appId", appModule.getModuleId());

        for (final EjbModule ejbModule : appModule.getEjbModules()) {
            contextData.put("host", ejbModule.isWebapp() ? findHost(ejbModule.getModuleId(), appModule.getWebModules()) : appModule.uniqueHostIfExists());
            contextData.put("hash", Integer.toString(ejbModule.hashCode()));
            contextData.put("ejbJarId", ejbModule.getModuleId());
            deploy(ejbModule, contextData, abstractSchemaNames);
        }
        contextData.clear();
        return appModule;
    }

    private String findCommonHost(final Collection<WebModule> webModules) {
        String host = null;
        for (final WebModule w: webModules) {
            final String wHost = w.getHost();
            if (host == null) {
                host = wHost;
            } else if (!host.equals(wHost)) {
                return "lib"; // surely better to do
            }
        }
        return host != null ? host : "localhost";
    }

    private String findHost(final String id, final Collection<WebModule> webModules) {
        for (final WebModule w: webModules) {
            if (w.getModuleId().equals(id)) {
                final String host = w.getHost();
                return host != null ? host : "localhost";
            }
        }
        return "localhost";
    }

    public EjbModule deploy(final EjbModule ejbModule) throws OpenEJBException {
        return deploy(ejbModule, new HashMap<String, String>(), new HashSet<String>());
    }

    private EjbModule deploy(final EjbModule ejbModule, final Map<String, String> contextData, final Set<String> abstractSchemaNames) throws OpenEJBException {
        contextData.put("moduleId", ejbModule.getModuleId());
        contextData.put("moduleUri", ejbModule.getModuleUri().toString());

        final OpenejbJar openejbJar;
        if (ejbModule.getOpenejbJar() != null) {
            openejbJar = ejbModule.getOpenejbJar();
        } else {
            openejbJar = new OpenejbJar();
            ejbModule.setOpenejbJar(openejbJar);
        }

        StringTemplate deploymentIdTemplate = this.deploymentIdTemplate;
        if (openejbJar.getProperties().containsKey(DEPLOYMENT_ID_FORMAT)) {
            final String format = openejbJar.getProperties().getProperty(DEPLOYMENT_ID_FORMAT);
            logger.info("Using " + DEPLOYMENT_ID_FORMAT + " '" + format + "'");
            deploymentIdTemplate = new StringTemplate(format);
        }


        for (final EnterpriseBean bean : ejbModule.getEjbJar().getEnterpriseBeans()) {
            StringTemplate template = deploymentIdTemplate;

            final org.apache.openejb.api.EjbDeployment annotation = getEjbDeploymentAnnotation(ejbModule, bean);

            EjbDeployment ejbDeployment = openejbJar.getDeploymentsByEjbName().get(bean.getEjbName());
            if (ejbDeployment == null) {

                ejbDeployment = new EjbDeployment();

                ejbDeployment.setEjbName(bean.getEjbName());

                if (annotation != null && isDefined(annotation.id())) {
                    template = new StringTemplate(annotation.id());
                    ejbDeployment.setDeploymentId(formatDeploymentId(bean, contextData, template));
                } else {
                    ejbDeployment.setDeploymentId(formatDeploymentId(bean, contextData, template));
                    if (!(bean instanceof ManagedBean) || !((ManagedBean) bean).isHidden()) {
                        logger.info("Auto-deploying ejb " + bean.getEjbName() + ": EjbDeployment(deployment-id=" + ejbDeployment.getDeploymentId() + ")");
                    }
                }

                openejbJar.getEjbDeployment().add(ejbDeployment);
            } else if (ejbDeployment.getDeploymentId() == null) {
                if (annotation != null && isDefined(annotation.id())) {
                    template = new StringTemplate(annotation.id());
                    ejbDeployment.setDeploymentId(formatDeploymentId(bean, contextData, template));
                } else {
                    ejbDeployment.setDeploymentId(formatDeploymentId(bean, contextData, template));
                    logger.info("Auto-assigning deployment-id for ejb " + bean.getEjbName() + ": EjbDeployment(deployment-id=" + ejbDeployment.getDeploymentId() + ")");
                }
            }

            if (ejbDeployment.getContainerId() == null && annotation != null && isDefined(annotation.container())) {
                ejbDeployment.setContainerId(annotation.container());
            }

            if (isCmpEntity(bean)) {
                final EntityBean entity = (EntityBean) bean;
                if (entity.getAbstractSchemaName() == null) {
                    String abstractSchemaName = bean.getEjbName().trim().replaceAll("[ \\t\\n\\r-]+", "_");

                    // The AbstractSchemaName must be unique, we should check that it is
                    if (abstractSchemaNames.contains(abstractSchemaName)) {
                        int i = 2;
                        while (abstractSchemaNames.contains(abstractSchemaName + i)) {
                            i++;
                        }
                        abstractSchemaName = abstractSchemaName + i;
                    }

                    abstractSchemaNames.add(abstractSchemaName);
                    entity.setAbstractSchemaName(abstractSchemaName);
                }
            }
        }

        return ejbModule;
    }

    private org.apache.openejb.api.EjbDeployment getEjbDeploymentAnnotation(final EjbModule ejbModule, final EnterpriseBean bean) {
        try {
            final Class<?> beanClass = ejbModule.getClassLoader().loadClass(bean.getEjbClass());
            return beanClass.getAnnotation(org.apache.openejb.api.EjbDeployment.class);
        } catch (final ClassNotFoundException e) {
            // this should never happen, the class has already been loaded a ton of times by this point
            // unfortunately we have some unit tests that prevent us from throwing an exception just in case
            // Those are OpenEjb2ConversionTest and SunCmpConversionTest
            return null;
        }
    }

    private boolean isDefined(final String s) {
        return s != null && !"".equals(s);
    }

    private static boolean isCmpEntity(final EnterpriseBean bean) {
        return bean instanceof EntityBean && ((EntityBean) bean).getPersistenceType() == PersistenceType.CONTAINER;
    }

    private String formatDeploymentId(final EnterpriseBean bean, final Map<String, String> contextData, final StringTemplate template) {
        contextData.put("ejbType", bean.getClass().getSimpleName());
        contextData.put("ejbClass", bean.getEjbClass());

        // we don't have the ejb class object (only the string name) so we have
        // to extract the simple name from the FQN of the class
        final int simpleNameIdx = bean.getEjbClass().lastIndexOf(".");
        contextData.put("ejbClass.simpleName", bean.getEjbClass().substring(simpleNameIdx + 1));

        contextData.put("ejbName", bean.getEjbName());

        final String name = template.apply(contextData);
        if (bean instanceof CompManagedBean) { // avoid conflict in ear between an ejbmodule and a war using the same name
            return name + System.identityHashCode(bean);
        }
        return name;
    }
}
