package org.eu.ingwar.tools.arquillian.extension.deployment;

/*
 * #%L
 * Arquillian suite extension
 * %%
 * Copyright (C) 2013 Ingwar & co.
 * %%
 * Licensed 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.
 * #L%
 */

import org.jboss.shrinkwrap.api.Archive;
import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.jboss.shrinkwrap.api.importer.ArchiveImportException;
import org.jboss.shrinkwrap.api.importer.ExplodedImporter;
import org.jboss.shrinkwrap.api.spec.EnterpriseArchive;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.jboss.shrinkwrap.api.spec.WebArchive;
import org.jboss.shrinkwrap.resolver.api.InvalidConfigurationFileException;
import org.jboss.shrinkwrap.resolver.api.maven.*;
import org.jboss.shrinkwrap.resolver.api.maven.coordinate.MavenCoordinate;
import org.jboss.shrinkwrap.resolver.api.maven.strategy.AcceptScopesStrategy;

import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Karol Lassak 'Ingwar'
 */
public class EarGenericBuilder {

    private static final String RUN_AT_ARQUILLIAN_PATH = "/runs-at-arquillian.txt";
    private static final String RUN_AT_ARQUILLIAN_CONTENT = "at-arquillian";
    private static final Logger LOG = Logger.getLogger(EarGenericBuilder.class.getName());

    /**
     * Private constructor.
     */
    private EarGenericBuilder() {
    }

    /**
     * Generates deployment for given application.
     *
     * @param type Module type to generate
     * @return EnterpriseArchive containing given module and all dependencies
     */
    public static EnterpriseArchive getModuleDeployment(ModuleType type) {
        return getModuleDeployment(type, type.generateModuleName());
    }

    /**
     * Generates deployment for given application.
     *
     * @param type Module type to generate
     * @param basename Base name of module to generate
     * @return EnterpriseArchive containing given module and all dependencies
     */
    public static EnterpriseArchive getModuleDeployment(ModuleType type, String basename) {
        return getModuleDeployment(type, basename, true);
    }

    /**
     * Generates deployment for given application.
     *
     * @param type Module type to generate
     * @param basename Base name of module to generate
     * @param doFiltering should do basic filtering
     * @return EnterpriseArchive containing given module and all dependencies
     */
    public static EnterpriseArchive getModuleDeployment(ModuleType type, String basename, boolean doFiltering) {
        String name = basename + "." + type.getExtension();
        String testJarName = basename + "-tests.jar";
//        LOG.debug("Creating Arquillian deployment for [" + name + "]");
        try {
            EarDescriptorBuilder descriptorBuilder = new EarDescriptorBuilder(basename);
            MavenResolverSystem maven = Maven.resolver();
            //ConfigurableMavenResolverSystem maven = Maven.configureResolver().workOffline().withMavenCentralRepo(false);
            EnterpriseArchive ear = ShrinkWrap.create(EnterpriseArchive.class, basename + "-full.ear");
            PomEquippedResolveStage resolveStage = maven.loadPomFromFile("pom.xml");

            // przejrzenie dependency oznaczonych jako provided w celu znalezienia EJB'ków
            MavenResolvedArtifact[] provided = resolveStage.importRuntimeDependencies().importDependencies(ScopeType.PROVIDED).resolve().using(new AcceptScopesStrategy(ScopeType.PROVIDED)).asResolvedArtifact();
            for (MavenResolvedArtifact mra : provided) {
//                System.out.println("Checking provided: " + mra.getCoordinate().toCanonicalForm());
                if (isArtifactEjb(mra.getCoordinate())) {
                    ear.addAsModule(mra.as(JavaArchive.class));
                    // dodajemy jako moduł
                    descriptorBuilder.addEjb(mra.asFile().getName());
                    // przeglądamy dependency EJB'ka w celu pobrania także zależności z EJB'ka
                    for (MavenArtifactInfo mai : mra.getDependencies()) {
//                            LOG.debug("Resolved: " + mai.getCoordinate().getGroupId() + ":" + mai.getCoordinate().getArtifactId());
                        // pomijamy wzajemne zależności do innych EJB'ków
                        if (!isArtifactEjb(mai.getCoordinate())) {
                            for (MavenResolvedArtifact reqMra : provided) {
                                if (reqMra.getCoordinate().toCanonicalForm().equals(mai.getCoordinate().toCanonicalForm())) {
                                    // dodanie zależności do lib'ów
                                    ear.addAsLibrary(reqMra.asFile());
                                    break;
                                }
                            }
                        }
                    }
                }
            }
            MavenResolvedArtifact[] deps = resolveStage.importRuntimeAndTestDependencies().resolve().withTransitivity().asResolvedArtifact();

            for (MavenResolvedArtifact mra : deps) {
                MavenCoordinate mc = mra.getCoordinate();
                PackagingType packaging = mc.getPackaging();
                if (doFiltering && isFiltered(mc)) {
                    continue;
                }
                LOG.log(Level.FINEST, "Adding: {0}", mc.toCanonicalForm());
                if (isArtifactEjb(mc)) {
                    // dependency w postaci ejb'ków
                    ear.addAsModule(mra.as(JavaArchive.class));
                    descriptorBuilder.addEjb(mra.asFile().getName());
                } else if (packaging.equals(PackagingType.WAR)) {
                    // dependency w postaci war'ów
                    ear.addAsModule(mra.as(WebArchive.class));
                    descriptorBuilder.addWeb(mra.asFile().getName());
                } else {
                    // resztę dodajemy jako lib
                    ear.addAsLibrary(mra.asFile());
                }
            }
            // utworzenie głównego archiwum
//            Archive<?> module = ShrinkWrap.create(MavenImporter.class, name)
//                    .loadPomFromFile("pom.xml")
//                    .as(type.getType());

            Archive<?> module = ShrinkWrap.create(ExplodedImporter.class, name)
                    .importDirectory(type.getExplodedDir(basename))
                    .as(type.getType());

            JavaArchive testJar = ShrinkWrap.create(ExplodedImporter.class, testJarName)
                    .importDirectory("target/test-classes")
                    .as(JavaArchive.class);

            module = module.merge(testJar, type.getMergePoint());
//            mergeReplace(ear, module, testJar);

            module.add(new StringAsset(RUN_AT_ARQUILLIAN_CONTENT), RUN_AT_ARQUILLIAN_PATH);
            LOG.log(Level.FINE, module.toString(true));

            addMainModule(ear, type, module, descriptorBuilder);
            
            // Workaround for arquillian bug
            if (!descriptorBuilder.containsWar()) {
                String testModuleName = ModuleType.WAR.generateModuleName() + ".war";
                ear.addAsModule(ShrinkWrap.create(WebArchive.class, testModuleName));
                descriptorBuilder.addWeb(testModuleName);
            }

            ear.setApplicationXML(new StringAsset(descriptorBuilder.render()));
            ear.addManifest();
            LOG.log(Level.INFO, "Created deployment [{0}]", ear.getName());
//            System.out.println(ear.toString(true));
//            System.out.println(descriptorBuilder.render());
            return ear;
        } catch (IllegalArgumentException ex) {
            throw new IllegalStateException("Error in creating deployment [" + ex + "]", ex);
        } catch (InvalidConfigurationFileException ex) {
            throw new IllegalStateException("Error in creating deployment [" + ex + "]", ex);
        } catch (ArchiveImportException ex) {
            throw new IllegalStateException("Error in creating deployment [" + ex + "]", ex);
        }
    }

