/*
 * Copyright 2016-2022 Red Hat, Inc. and/or its affiliates
 * and other contributors as indicated by the @author tags.
 *
 * 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.
 */
package org.jboss.galleon.runtime;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.jboss.galleon.ProvisioningDescriptionException;
import org.jboss.galleon.ProvisioningException;
import org.jboss.galleon.layout.FeaturePackLayout;
import org.jboss.galleon.spec.FeatureSpec;
import org.jboss.galleon.spec.PackageDependencySpec;
import org.jboss.galleon.state.FeaturePack;

/**
 *
 * @author Alexey Loubyansky
 */
public class FeaturePackRuntime extends FeaturePackLayout implements FeaturePack<PackageRuntime> {

    private final Map<String, PackageRuntime> packages;
    private final Map<String, ResolvedFeatureSpec> featureSpecs;

    FeaturePackRuntime(FeaturePackRuntimeBuilder builder, ProvisioningRuntimeBuilder rt) throws ProvisioningException {
        super(builder.producer.getLocation().getFPID(), builder.getDir(), builder.getType());
        this.spec = builder.getSpec();
        this.featureSpecs = builder.featureSpecs == null ? Collections.emptyMap() : builder.featureSpecs;

        final Map<String, PackageRuntime> tmpPackages = new LinkedHashMap<>(builder.pkgOrder.size());

        switch(rt.includedPkgDeps) {
            case ProvisioningRuntimeBuilder.PKG_DEP_ALL:
                for(String pkgName : builder.pkgOrder) {
                    tmpPackages.put(pkgName, builder.pkgBuilders.get(pkgName).build(this));
                }
                break;
            case ProvisioningRuntimeBuilder.PKG_DEP_PASSIVE_PLUS:
            case ProvisioningRuntimeBuilder.PKG_DEP_PASSIVE:
                final List<PackageRuntime> included = new ArrayList<>(builder.pkgOrder.size());
                int i;
                do {
                    i = included.size();
                    if(builder.pkgOrder.isEmpty()) {
                        break;
                    }
                    final Iterator<String> pkgNames = builder.pkgOrder.descendingIterator();
                    while(pkgNames.hasNext()) {
                        final String pkgName = pkgNames.next();
                        final PackageRuntime.Builder pkgBuilder = builder.pkgBuilders.get(pkgName);
                        if(pkgBuilder.isFlagOn(PackageRuntime.INCLUDED)) {
                            included.add(pkgBuilder.build(this));
                            pkgNames.remove();
                        } else if ((pkgBuilder.isFlagOn(PackageRuntime.PARENT_INCLUDED) || pkgBuilder.isFlagOn(PackageRuntime.ROOT))
                                && (rt.includedPkgDeps == ProvisioningRuntimeBuilder.PKG_DEP_PASSIVE
                                        && pkgBuilder.isPassiveWithSatisfiedDeps()
                                        || rt.includedPkgDeps == ProvisioningRuntimeBuilder.PKG_DEP_PASSIVE_PLUS
                                                && ((pkgBuilder.type & PackageDependencySpec.OPTIONAL) > 0
                                                        && pkgBuilder.type != PackageDependencySpec.PASSIVE
                                                        || pkgBuilder.isPassiveWithSatisfiedDeps()))) {
                            pkgBuilder.include();
                            included.add(pkgBuilder.build(this));
                            pkgNames.remove();
                        }
                    }
                } while(included.size() != i);

                while (--i >= 0) {
                    final PackageRuntime pkg = included.get(i);
                    tmpPackages.put(pkg.getName(), pkg);
                }
                break;
            case ProvisioningRuntimeBuilder.PKG_DEP_REQUIRED:
                for(String pkgName : builder.pkgOrder) {
                    final PackageRuntime.Builder pkgBuilder = builder.pkgBuilders.get(pkgName);
                    if(pkgBuilder.isFlagOn(PackageRuntime.INCLUDED)) {
                        tmpPackages.put(pkgName, pkgBuilder.build(this));
                    }
                }
                break;
            default:
                throw new ProvisioningException("Unexpected package dependency mask " + Integer.toBinaryString(rt.includedPkgDeps));
        }

        packages = Collections.unmodifiableMap(tmpPackages);
    }

    public Set<String> getSystemPaths() {
        return spec.getSystemPaths();
    }

    @Override
    public boolean hasPackages() {
        return !packages.isEmpty();
    }

    @Override
    public boolean containsPackage(String name) {
        return packages.containsKey(name);
    }

    @Override
    public Set<String> getPackageNames() {
        return packages.keySet();
    }

    @Override
    public Collection<PackageRuntime> getPackages() {
        return packages.values();
    }

    @Override
    public PackageRuntime getPackage(String name) {
        return packages.get(name);
    }

    public Set<String> getFeatureSpecNames() {
        return featureSpecs.keySet();
    }

    public Collection<ResolvedFeatureSpec> getFeatureSpecs() {
        return featureSpecs.values();
    }

    public FeatureSpec getFeatureSpec(String name) throws ProvisioningException {
        if (featureSpecs.containsKey(name)) {
            return featureSpecs.get(name).xmlSpec;
        }
        return loadFeatureSpec(name);
    }

    public ResolvedFeatureSpec getResolvedFeatureSpec(String name) throws ProvisioningDescriptionException {
        return featureSpecs.get(name);
    }
}
