/**
 *  Copyright 2005-2016 Red Hat, Inc.
 *
 *  Red Hat 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 io.fabric8.boot.commands.support;

import io.fabric8.api.CreateContainerMetadata;
import io.fabric8.api.FabricAuthenticationException;
import io.fabric8.api.FabricService;
import io.fabric8.api.Profile;
import io.fabric8.api.ProfileRegistry;
import io.fabric8.api.ProfileService;
import io.fabric8.api.Version;
import io.fabric8.api.ZooKeeperClusterService;
import io.fabric8.utils.FabricValidations;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.felix.gogo.commands.Option;
import org.apache.karaf.shell.console.AbstractAction;

public abstract class AbstractContainerCreateAction extends AbstractAction {

    @Option(name = "--version", description = "The version of the new container (must be an existing version). Defaults to the current default version.")
    protected String version;
    @Option(name = "--profile", multiValued = true, required = false, description = "The profile IDs to associate with the new container(s). For multiple profiles, specify the flag multiple times. Defaults to the profile named \"default\".")
    protected Set<String> profiles;
    @Option(name = "--resolver", multiValued = false, required = false, description = "The resolver policy for this container(s). Possible values are: localip, localhostname, publicip, publichostname, manualip. Defaults to the fabric's default resolver policy.")
    protected String resolver;
    @Option(name = "-m", aliases = {"--manual-ip"}, description = "An address to use, when using the manualip resolver.")
    protected String manualIp;
    @Option(name = "-b", aliases = {"--bind-address"}, description = "The default bind address.")
    protected String bindAddress;
    @Option(name = "--ensemble-server", multiValued = false, required = false, description = "[Deprecated] Whether the new container should be a fabric ensemble server (ZooKeeper ensemble server). Note that this does not add the new container to the current fabric but creates a wholly new fabric.")
    protected Boolean isEnsembleServer = Boolean.FALSE;
    @Option(name = "--zookeeper-password", multiValued = false, description = "The ensemble password to use (one will be generated if not given)")
    protected String zookeeperPassword;
    @Option(name = "--jvm-opts", multiValued = false, required = false, description = "Options to pass to the container's JVM.")
    protected String jvmOpts;
    @Option(name = "--datastore-option", multiValued = true, required = false, description = "Options to pass to the container's datastore. To specify multiple options, use this flag multiple times.")
    protected String[] dataStoreOption;

    protected final FabricService fabricService;
    protected final ProfileService profileService;
    protected final ZooKeeperClusterService clusterService;

    protected AbstractContainerCreateAction(FabricService fabricService, ZooKeeperClusterService clusterService) {
        this.fabricService = fabricService;
        this.profileService = fabricService.adapt(ProfileService.class);
        this.clusterService = clusterService;
    }

    protected Set<String> getProfileNames() {
        Set<String> names = this.profiles;
        if (names == null || names.isEmpty()) {
            names = new LinkedHashSet<String>();
            names.add("default");
        }
        return names;
    }

    /**
     * Pre validates input before creating the container(s)
     *
     * @param name the name of the container to create
     * @throws IllegalArgumentException is thrown if input is invalid
     */
    protected void preCreateContainer(String name) throws IllegalArgumentException {
        FabricValidations.validateContainerName(name);
        if (!isEnsembleServer) {
            if (clusterService.getEnsembleContainers().isEmpty()) {
                if (!isEnsembleServer) {
                    throw new IllegalStateException("The use of the --ensemble-server option is mandatory when creating an initial container");
                }
                return;
            }

            if (FabricCommand.doesContainerExist(fabricService, name)) {
                throw new IllegalArgumentException("A container with name " + name + " already exists.");
            }

            // get the profiles for the given version
            Version ver = version != null ? profileService.getRequiredVersion(version) : fabricService.getRequiredDefaultVersion();
            List<Profile> profiles = ver.getProfiles();

            // validate profiles exists before creating a new container
            Set<String> names = getProfileNames();
            for (String profile : names) {
                Profile prof = getProfile(profiles, profile, ver);
                if (prof == null) {
                    throw new IllegalArgumentException("Profile " + profile + " with version " + ver.getId() + " does not exist.");
                }
                if (prof.isAbstract()) {
                    throw new IllegalArgumentException("Profile " + profile + " with version " + ver.getId() + " is abstract and can not be associated to containers.");
                }
            }
        }

        if (!isEnsembleServer && fabricService.getZookeeperUrl() == null) {
            throw new IllegalArgumentException("Either start a zookeeper ensemble or use --ensemble-server.");
        }
    }

    protected void displayContainers(CreateContainerMetadata[] metadatas) {
        List<CreateContainerMetadata> success = new ArrayList<CreateContainerMetadata>();
        List<CreateContainerMetadata> failures = new ArrayList<CreateContainerMetadata>();
        for (CreateContainerMetadata metadata : metadatas) {
            (metadata.isSuccess() ? success : failures).add(metadata);
        }
        if (success.size() > 0) {
            System.out.println("The following containers have been created successfully:");
            for (CreateContainerMetadata m : success) {
                System.out.println("\t" + m.toString());
            }
        }
        if (failures.size() > 0) {
            System.out.println("The following containers have failed:");
            for (CreateContainerMetadata m : failures) {
                System.out.println("\t" + m.getContainerName() + ": " + m.getFailure().getMessage());
            }
        }
    }

    protected void rethrowAuthenticationErrors(CreateContainerMetadata[] metadata) {
        FabricAuthenticationException rethrow = null;
        for (CreateContainerMetadata md : metadata) {
            if (md.getFailure() instanceof FabricAuthenticationException) {
                rethrow = (FabricAuthenticationException) md.getFailure();
            } else {
                return;
            }
        }
        if (rethrow != null) {
            throw rethrow;
        }
    }

    protected Map<String, String> getDataStoreProperties() {
        ProfileRegistry profileRegistry = fabricService.adapt(ProfileRegistry.class);
        Map<String, String> options = new HashMap<String, String>(profileRegistry.getDataStoreProperties());
        if (dataStoreOption != null) {
            for (String opt : dataStoreOption) {
                String[] parts = opt.trim().split(" +");
                options.put(parts[0], parts[1]);
            }
        }
        return options;
    }

    private static Profile getProfile(List<Profile> profiles, String name, Version version) {
        if (profiles == null || profiles.size() == 0) {
            return null;
        }
        for (Profile profile : profiles) {
            if (profile.getId().equals(name) && profile.getVersion().equals(version.getId())) {
                return profile;
            }
        }
        return null;
    }
}
