/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 2010 Oracle and/or its affiliates. All rights reserved.
 *
 * Oracle and Java are registered trademarks of Oracle and/or its affiliates.
 * Other names may be trademarks of their respective owners.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common
 * Development and Distribution License("CDDL") (collectively, the
 * "License"). You may not use this file except in compliance with the
 * License. You can obtain a copy of the License at
 * http://www.netbeans.org/cddl-gplv2.html
 * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
 * specific language governing permissions and limitations under the
 * License.  When distributing the software, include this License Header
 * Notice in each file and include the License file at
 * nbbuild/licenses/CDDL-GPL-2-CP.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the GPL Version 2 section of the License file that
 * accompanied this code. If applicable, add the following below the
 * License Header, with the fields enclosed by brackets [] replaced by
 * your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 *
 * If you wish your version of this file to be governed by only the CDDL
 * or only the GPL Version 2, indicate your decision by adding
 * "[Contributor] elects to include this software in this distribution
 * under the [CDDL or GPL Version 2] license." If you do not indicate a
 * single choice of license, a recipient has the option to distribute
 * your version of this file under either the CDDL, the GPL Version 2 or
 * to extend the choice of license to its licensees as provided above.
 * However, if you add GPL Version 2 code and therefore, elected the GPL
 * Version 2 license, then the option applies only if the new code is
 * made subject to such option by the copyright holder.
 *
 * Contributor(s):
 *
 * Portions Copyrighted 2009 Sun Microsystems, Inc.
 */

package org.netbeans.modules.maven.newproject;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.maven.artifact.repository.ArtifactRepository;
import org.apache.maven.artifact.repository.ArtifactRepositoryFactory;
import org.apache.maven.artifact.repository.ArtifactRepositoryPolicy;
import org.apache.maven.artifact.repository.layout.ArtifactRepositoryLayout;
import org.apache.maven.repository.MirrorSelector;
import org.apache.maven.repository.RepositorySystem;
import org.apache.maven.settings.Mirror;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Server;
import org.apache.maven.settings.crypto.DefaultSettingsDecryptionRequest;
import org.apache.maven.settings.crypto.SettingsDecrypter;
import org.apache.maven.settings.crypto.SettingsDecryptionResult;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.WagonException;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authentication.AuthenticationInfo;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;
import org.codehaus.plexus.component.repository.exception.ComponentLookupException;
import org.codehaus.plexus.util.FileUtils;
import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.SAXBuilder;
import org.netbeans.modules.maven.api.archetype.Archetype;
import org.netbeans.modules.maven.api.archetype.ArchetypeProvider;
import org.netbeans.modules.maven.embedder.EmbedderFactory;
import org.netbeans.modules.maven.embedder.MavenEmbedder;
import org.netbeans.modules.maven.indexer.api.RepositoryInfo;
import org.netbeans.modules.maven.indexer.api.RepositoryPreferences;
import org.openide.modules.Places;
import org.openide.util.NbCollections;
import org.openide.util.Utilities;
import org.openide.util.lookup.ServiceProvider;

/**
 * list of archetypes coming from an archetype catalog
 * @author mkleint
 */
public abstract class CatalogRepoProvider implements ArchetypeProvider {

    private static final String EL_ARCHETYPES = "archetypes"; //NOI18N
    private static final String EL_ARCHETYPE = "archetype"; //NOI18N
    private static final String EL_ARTIFACTID = "artifactId"; //NOI18N
    private static final String EL_DESCRIPTION = "description";
    private static final String EL_GROUPID = "groupId"; //NOI18N
    private static final String EL_REPOSITORY = "repository"; //NOI18N
    private static final String EL_VERSION = "version"; //NOI18N

    private static final Logger LOG = Logger.getLogger(CatalogRepoProvider.class.getName());

    protected CatalogRepoProvider() {}

    protected abstract URL file() throws IOException;

    protected abstract String repository();

    public @Override List<Archetype> getArchetypes() {
        try {
            return getArchetypes(file(), repository());
        } catch (IOException ex) {
            LOG.log(Level.INFO, null, ex);
            return Collections.emptyList();
        }
    }
    
