/*
 * 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.netbeans.modules.javaee.wildfly.ide;

import java.io.BufferedReader;
import java.io.File;
import java.io.FilenameFilter;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.enterprise.deploy.spi.DeploymentManager;
import org.netbeans.api.j2ee.core.Profile;
import org.netbeans.api.java.platform.JavaPlatform;
import org.netbeans.api.project.Project;
import org.netbeans.modules.j2ee.deployment.common.api.J2eeLibraryTypeProvider;
import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eeModule.Type;
import org.netbeans.modules.j2ee.deployment.devmodules.api.J2eePlatform;
import org.netbeans.modules.j2ee.deployment.plugins.api.InstanceProperties;
import org.netbeans.modules.j2ee.deployment.plugins.spi.J2eePlatformFactory;
import org.netbeans.modules.j2ee.deployment.plugins.spi.J2eePlatformImpl;
import org.netbeans.modules.j2ee.deployment.plugins.spi.J2eePlatformImpl2;
import org.netbeans.modules.j2ee.deployment.plugins.spi.support.LookupProviderSupport;
import org.netbeans.modules.javaee.specs.support.api.JaxWs;
import org.netbeans.modules.javaee.specs.support.spi.JaxRsStackSupportImplementation;
import org.netbeans.modules.javaee.wildfly.WildflyDeploymentManager;
import org.netbeans.modules.javaee.wildfly.ide.ui.WildflyPluginUtils;
import org.netbeans.modules.javaee.wildfly.util.WildFlyProperties;
import org.netbeans.modules.websvc.wsstack.api.WSStack;
import org.netbeans.modules.websvc.wsstack.spi.WSStackFactory;
import org.netbeans.spi.project.libraries.LibraryImplementation;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.modules.InstalledFileLocator;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.NbBundle;
import org.openide.util.lookup.Lookups;

/**
 *
 * @author Kirill Sorokin <Kirill.Sorokin@Sun.COM>
 */
public class WildflyJ2eePlatformFactory extends J2eePlatformFactory {

    static final String HIBERNATE_JPA_PROVIDER = "org.hibernate.ejb.HibernatePersistence";

    static final String TOPLINK_JPA_PROVIDER = "oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider";

    static final String KODO_JPA_PROVIDER = "kodo.persistence.PersistenceProviderImpl";

    private static final WeakHashMap<InstanceProperties, J2eePlatformImplImpl> instanceCache = new WeakHashMap<InstanceProperties, J2eePlatformImplImpl>();

    @Override
    public synchronized J2eePlatformImpl getJ2eePlatformImpl(DeploymentManager dm) {
        WildflyDeploymentManager manager = (WildflyDeploymentManager) dm;
        InstanceProperties ip = manager.getInstanceProperties();
        if (ip == null) {
            throw new RuntimeException("Cannot create J2eePlatformImpl instance for " + manager.getUrl()); // NOI18N
        }
        J2eePlatformImplImpl platform = instanceCache.get(ip);
        if (platform == null) {
            platform = new J2eePlatformImplImpl(manager.getProperties());
            instanceCache.put(ip, platform);
        }
        return platform;
    }

    public static class J2eePlatformImplImpl extends J2eePlatformImpl2 {

        private static final Set<Type> MODULE_TYPES = new HashSet<Type>();

        static {
            MODULE_TYPES.add(Type.EAR);
            MODULE_TYPES.add(Type.WAR);
            MODULE_TYPES.add(Type.EJB);
            MODULE_TYPES.add(Type.RAR);
            MODULE_TYPES.add(Type.CAR);
        }

        private static final Set<Profile> WILDFLY_PROFILES = new HashSet<Profile>();

        static {
            WILDFLY_PROFILES.add(Profile.JAVA_EE_6_WEB);
            WILDFLY_PROFILES.add(Profile.JAVA_EE_6_FULL);
            WILDFLY_PROFILES.add(Profile.JAVA_EE_7_WEB);
            WILDFLY_PROFILES.add(Profile.JAVA_EE_7_FULL);
            WILDFLY_PROFILES.add(Profile.JAVA_EE_8_WEB);
            WILDFLY_PROFILES.add(Profile.JAVA_EE_8_FULL);
            WILDFLY_PROFILES.add(Profile.JAKARTA_EE_8_FULL);
        }
        private static final Set<Profile> JAKARTAEE_FULL_PROFILES = new HashSet<Profile>();

