/**
 *  Copyright 2005-2015 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.commands;

import io.fabric8.api.FabricService;
import io.fabric8.api.RuntimeProperties;
import io.fabric8.common.util.Strings;
import io.fabric8.core.jmx.MQManager;

import org.apache.felix.gogo.commands.Argument;
import org.apache.felix.gogo.commands.Command;
import org.apache.felix.gogo.commands.CompleterValues;
import org.apache.felix.gogo.commands.Option;

import io.fabric8.api.CreateChildContainerOptions;
import io.fabric8.api.CreateContainerBasicOptions;
import io.fabric8.api.CreateContainerMetadata;
import io.fabric8.api.FabricAuthenticationException;
import io.fabric8.api.Profile;
import io.fabric8.api.ZkDefs;
import io.fabric8.api.jmx.BrokerKind;
import io.fabric8.api.jmx.MQBrokerConfigDTO;
import io.fabric8.utils.shell.ShellUtils;

import org.apache.karaf.shell.console.AbstractAction;

import java.io.IOException;
import java.util.List;
import java.util.Map;

@Command(name = MQCreate.FUNCTION_VALUE, scope = MQCreate.SCOPE_VALUE, description = MQCreate.DESCRIPTION)
public class MQCreateAction extends AbstractAction {

    @Argument(index = 0, required = true, description = "Broker name")
    protected String name = null;

    @Option(name = "--parent-profile", description = "The parent profile to extend")
    protected String parentProfile;

    @Option(name = "--profile", description = "The profile name to create/update if defining N+1 broker groups (otherwise this is defaulted to the broker name). Defaults to 'mq-broker-$GROUP.$NAME'")
    protected String profile;

    @Option(name = "--client-profile", description = "The profile name for clients to use to connect to the broker group. Defaults to 'mq-client-$GROUP'")
    protected String clientProfile;

    @Option(name = "--client-parent-profile", description = "The parent profile used for the client-profile for clients connecting to the broker group. Defaults to 'mq-client-base'")
    protected String clientParentProfile;

    @Option(name = "--property", aliases = {"-D"}, description = "Additional properties to define in the profile. To specify multiple properties, use this flag multiple times.", multiValued = true)
    List<String> properties;

    @Option(name = "--config", description = "Configuration to use")
    protected String config;

    @Option(name = "--data", description = "Data directory for the broker")
    protected String data;

    @Option(name = "--port", aliases = "--ports", multiValued = true, description = "Port number for the transport connectors. To specify multiple ports, use this flag multiple times.")
    protected String[] ports;

    @Option(name = "--group", description = "Broker group")
    protected String group;

    @Option(name = "--network", aliases = "--networks", multiValued = true, description = "Broker networks. To specify multiple networks, use this flag multiple times.")
    protected String[] networks;

    @Option(name = "--networks-username", description = "Broker networks UserName")
    protected String networksUserName;

    @Option(name = "--networks-password", description = "Broker networks Password")
    protected String networksPassword;

    @Option(name = "--version", description = "The version id in the registry")
    protected String version = ZkDefs.DEFAULT_VERSION;

    @Option(name = "--create-container", multiValued = false, required = false, description = "Comma separated list of child containers to create with mq profile")
    protected String create;

    @Option(name = "--assign-container", multiValued = false, required = false, description = "Assign this mq profile to the following containers")
    protected String assign;

    @Option(name = "--jmx-user", multiValued = false, required = false, description = "The jmx user name of the parent container.")
    protected String username;

    @Option(name = "--jmx-password", multiValued = false, required = false, description = "The jmx password of the parent container.")
    protected String password;

    @Option(name = "--jvm-opts", multiValued = false, required = false, description = "Options to pass to the container's JVM.")
    protected String jvmOpts;

    @Option(name = "--minimumInstances", multiValued = false, required = false, description = "Minimum number of containers required of this broker's profile.")
    protected Integer minimumInstances;

    @Option(name = "--replicas", multiValued = false, required = false, description = "Number of replicas required for replicated brokers (which typically use a parent-profile of mq-replicated profile).")
    protected Integer replicas;

    @Option(name = "--kind", multiValued = false, required = false, description = "The kind of broker to create. Defaults to 'MasterSlave'")
    @CompleterValues()
    protected BrokerKind kind;

    @Option(name = "--no-ssl", multiValued = false, required = false, description = "Used to disable ssl support")
    protected boolean nossl;

    private final FabricService fabricService;
    private final RuntimeProperties runtimeProperties;


    MQCreateAction(FabricService fabricService, RuntimeProperties runtimeProperties) {
        this.fabricService = fabricService;
        this.runtimeProperties = runtimeProperties;
    }

    @Override
    protected Object doExecute() throws Exception {
        MQBrokerConfigDTO dto = createDTO();

        Profile profile = MQManager.createOrUpdateProfile(dto, fabricService, runtimeProperties);
        if (profile == null) {
            return null;
        }
        String profileId = profile.getId();

        System.out.println("MQ profile " + profileId + " ready");

        // assign profile to existing containers
        if (assign != null) {
            String[] assignContainers = assign.split(",");
            MQManager.assignProfileToContainers(fabricService, profile, assignContainers);
        }

        // create containers
        if (create != null) {
            String[] createContainers = create.split(",");
            List<CreateContainerBasicOptions.Builder> builderList = MQManager.createContainerBuilders(
                    dto, fabricService, "child", profileId, dto.version(), createContainers);
            for (CreateContainerBasicOptions.Builder builder : builderList) {
                CreateContainerMetadata[] metadatas;
                try {
                    if (builder instanceof CreateChildContainerOptions.Builder) {
                        CreateChildContainerOptions.Builder childBuilder = (CreateChildContainerOptions.Builder) builder;
                        builder = childBuilder.jmxUser(username).jmxPassword(password);
                    }
                    metadatas = fabricService.createContainers(builder.build());

                    // check if there was a FabricAuthenticationException as failure then we can try again
                    if (metadatas != null) {
                        for (CreateContainerMetadata meta : metadatas) {
                            if (meta.getFailure() != null && meta.getFailure() instanceof FabricAuthenticationException) {
                                throw (FabricAuthenticationException) meta.getFailure();
                            }
                        }
                    }

                    ShellUtils.storeFabricCredentials(session, username, password);
                } catch (FabricAuthenticationException fae) {
                    //If authentication fails, prompts for credentials and try again.
                    if (builder instanceof CreateChildContainerOptions.Builder) {
                        CreateChildContainerOptions.Builder childBuilder = (CreateChildContainerOptions.Builder) builder;
                        promptForJmxCredentialsIfNeeded();
                        metadatas = fabricService.createContainers(childBuilder.jmxUser(username).jmxPassword(password).build());
                        ShellUtils.storeFabricCredentials(session, username, password);
                    }
                }
            }
        }
        return null;
    }

    private MQBrokerConfigDTO createDTO() {
        if (Strings.isNullOrBlank(username)) {
            username = ShellUtils.retrieveFabricUser(session);
        }
        if (Strings.isNullOrBlank(password)) {
            password = ShellUtils.retrieveFabricUserPassword(session);
        }

        MQBrokerConfigDTO dto = new MQBrokerConfigDTO();
        if( config != null ) {
            dto.setConfigUrl(config);
        } else {
            if( nossl) {
                dto.setConfigUrl("broker.xml");
            } else {
                dto.setConfigUrl("ssl-broker.xml");
            }
        }
        dto.setData(data);
        if (ports != null && ports.length > 0) {
            for (String port : ports) {
                addConfig(port, dto.getPorts());
            }
        }
        dto.setGroup(group);
        dto.setJvmOpts(jvmOpts);
        dto.setBrokerName(name);
        dto.setProfile(profile);
        dto.setClientProfile(clientProfile);
        dto.setClientParentProfile(clientParentProfile);
        dto.setNetworks(networks);
        dto.setNetworksPassword(networksPassword);
        dto.setNetworksUserName(networksUserName);
        dto.setParentProfile(parentProfile);
        dto.setProperties(properties);
        dto.setVersion(version);
        dto.setMinimumInstances(minimumInstances);
        dto.setReplicas(replicas);
        dto.setSsl(!nossl);
        dto.setKind(kind);
        return dto;
    }

    private void promptForJmxCredentialsIfNeeded() throws IOException {
        // If the username was not configured via cli, then prompt the user for the values
        if (username == null) {
            log.debug("Prompting user for jmx login");
            username = ShellUtils.readLine(session, "Jmx Login for " + fabricService.getCurrentContainerName() + ": ", false);
        }

        if (password == null) {
            password = ShellUtils.readLine(session, "Jmx Password for " + fabricService.getCurrentContainerName() + ": ", true);
        }
    }

    private void addConfig(String config, Map<String, String> map) {
        String key = null;
        String value = null;
        if (config.contains("=")) {
            key = config.substring(0, config.indexOf("="));
            value = config.substring(config.indexOf("=") + 1);
        }
        if (key != null && value != null) {
           map.put(key, value);
        }
    }
}