    static List<Archetype> getArchetypes(URL file, String repository) {
        List<Archetype> toRet = new ArrayList<Archetype>();
        try {
            if (file == null) {
                return toRet;
            }
            SAXBuilder builder = new SAXBuilder();
            Document doc = builder.build(file);
            Element root = doc.getRootElement();
            Namespace ns = root.getNamespace(); // should be http://maven.apache.org/plugins/maven-archetype-plugin/archetype-catalog/1.0.0 but missing in older copies
            Element list = root.getChild(EL_ARCHETYPES, ns);
            if (list != null) {
                for (Element el : NbCollections.checkedListByCopy(list.getChildren(EL_ARCHETYPE, ns), Element.class, true)) {
                    String grId = el.getChildText(EL_GROUPID, ns);
                    String artId = el.getChildText(EL_ARTIFACTID, ns);
                    String ver = el.getChildText(EL_VERSION, ns);
                    String repo = el.getChildText(EL_REPOSITORY, ns);
                    String desc = el.getChildText(EL_DESCRIPTION, ns);
                    Archetype archetype = new Archetype(false);
                    if (grId != null && artId != null && ver != null) {
                        archetype.setArtifactId(artId);
                        archetype.setGroupId(grId);
                        archetype.setVersion(ver);
                        if (repo != null) {
                            archetype.setRepository(repo);
                        } else {
                            archetype.setRepository(repository);
                        }
                        if (desc != null) {
                            archetype.setDescription(desc);
                        }
                        toRet.add(archetype);
                    }
                }
            }
        } catch (IOException exc) {
            LOG.log(Level.INFO, null, exc);
        } catch (JDOMException exc) {
            LOG.log(Level.INFO, null, exc);
        }
        return toRet;
    }

    @ServiceProvider(service=ArchetypeProvider.class, position=100)
    public static class LocalCatalogRepoProvider extends CatalogRepoProvider {

        @Override protected URL file() throws IOException {
            File f = new File(RepositorySystem.userMavenConfigurationHome, "archetype-catalog.xml"); //NOI18N
            return f.isFile() ? Utilities.toURI(f).toURL() : null;
        }

        @Override protected String repository() {
            return RepositorySystem.DEFAULT_LOCAL_REPO_ID;
        }

    }

    // one day period for updating downloaded/cached archetype catalogs
    private static final long ARCHETYPE_TIMEOUT = 1000 * 60 * 60 * 24; // * 7;
    
    @ServiceProvider(service=ArchetypeProvider.class, position=50) //has to come before the localCatalogProvider one..
    public static class RemoteCatalogProvider implements ArchetypeProvider {

        @Override
        public List<Archetype> getArchetypes() {
            File root = Places.getCacheSubdirectory("mavenarchetypes"); //NOI18N
            ArrayList<Archetype> toRet = new ArrayList<Archetype>();
            MavenEmbedder embedder = EmbedderFactory.getOnlineEmbedder();
            SettingsDecryptionResult settings = embedder.lookupComponent(SettingsDecrypter.class).decrypt(new DefaultSettingsDecryptionRequest(embedder.getSettings()));
            
            for (RepositoryInfo info : RepositoryPreferences.getInstance().getRepositoryInfos()) {
                if (info.isRemoteDownloadable()) {
                    File catalog = new File(new File( root, info.getId()), "archetype-catalog.xml"); //NOI18N
                    boolean download = false;
                    if (!catalog.exists()) {
                        download = true;
                    } else {
                        long lastM = catalog.lastModified();
                        if (lastM == 0) {
                            download = true;
                        } else if (lastM - System.currentTimeMillis() > ARCHETYPE_TIMEOUT) {
                            download = true;
                        }
                    }
                    
                    if (download) {
                        download(info.getId(), info.getRepositoryUrl(), catalog, settings, embedder);
                    }
                    
                    if (catalog.exists()) {
                        try {
                            toRet.addAll(CatalogRepoProvider.getArchetypes(Utilities.toURI(catalog).toURL(), info.getRepositoryUrl()));
                        } catch (MalformedURLException ex) {
                            LOG.log(Level.INFO, null, ex);
                        }
                    }
                }
            }
            
            return toRet;
        }