        static {
            JAKARTAEE_FULL_PROFILES.add(Profile.JAKARTA_EE_9_FULL);
            JAKARTAEE_FULL_PROFILES.add(Profile.JAKARTA_EE_9_1_FULL);
            JAKARTAEE_FULL_PROFILES.add(Profile.JAKARTA_EE_10_FULL);
        }
        private static final Set<Profile> EAP6_PROFILES = new HashSet<Profile>();

        static {
            EAP6_PROFILES.add(Profile.JAVA_EE_6_WEB);
            EAP6_PROFILES.add(Profile.JAVA_EE_6_FULL);
        }

        private static final Set<Profile> WILDFLY_WEB_PROFILES = new HashSet<Profile>();

        static {
            WILDFLY_WEB_PROFILES.add(Profile.JAVA_EE_6_WEB);
            WILDFLY_WEB_PROFILES.add(Profile.JAVA_EE_7_WEB);
            WILDFLY_WEB_PROFILES.add(Profile.JAVA_EE_8_WEB);
            WILDFLY_WEB_PROFILES.add(Profile.JAKARTA_EE_8_WEB);
            WILDFLY_WEB_PROFILES.add(Profile.JAKARTA_EE_9_WEB);
            WILDFLY_WEB_PROFILES.add(Profile.JAKARTA_EE_9_1_WEB);
            WILDFLY_WEB_PROFILES.add(Profile.JAKARTA_EE_10_WEB);
        }

        private static final Set<Profile> JAKARTAEE_WEB_PROFILES = new HashSet<Profile>();

        static {
            JAKARTAEE_WEB_PROFILES.add(Profile.JAKARTA_EE_9_WEB);
            JAKARTAEE_WEB_PROFILES.add(Profile.JAKARTA_EE_9_1_WEB);
            JAKARTAEE_WEB_PROFILES.add(Profile.JAKARTA_EE_10_WEB);
        }
        private LibraryImplementation[] libraries;

        private final WildFlyProperties properties;

        public J2eePlatformImplImpl(WildFlyProperties properties) {
            this.properties = properties;
        }

        @Override
        public Set<org.netbeans.api.j2ee.core.Profile> getSupportedProfiles() {
            if (this.properties.isWildfly()) {
                if (this.properties.isServletOnly()) {
                    if (this.properties.getServerVersion().compareToIgnoreUpdate(WildflyPluginUtils.WILDFLY_27_0_0) >= 0) {
                        return Collections.unmodifiableSet(JAKARTAEE_WEB_PROFILES);
                    }
                    return Collections.unmodifiableSet(WILDFLY_WEB_PROFILES);
                }
                if (this.properties.getServerVersion().compareToIgnoreUpdate(WildflyPluginUtils.WILDFLY_27_0_0) >= 0) {
                    Set<org.netbeans.api.j2ee.core.Profile> allJakarta = new HashSet<>(
                            (int) Math.ceil((JAKARTAEE_FULL_PROFILES.size()+JAKARTAEE_WEB_PROFILES.size()) / 0.75));
                    allJakarta.addAll(JAKARTAEE_FULL_PROFILES);
                    allJakarta.addAll(JAKARTAEE_WEB_PROFILES);
                    return Collections.unmodifiableSet(allJakarta);
                }
                return Collections.unmodifiableSet(WILDFLY_PROFILES);
            }
            if (this.properties.getServerVersion().compareToIgnoreUpdate(WildflyPluginUtils.EAP_7_0) >= 0) {
                return Collections.unmodifiableSet(WILDFLY_PROFILES);
            }
            return Collections.unmodifiableSet(EAP6_PROFILES);
        }

        @Override
        public Set<org.netbeans.api.j2ee.core.Profile> getSupportedProfiles(Type moduleType) {
            return getSupportedProfiles();
        }

        @Override
        public Set<Type> getSupportedTypes() {
            return Collections.unmodifiableSet(MODULE_TYPES);
        }

        @Override
        public Set/*<String>*/ getSupportedJavaPlatformVersions() {
            Set versions = new HashSet();
            versions.add("1.7"); // NOI18N
            versions.add("1.8"); // NOI18N
            versions.add("1.8"); // NOI18N
            versions.add("1.9"); // NOI18N
            versions.add("11"); // NOI18N
            if (this.properties.getServerVersion().compareToIgnoreUpdate(WildflyPluginUtils.EAP_7_0) >= 0) {
                versions.add("17"); // NOI18N
            }
            return versions;
        }

