/*
 *  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
 *
 *      https://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.ivy.osgi.repo;

import java.io.IOException;
import java.net.URI;
import java.text.ParseException;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.jar.JarInputStream;
import java.util.jar.Manifest;

import org.apache.ivy.core.event.EventManager;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultDependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.resolve.ResolveData;
import org.apache.ivy.core.resolve.ResolveEngine;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.resolve.ResolvedModuleRevision;
import org.apache.ivy.core.search.ModuleEntry;
import org.apache.ivy.core.search.OrganisationEntry;
import org.apache.ivy.core.search.RevisionEntry;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.core.sort.SortEngine;
import org.apache.ivy.osgi.core.BundleInfoAdapter;
import org.apache.ivy.plugins.resolver.BasicResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.Message;

public class ResolverManifestIterable implements Iterable<ManifestAndLocation> {

    // We should support the interface DependencyResolver, but the API is not convenient to get
    // references to artifact
    private final BasicResolver resolver;

    public ResolverManifestIterable(BasicResolver resolver) {
        this.resolver = resolver;
    }

    public Iterator<ManifestAndLocation> iterator() {
        return new ResolverManifestIterator();
    }

    class ResolverManifestIterator implements Iterator<ManifestAndLocation> {

        private OrganisationEntry[] organisations;

        private int indexOrganisation = 0;

        private OrganisationEntry organisation;

        private ModuleEntry[] modules;

        private int indexModule = -1;

        private ModuleEntry module;

        private ManifestAndLocation next = null;

        private RevisionEntry[] revisions;

        private int indexRevision;

        private RevisionEntry revision;

        private Artifact[] artifacts;

        private int indexArtifact;

        private Artifact artifact;

        private ModuleRevisionId mrid;

        private ResolveData data;

        public ResolverManifestIterator() {
            organisations = resolver.listOrganisations();
            IvySettings settings = new IvySettings();
            ResolveEngine engine = new ResolveEngine(settings, new EventManager(), new SortEngine(
                    settings));
            data = new ResolveData(engine, new ResolveOptions());
        }

        public boolean hasNext() {
            while (next == null) {
                if (organisation == null) {
                    if (indexOrganisation >= organisations.length) {
                        return false;
                    }
                    organisation = organisations[indexOrganisation++];
                    modules = resolver.listModules(organisation);
                    indexModule = 0;
                    module = null;
                }
                if (module == null) {
                    if (indexModule >= modules.length) {
                        organisation = null;
                        continue;
                    }
                    module = modules[indexModule++];
                    revisions = resolver.listRevisions(module);
                    indexRevision = 0;
                    revision = null;
                }
                if (revision == null) {
                    if (indexRevision >= revisions.length) {
                        module = null;
                        continue;
                    }
                    revision = revisions[indexRevision++];
                    mrid = ModuleRevisionId.newInstance(organisation.getOrganisation(),
                        module.getModule(), revision.getRevision());
                    DefaultDependencyDescriptor dd = new DefaultDependencyDescriptor(mrid, false);
                    ResolvedModuleRevision dependency;
                    try {
                        dependency = resolver.getDependency(dd, data);
                    } catch (ParseException e) {
                        Message.error("Error while resolving " + mrid + " : " + e.getMessage());
                        revision = null;
                        continue;
                    }
                    if (dependency == null) {
                        revision = null;
                        continue;
                    }
                    ModuleDescriptor md = dependency.getDescriptor();
                    mrid = md.getModuleRevisionId();
                    artifacts = md.getAllArtifacts();
                    indexArtifact = 0;
                    artifact = null;
                }
                if (artifact == null) {
                    if (indexArtifact >= artifacts.length) {
                        revision = null;
                        continue;
                    }
                    artifact = artifacts[indexArtifact++];
                }
                ResolvedResource resource = resolver.doFindArtifactRef(artifact, null);
                if (resource == null) {
                    artifact = null;
                    continue;
                }
                JarInputStream in;
                try {
                    in = new JarInputStream(resource.getResource().openStream());
                } catch (IOException e) {
                    Message.warn("Unreadable jar " + resource.getResource().getName() + " ("
                            + e.getMessage() + ")");
                    artifact = null;
                    continue;
                }
                Manifest manifest;
                try {
                    manifest = in.getManifest();
                } finally {
                    try {
                        in.close();
                    } catch (IOException e) {
                        // don't care
                    }
                }
                if (manifest == null) {
                    Message.debug("No manifest on " + artifact);
                } else {
                    URI uri = BundleInfoAdapter.buildIvyURI(artifact);
                    next = new ManifestAndLocation(manifest, uri, null);
                }
                artifact = null;
            }
            return true;
        }

        public ManifestAndLocation next() {
            if (!hasNext()) {
                throw new NoSuchElementException();
            }
            ManifestAndLocation manifest = next;
            next = null;
            return manifest;
        }

        public void remove() {
            throw new UnsupportedOperationException();
        }
    }

}