    /**
     * Sprawdza czy dany artefakt jest ejb (ale nie jest ejb-clientem). Artefakt w formie MavenCoordinate.
     *
     * FIXME: Z powodu bledu shrinkwrapa szukamy tak na lewo po nazwie.
     *
     * @param artifactCoordinate MavenCoordinate artefaktu do sprawdzenia
     * @return true jesli jest projektem ejb
     */
    private static boolean isArtifactEjb(MavenCoordinate artifactCoordinate) {
        if ("client".equals(artifactCoordinate.getClassifier())) {
            return false;
        }
        if (!artifactCoordinate.getGroupId().startsWith("pl.gov.coi")) {
            return false;
        }
        if (!artifactCoordinate.getArtifactId().toLowerCase().contains("ejb")) {
            return false;
        }
        return true;
    }

    /**
     * Dodaje główny moduł do archiwum EAR.
     *
     * @param ear archiwum EAR
     * @param type typ głównego modułu
     * @param module główny moduł
     * @param descriptorBuilder deskreptor builder dla EAR'a
     */
    private static void addMainModule(EnterpriseArchive ear, ModuleType type, Archive<?> module, EarDescriptorBuilder descriptorBuilder) {
        if (type.isModule()) {
            ear.addAsModule(module);
            if (type == ModuleType.EJB) {
                descriptorBuilder.addEjb(module.getName());
            }
            if (type == ModuleType.WAR) {
                descriptorBuilder.addWeb(module.getName());
            }
        } else {
            ear.addAsLibrary(module);
        }
    }

//    /**
//     * Merges additional (usualy test) archive into main module.
//     *
//     * @param module main module
//     * @param additional module
//     */
//    private static void mergeReplace(EnterpriseArchive ear, Archive<?> module, Archive<?> additional) {
//        for (Map.Entry<ArchivePath, Node> entry : additional.getContent().entrySet()) {
//            ArchivePath ap = new BasicPath(entry.getKey().get());
//            if (module.contains(ap) && module.get(ap).getAsset() != null) {
//                module.delete(ap);
//            }
//            Asset asset = entry.getValue().getAsset();
//            if (asset == null) {
//                module.addAsDirectory(ap);
//            } else if ("/META-INF/jboss-deployment-structure.xml".equals(ap.get())) {
//                ear.add(asset, ap);
//            } else {
//                module.add(asset, ap);
//            }
//        }
//    }

    /**
     * Check if artefact should be filtered (omitted from packaging).
     * <p>
     * By default all artefact witch groups start with org.jboss.(shrinkwrap|arqrquillian) are filtered.
     * </p>
     * @param artifactCoordinate Artifact coordinate to check
     * @return true if artifact should be filtered
     */
    private static boolean isFiltered(MavenCoordinate artifactCoordinate) {
        if (artifactCoordinate.getGroupId().startsWith("org.jboss.shrinkwrap")) {
            return true;
        }
        if (artifactCoordinate.getGroupId().startsWith("org.jboss.arquillian")) {
            return true;
        }
        if (artifactCoordinate.getGroupId().startsWith("org.jboss.as")) {
            return true;
        }
        return false;
    }
}