        @Override
        public JavaPlatform getJavaPlatform() {
            return properties.getJavaPlatform();
        }

        @Override
        public File[] getPlatformRoots() {
            return new File[]{getServerHome(), getDomainHome()};
        }

        @Override
        public File getServerHome() {
            return properties.getRootDir();
        }

        @Override
        public File getDomainHome() {
            return properties.getServerDir();
        }

        @Override
        public File getMiddlewareHome() {
            return null;
        }

        private static class FF implements FilenameFilter {

            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(".jar") || new File(dir, name).isDirectory(); //NOI18N
            }
        }

        @Override
        public LibraryImplementation[] getLibraries() {
            if (libraries == null) {
                initLibraries();
            }
            return libraries.clone();
        }

        public void notifyLibrariesChanged() {
            initLibraries();
            firePropertyChange(PROP_LIBRARIES, null, libraries.clone());
        }

        @Override
        public java.awt.Image getIcon() {
            return null;
        }

        @Override
        public String getDisplayName() {
            return NbBundle.getMessage(WildflyJ2eePlatformFactory.class, "TITLE_JBOSS_FACTORY");

        }

        @Override
        @Deprecated
        public boolean isToolSupported(String toolName) {

            if (J2eePlatform.TOOL_JSR109.equals(toolName)) {
                if (containsJaxWsLibraries()) {
                    return true;
                }
            }

            if (J2eePlatform.TOOL_WSIMPORT.equals(toolName)) {
                if (containsJaxWsLibraries()) {
                    return true;
                }
            }

            if (J2eePlatform.TOOL_WSGEN.equals(toolName)) {
                if (containsJaxWsLibraries()) {
                    return true;
                }
            }

            if ("JaxWs-in-j2ee14-supported".equals(toolName)) { //NOI18N
                if (containsJaxWsLibraries()) {
                    return true;
                }
            }

            if (!containsJaxWsLibraries()
                    && (J2eePlatform.TOOL_WSCOMPILE.equals(toolName) || J2eePlatform.TOOL_APP_CLIENT_RUNTIME.equals(toolName))) {
                return true;
            }

            if (HIBERNATE_JPA_PROVIDER.equals(toolName)
                    || TOPLINK_JPA_PROVIDER.equals(toolName)
                    || KODO_JPA_PROVIDER.equals(toolName)) {
                return containsPersistenceProvider(toolName);
            }

            if ("jpaversionverification".equals(toolName)) { // NOI18N
                return true;
            }
            if ("jpa1.0".equals(toolName)) { // NOI18N
                return true;
            }
            if ("jpa2.0".equals(toolName)) { // NOI18N
                return true;
            }
            if ("jpa2.1".equals(toolName)) { // NOI18N
                return this.properties.isWildfly();
            }
            if ("jpa3.0".equals(toolName)) { // NOI18N
                return this.properties.isWildfly();
            }
            if ("jpa3.1".equals(toolName)) { // NOI18N
                return this.properties.isWildfly();
            }

            if ("hibernatePersistenceProviderIsDefault2.0".equals(toolName)) {
                return true;
            }
            if ("defaultPersistenceProviderJavaEE5".equals(toolName)) {
                return true;
            }

            return false;
        }

        String getDefaultJpaProvider() {
            return HIBERNATE_JPA_PROVIDER;
        }

