/*
 * 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
 *
 *      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.apache.karaf.shell.dev;

import java.io.IOException;
import static java.lang.String.format;
import java.net.URL;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;

import org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.Command;
import org.apache.karaf.shell.console.OsgiCommandSupport;
import org.ops4j.pax.url.wrap.Handler;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleException;
import org.osgi.framework.ServiceReference;
import org.osgi.service.packageadmin.ExportedPackage;
import org.osgi.service.packageadmin.PackageAdmin;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Command for enabling/disabling debug logging on a bundle and calculating the difference in
 * wired imports.
 */
@Command(scope = "dev", name = "dynamic-import", description = "Enables/disables dynamic-import for a given bundle.")
public class DynamicImport extends AbstractBundleCommand {

    private final Logger LOG = LoggerFactory.getLogger(DynamicImport.class);

    /**
     * The header key where we store the active wires when we enable DynamicImport=*
     */
    protected static final String ORIGINAL_WIRES = "Original-Wires";

    @Override
    protected void doExecute(Bundle bundle) throws Exception {
        if (bundle.getHeaders().get(ORIGINAL_WIRES) == null) {
            enableDynamicImports(bundle);
        } else {
            disableDynamicImports(bundle);
        }
    }

    /*
     * Enable DynamicImport=* on the bundle
     */
    private void enableDynamicImports(Bundle bundle) throws IOException, BundleException {
        System.out.printf("Enabling dynamic imports on bundle %s%n", bundle);

        String location =
                String.format("wrap:%s$" +
                        "Bundle-UpdateLocation=%s&" +
                        "DynamicImport-Package=*&" +
                        "%s=%s&" +
                        "overwrite=merge",
                        bundle.getLocation(),
                        bundle.getLocation(),
                        ORIGINAL_WIRES,
                        explode(getWiredBundles(bundle).keySet()));
        LOG.debug(format("Updating %s with URL %s", bundle, location));

        URL url = new URL(location);
        bundle.update(url.openStream());
        getPackageAdmin().refreshPackages(new Bundle[] {bundle});
    }

    /*
     * Disable DynamicImport=* on the bundle
     *
     * At this time, we will also calculate the difference in package wiring for the bundle compared to
     * when we enabled the DynamicImport
     */
    private void disableDynamicImports(Bundle bundle) throws BundleException {
        System.out.printf("Disabling dynamic imports on bundle %s%n", bundle);

        Set<String> current = getWiredBundles(bundle).keySet();
        for (String original : bundle.getHeaders().get(ORIGINAL_WIRES).toString().split(",")) {
            current.remove(original);
        }

        if (current.isEmpty()) {
            System.out.println("(no additional packages have been wired since dynamic import was enabled)");
        } else {
            System.out.printf("%nAdditional packages wired since dynamic import was enabled:%n");
            for (String pkg : current) {
                System.out.printf("- %s%n", pkg);
            }
        }

        bundle.update();
    }

    /*
     * Explode a set of string values in to a ,-delimited string
     */
    private String explode(Set<String> set) {
        StringBuffer result = new StringBuffer();
        Iterator<String> it = set.iterator();
        while (it.hasNext()) {
            result.append(it.next());
            if (it.hasNext()) {
                result.append(",");
            }
        }
        if (result.length() == 0) {
            return "--none--";
        }
        return result.toString();
    }
}