        private void download(String id, String url, File catalog, SettingsDecryptionResult settings, MavenEmbedder embedder) {
            String baseurl = url;
            String repoId = id;
            try {
                catalog.getParentFile().mkdirs();
                AuthenticationInfo wagonAuth = null;
//        //this comes from maven-compat
                MirrorSelector mrri = embedder.lookupComponent(MirrorSelector.class);

                // handle mirroring
                ArtifactRepositoryLayout layout = (ArtifactRepositoryLayout) embedder.getPlexus().lookup(ArtifactRepositoryLayout.ROLE, "default"); //NOI18N
                ArtifactRepositoryFactory arf = embedder.lookupComponent(ArtifactRepositoryFactory.class);

                //what is the update policy?
                ArtifactRepositoryPolicy policy = new ArtifactRepositoryPolicy(true, ArtifactRepositoryPolicy.UPDATE_POLICY_DAILY, ArtifactRepositoryPolicy.UPDATE_POLICY_DAILY);
                ArtifactRepository art = arf.createArtifactRepository(repoId, baseurl, layout, policy, policy);
                Mirror mirr = mrri.getMirror(art, embedder.getSettings().getMirrors());
                if (mirr != null) {
                    baseurl = mirr.getUrl();
                    repoId = mirr.getId();
                }

                for (Server server : settings.getServers()) {
                    if (repoId.equals(server.getId())) {
                        wagonAuth = new AuthenticationInfo();
                        wagonAuth.setUserName(server.getUsername());
                        wagonAuth.setPassword(server.getPassword());
                        wagonAuth.setPassphrase(server.getPassphrase());
                        wagonAuth.setPrivateKey(server.getPrivateKey());
                        break;
                    }
                }
                String protocol = URI.create(baseurl).getScheme();
                ProxyInfo wagonProxy = null;
                for (Proxy proxy : settings.getProxies()) {
                    if (proxy.isActive()) {
                        wagonProxy = new ProxyInfo();
                        wagonProxy.setHost(proxy.getHost());
                        wagonProxy.setPort(proxy.getPort());
                        wagonProxy.setNonProxyHosts(proxy.getNonProxyHosts());
                        wagonProxy.setUserName(proxy.getUsername());
                        wagonProxy.setPassword(proxy.getPassword());
                        wagonProxy.setType(protocol);
                        break;
                    }
                }
                Wagon wagon = embedder.getPlexus().lookup(Wagon.class, protocol);
                assert wagon != null;

                Repository repository = new Repository(repoId, baseurl);

                try {

                    // when working in the context of Maven, the WagonManager is already
                    // populated with proxy information from the Maven environment

                    if (wagonAuth != null) {
                        if (wagonProxy != null) {
                            wagon.connect(repository, wagonAuth, wagonProxy);
                        } else {
                            wagon.connect(repository, wagonAuth);
                        }
                    } else {
                        if (wagonProxy != null) {
                            wagon.connect(repository, wagonProxy);
                        } else {
                            wagon.connect(repository);
                        }
                    }

                    File temp = File.createTempFile("maven", "catalog"); //NOI18N
                    try {
                        wagon.get("archetype-catalog.xml", temp); //NOI18N
                        //only overwrite the old file or create file if the content is there.
                        if (temp.exists() && temp.length() > 0) {
                            FileUtils.copyFile(temp, catalog);
                            temp.delete();
                        }
                    } finally {
                        wagon.disconnect();
                    }

                } catch (AuthenticationException ex) {
                    String msg = "Authentication exception connecting to " + repository;  //NOI18N
                    throw new IOException(msg, ex);
                } catch (WagonException ex) {
                    String msg = "Wagon exception connecting to " + repository;  //NOI18N
                    throw new IOException(msg, ex);
                } finally {
                    try {
                        wagon.disconnect();
                    } catch (ConnectionException ex) {
                        String msg = "Wagon exception disconnecting from " + repository; //NOI18N
                        throw new IOException(msg, ex);
                    }
                }
            } catch (ComponentLookupException ex) {
                LOG.log(Level.FINE, null, ex);
            } catch (IOException io) {
                LOG.log(Level.INFO, null, io);
            }

        }
    }
}
