/*
 * JBoss, Home of Professional Open Source
 * Copyright 2012 Red Hat Inc. and/or its affiliates and other contributors
 * as indicated by the @author tags. All rights reserved.
 * See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This copyrighted material is made available to anyone wishing to use,
 * modify, copy, or redistribute it subject to the terms and conditions
 * of the GNU Lesser General Public License, v. 2.1.
 * This program is distributed in the hope that it will be useful, but WITHOUT A
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
 * You should have received a copy of the GNU Lesser General Public License,
 * v.2.1 along with this distribution; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */

package org.jboss.as.jsf.deployment;

import java.io.File;
import java.io.FileFilter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.as.jsf.logging.JSFLogger;
import org.jboss.as.jsf.subsystem.JSFResourceDefinition;
import org.jboss.modules.ModuleIdentifier;

/**
 * This class finds all the installed Jakarta Server Faces implementations and provides their ModuleId's.
 *
 * @author Stan Silvert ssilvert@redhat.com (C) 2012 Red Hat Inc.
 */
public class JSFModuleIdFactory {
    private static final String API_MODULE = "jakarta.faces.api";
    private static final String IMPL_MODULE = "jakarta.faces.impl";
    private static final String INJECTION_MODULE = "org.jboss.as.jsf-injection";

    private static final JSFModuleIdFactory instance = new JSFModuleIdFactory();

    // The default JSF impl slot.  This can be overridden by the management layer.
    private String defaultSlot = JSFResourceDefinition.DEFAULT_SLOT;

    private Map<String, ModuleIdentifier> apiIds = new HashMap<>();
    private Map<String, ModuleIdentifier> implIds = new HashMap<>();
    private Map<String, ModuleIdentifier> injectionIds = new HashMap<>();

    private Set<String> allVersions = new HashSet<>();
    private List<String> activeVersions = new ArrayList<>();

    public static JSFModuleIdFactory getInstance() {
        return instance;
    }

    private JSFModuleIdFactory() {
        String modulePath = System.getProperty("module.path", System.getenv("JAVA_MODULEPATH"));
        if (!isBogusPath(modulePath)) {
            loadIdsFromModulePath(modulePath);
        }

        if (!activeVersions.contains("main")) {
            loadIdsManually();
        }

        JSFLogger.ROOT_LOGGER.activatedJSFImplementations(activeVersions);
    }

    void setDefaultSlot(String defaultSlot) {
        this.defaultSlot = defaultSlot;
    }

    String getDefaultSlot() {
        return this.defaultSlot;
    }

    private boolean isBogusPath(String path) {
        if (path == null) return true;

        // must have at least one existing directory in the path
        for (String dir : path.split(File.pathSeparator)) {
            if (new File(dir).exists()) return false;
        }

        return true; // no directory in the path exists
    }

    // just provide the default implementations
    private void loadIdsManually() {
        implIds.put("main", ModuleIdentifier.create(IMPL_MODULE));
        apiIds.put("main", ModuleIdentifier.create(API_MODULE));
        injectionIds.put("main", ModuleIdentifier.create(INJECTION_MODULE));

        allVersions.add("main");

        activeVersions.add("main");
    }

    private void loadIdsFromModulePath(String modulePath) {
        for (String moduleRootDir : modulePath.split(File.pathSeparator)) {
            loadIds(moduleRootDir, apiIds, API_MODULE);
            loadIds(moduleRootDir, implIds, IMPL_MODULE);
            loadIds(moduleRootDir, injectionIds, INJECTION_MODULE);
        }
        checkVersionIntegrity();
    }

    private void loadIds(String moduleRootDir, Map<String, ModuleIdentifier> idMap, String moduleName) {
        StringBuilder baseDirBuilder = new StringBuilder(moduleRootDir);
        baseDirBuilder.append(File.separator);
        baseDirBuilder.append(moduleName.replace(".", File.separator));

        File moduleBaseDir = new File(baseDirBuilder.toString());
        File[] slots = moduleBaseDir.listFiles(new FileFilter() {
            @Override
            public boolean accept(File pathname) {
                return pathname.isDirectory();
            }
        });

        if (slots == null) return;

        for (File slot : slots) {
            if (!new File(slot, "module.xml").exists()) continue; // make sure directory represents a real module
            String slotName = slot.getName();
            allVersions.add(slotName);
            idMap.put(slotName, ModuleIdentifier.create(moduleName, slotName));
        }
    }

    // make sure that each version has api, impl, and injection
    private void checkVersionIntegrity() {
        activeVersions.addAll(allVersions);

        for (String version : allVersions) {
            if (!apiIds.containsKey(version)) {
                JSFLogger.ROOT_LOGGER.missingJSFModule(version, API_MODULE);
                activeVersions.remove(version);
            }

            if (!implIds.containsKey(version)) {
                JSFLogger.ROOT_LOGGER.missingJSFModule(version, IMPL_MODULE);
                activeVersions.remove(version);
            }

            if (!injectionIds.containsKey(version)) {
                JSFLogger.ROOT_LOGGER.missingJSFModule(version, INJECTION_MODULE);
                activeVersions.remove(version);
            }
        }
    }

    /**
     * If needed, convert old JSFVersionMarker values to slot values.
     *
     * @param jsfVersion The version value from JSFVersionMarker, or null for default slot.
     * @return The equivalent slot value.
     */
    String computeSlot(String jsfVersion) {
        if (jsfVersion == null) return defaultSlot;
        if (JsfVersionMarker.JSF_4_0.equals(jsfVersion)) return defaultSlot;
        return jsfVersion;
    }

    ModuleIdentifier getApiModId(String jsfVersion) {
        return this.apiIds.get(computeSlot(jsfVersion));
    }

    ModuleIdentifier getImplModId(String jsfVersion) {
        return this.implIds.get(computeSlot(jsfVersion));
    }

    ModuleIdentifier getInjectionModId(String jsfVersion) {
        return this.injectionIds.get(computeSlot(jsfVersion));
    }

    boolean isValidJSFSlot(String slot) {
        String computedSlot = computeSlot(slot);
        return apiIds.containsKey(computedSlot) && implIds.containsKey(computedSlot) && injectionIds.containsKey(computedSlot);
    }

    /**
     * Return the slot id's of all active JSF versions.
     *
     * @return The slot id's of all active JSF versions.
     */
    public List<String> getActiveJSFVersions() {
        return Collections.unmodifiableList(activeVersions);
    }

}