        private boolean containsJaxWsLibraries() {
            File[] jaxWsAPILib = new File(properties.getModulePath("org/jboss/ws/api/main")).listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.startsWith("jbossws-api") && name.endsWith("jar");
                }
            }); // NOI18N
            if (jaxWsAPILib != null && jaxWsAPILib.length == 1 && jaxWsAPILib[0].exists()) {
                return true;
            }
            jaxWsAPILib = new File(properties.getModulePath("javax/xml/ws/api/main")).listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File dir, String name) {
                    return name.startsWith("jboss-jaxws-api") && name.endsWith("jar");
                }
            }); // NOI18N
            if (jaxWsAPILib != null && jaxWsAPILib.length == 1 && jaxWsAPILib[0].exists()) {
                return true;
            }
            return false;
        }

        boolean containsPersistenceProvider(String providerName) {
            return containsService(libraries, "javax.persistence.spi.PersistenceProvider", providerName);
        }

        private static boolean containsService(LibraryImplementation[] libraries, String serviceName, String serviceImplName) {
            for (LibraryImplementation libImpl : libraries) {
                if (containsService(libImpl, serviceName, serviceImplName)) { //NOI18N
                    return true;
                }
            }
            return false;
        }

        private static boolean containsService(LibraryImplementation library, String serviceName, String serviceImplName) {
            List roots = library.getContent(J2eeLibraryTypeProvider.VOLUME_TYPE_CLASSPATH);
            for (Iterator it = roots.iterator(); it.hasNext();) {
                URL rootUrl = (URL) it.next();
                FileObject root = URLMapper.findFileObject(rootUrl);
                if (root != null && "jar".equals(rootUrl.getProtocol())) {  //NOI18N
                    FileObject archiveRoot = FileUtil.getArchiveRoot(FileUtil.getArchiveFile(root));
                    String serviceRelativePath = "META-INF/services/" + serviceName; //NOI18N
                    FileObject serviceFO = archiveRoot.getFileObject(serviceRelativePath);
                    if (serviceFO != null && containsService(serviceFO, serviceName, serviceImplName)) {
                        return true;
                    }
                }
            }
            return false;
        }

        private static boolean containsService(FileObject serviceFO, String serviceName, String serviceImplName) {
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(serviceFO.getInputStream()));
                try {
                    String line;
                    while ((line = br.readLine()) != null) {
                        int ci = line.indexOf('#');
                        if (ci >= 0) {
                            line = line.substring(0, ci);
                        }
                        if (line.trim().equals(serviceImplName)) {
                            return true;
                        }
                    }
                } finally {
                    br.close();
                }
            } catch (Exception ex) {
                Exceptions.attachLocalizedMessage(ex, serviceFO.toURL().toString());
                Logger.getLogger("global").log(Level.INFO, null, ex);
            }
            return false;
        }

        @Override
        public File[] getToolClasspathEntries(String toolName) {
            if (J2eePlatform.TOOL_WSIMPORT.equals(toolName)) {
                return getJaxWsLibraries();
            }
            if (J2eePlatform.TOOL_WSGEN.equals(toolName)) {
                return getJaxWsLibraries();
            }
            if (J2eePlatform.TOOL_WSCOMPILE.equals(toolName)) {
                File root = InstalledFileLocator.getDefault().locate("modules/ext/jaxrpc16", null, false); // NOI18N
                return new File[]{
                    new File(root, "saaj-api.jar"), // NOI18N
                    new File(root, "saaj-impl.jar"), // NOI18N
                    new File(root, "jaxrpc-api.jar"), // NOI18N
                    new File(root, "jaxrpc-impl.jar"), // NOI18N
                };
            }
            if (J2eePlatform.TOOL_APP_CLIENT_RUNTIME.equals(toolName)) {
                return new File(properties.getRootDir(), "client").listFiles(new FF()); // NOI18N
            }
            return null;
        }

        private File[] getJaxWsLibraries() {
            File root = new File(properties.getRootDir(), "client"); // NOI18N
            File jaxWsAPILib = new File(root, "jboss-jaxws.jar"); // NOI18N
            // JBoss without jbossws
            if (jaxWsAPILib.exists()) {
                return new File[]{
                    new File(root, "wstx.jar"), // NOI18N
                    new File(root, "jaxws-tools.jar"), // NOI18N
                    new File(root, "jboss-common-client.jar"), // NOI18N
                    new File(root, "jboss-logging-spi.jar"), // NOI18N
                    new File(root, "stax-api.jar"), // NOI18N

                    new File(root, "jbossws-client.jar"), // NOI18N
                    new File(root, "jboss-jaxws-ext.jar"), // NOI18N
                    new File(root, "jboss-jaxws.jar"), // NOI18N
                    new File(root, "jboss-saaj.jar") // NOI18N
                };
            }
            jaxWsAPILib = new File(root, "jbossws-native-jaxws.jar"); // NOI18N
            // JBoss+jbossws-native
            if (jaxWsAPILib.exists()) {
                return new File[]{
                    new File(root, "wstx.jar"), // NOI18N
                    new File(root, "jaxws-tools.jar"), // NOI18N
                    new File(root, "jboss-common-client.jar"), // NOI18N
                    new File(root, "jboss-logging-spi.jar"), // NOI18N
                    new File(root, "stax-api.jar"), // NOI18N

                    new File(root, "jbossws-native-client.jar"), // NOI18N
                    new File(root, "jbossws-native-jaxws-ext.jar"), // NOI18N
                    new File(root, "jbossws-native-jaxws.jar"), // NOI18N
                    new File(root, "jbossws-native-saaj.jar") // NOI18N
                };
            }
            jaxWsAPILib = new File(root, "jaxws-api.jar"); // NOI18N
            // JBoss+jbossws-metro
            if (jaxWsAPILib.exists()) {
                return new File[]{
                    new File(root, "wstx.jar"), // NOI18N
                    new File(root, "jaxws-tools.jar"), // NOI18N
                    new File(root, "jboss-common-client.jar"), // NOI18N
                    new File(root, "jboss-logging-spi.jar"), // NOI18N
                    new File(root, "stax-api.jar"), // NOI18N

                    new File(root, "jbossws-metro-client.jar"), // NOI18N
                    new File(root, "saaj-api.jar") // NOI18N
                };
            }
            return null;
        }

        @Override
        @Deprecated
        public String getToolProperty(String toolName, String propertyName) {
            if (J2eePlatform.TOOL_APP_CLIENT_RUNTIME.equals(toolName)) {
                if (J2eePlatform.TOOL_PROP_MAIN_CLASS.equals(propertyName)) {
                    return ""; // NOI18N
                }
                if (J2eePlatform.TOOL_PROP_MAIN_CLASS_ARGS.equals(propertyName)) {
                    return ""; // NOI18N
                }
                if ("j2ee.clientName".equals(propertyName)) { // NOI18N
                    return "${jar.name}"; // NOI18N
                }
                if (J2eePlatform.TOOL_PROP_JVM_OPTS.equals(propertyName)) {
                    return " -Djava.naming.factory.url.pkgs=org.jboss.ejb.client.naming"; // NOI18N
                }
            }
            return null;
        }

        // private helper methods -------------------------------------------------
        private void initLibraries() {
            // create library
            LibraryImplementation lib = new J2eeLibraryTypeProvider().createLibrary();
            lib.setName(NbBundle.getMessage(WildflyJ2eePlatformFactory.class, "TITLE_JBOSS_LIBRARY"));
            lib.setContent(J2eeLibraryTypeProvider.VOLUME_TYPE_CLASSPATH, properties.getClasses());
            lib.setContent(J2eeLibraryTypeProvider.VOLUME_TYPE_JAVADOC, properties.getJavadocs());
            lib.setContent(J2eeLibraryTypeProvider.VOLUME_TYPE_SRC, properties.getSources());
            libraries = new LibraryImplementation[]{lib};
        }

        @Override
        public Lookup getLookup() {
            WSStack<JaxWs> wsStack = WSStackFactory.createWSStack(JaxWs.class,
                    new JBossJaxWsStack(properties.getRootDir()), WSStack.Source.SERVER);
            Lookup baseLookup = Lookups.fixed(properties.getRootDir(),
                    wsStack, new JpaSupportImpl(this), new JaxRsStackSupportImpl(this));
            return LookupProviderSupport.createCompositeLookup(baseLookup,
                    "J2EE/DeploymentPlugins/Wildlfy/Lookup"); //NOI18N
        }

        private class JaxRsStackSupportImpl implements JaxRsStackSupportImplementation {

            private static final String JAX_RS_APPLICATION_CLASS = "javax.ws.rs.core.Application"; //NOI18N
            private J2eePlatformImplImpl j2eePlatform;

            JaxRsStackSupportImpl(J2eePlatformImplImpl j2eePlatform) {
                this.j2eePlatform = j2eePlatform;
            }

            @Override
            public boolean addJsr311Api(Project project) {
                return isBundled(JAX_RS_APPLICATION_CLASS);
            }

            @Override
            public boolean extendsJerseyProjectClasspath(Project project) {
                return isBundled(JAX_RS_APPLICATION_CLASS);
            }

            @Override
            public void removeJaxRsLibraries(Project project) {
            }

            @Override
            public void configureCustomJersey(Project project) {
            }

            @Override
            public boolean isBundled(String classFqn) {
                j2eePlatform.getLibraries();
                for (LibraryImplementation lib : j2eePlatform.getLibraries()) {
                    List<URL> urls = lib.getContent(J2eeLibraryTypeProvider.VOLUME_TYPE_CLASSPATH);
                    for (URL url : urls) {
                        FileObject root = URLMapper.findFileObject(url);
                        if (FileUtil.isArchiveFile(root)) {
                            root = FileUtil.getArchiveRoot(root);
                        }
                        String path = classFqn.replace('.', '/') + ".class"; //NOI18N
                        if (root.getFileObject(path) != null) {
                            return true;
                        }
                    }
                }

                return false;
            }

        }

    }
}
