/*
 * 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.brooklyn.location.jclouds;

import java.util.Collection;
import java.util.Collections;
import java.util.Map;

import javax.annotation.Nullable;

import org.apache.brooklyn.api.location.LocationSpec;
import org.apache.brooklyn.api.mgmt.ManagementContext;
import org.apache.brooklyn.config.ConfigKey;
import org.apache.brooklyn.core.config.ConfigKeys;
import org.apache.brooklyn.core.internal.BrooklynProperties;
import org.apache.brooklyn.core.mgmt.internal.LocalManagementContext;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.core.config.ConfigBag;
import org.apache.brooklyn.util.exceptions.CompoundRuntimeException;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.jclouds.compute.ComputeService;
import org.jclouds.compute.domain.Image;
import org.jclouds.compute.domain.Template;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Throwables;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;

public class BailOutJcloudsLocation extends JcloudsLocation {

    public static final String ERROR_MESSAGE = "early termination for test";
    public static final RuntimeException BAIL_OUT_FOR_TESTING = new RuntimeException(ERROR_MESSAGE);
    
    // Don't care which image; not actually provisioning
    private static final String US_EAST_IMAGE_ID = "us-east-1/ami-7d7bfc14";

    public static final ConfigKey<Function<ConfigBag, Void>> BUILD_TEMPLATE_INTERCEPTOR = ConfigKeys.newConfigKey(
            new TypeToken<Function<ConfigBag, Void>>() {},
            "buildtemplateinterceptor");

    public static final ConfigKey<Boolean> BUILD_TEMPLATE = ConfigKeys.newBooleanConfigKey(
            "buildtemplate");

    ConfigBag lastConfigBag;
    Template template;

    public BailOutJcloudsLocation() {
        super();
    }

    public BailOutJcloudsLocation(Map<?, ?> conf) {
        super(conf);
    }

    @Override
    public Template buildTemplate(ComputeService computeService, ConfigBag config, Collection<JcloudsLocationCustomizer> customizers) {
        lastConfigBag = config;
        if (getConfig(BUILD_TEMPLATE_INTERCEPTOR) != null) {
            getConfig(BUILD_TEMPLATE_INTERCEPTOR).apply(config);
        }
        if (Boolean.TRUE.equals(getConfig(BUILD_TEMPLATE))) {
            template = super.buildTemplate(computeService, config, customizers);
        }
        throw new RuntimeException(BAIL_OUT_FOR_TESTING);
    }

    public Template getTemplate() {
        return template;
    }

    public void tryObtain() {
        tryObtain(Collections.emptyMap());
    }

    public void tryObtain(Map<?, ?> flags) {
        tryObtainAndCheck(flags, Predicates.alwaysTrue());
    }

    public void tryObtainAndCheck(Map<?, ?> flags, Predicate<? super ConfigBag> test) {
        try {
            obtain(flags);
        } catch (Exception e) {
            boolean found = Iterables.tryFind(Throwables.getCausalChain(e), Predicates.<Throwable>equalTo(e)).isPresent();
            if (!found && e instanceof CompoundRuntimeException) {
                for (Throwable cause : ((CompoundRuntimeException) e).getAllCauses()) {
                    found = Iterables.tryFind(Throwables.getCausalChain(cause), Predicates.<Throwable>equalTo(e)).isPresent();
                    if (found) break;
                }
            }
            if (found) {
                test.apply(lastConfigBag);
            } else {
                throw Exceptions.propagate(e);
            }
        }
    }

    @Override
    @VisibleForTesting
    public UserCreation createUserStatements(@Nullable Image image, ConfigBag config) {
        return super.createUserStatements(image, config);
    }


    public static BailOutJcloudsLocation newBailOutJcloudsLocation(ManagementContext mgmt) {
        return newBailOutJcloudsLocation(mgmt, Collections.<ConfigKey<?>, Object>emptyMap());
    }

    public static BailOutJcloudsLocation newBailOutJcloudsLocation(ManagementContext mgmt, Map<ConfigKey<?>, ?> config) {
        Map<ConfigKey<?>, ?> allConfig = MutableMap.<ConfigKey<?>, Object>builder()
                .put(IMAGE_ID, "bogus")
                .put(CLOUD_PROVIDER, "aws-ec2")
                .put(ACCESS_IDENTITY, "bogus")
                .put(CLOUD_REGION_ID, "bogus")
                .put(ACCESS_CREDENTIAL, "bogus")
                .put(USER, "fred")
                .put(MIN_RAM, 16)
                .put(JcloudsLocation.MACHINE_CREATE_ATTEMPTS, 1)
                .putAll(config)
                .build();
        return mgmt.getLocationManager().createLocation(
                LocationSpec.create(BailOutJcloudsLocation.class).configure(allConfig));
    }


    // todo better name

    /** As {@link BailOutJcloudsLocation}, counting the number of {@link #buildTemplate} calls. */
    public static class CountingBailOutJcloudsLocation extends BailOutJcloudsLocation {
        int buildTemplateCount = 0;
        @Override
        public Template buildTemplate(ComputeService computeService, ConfigBag config, Collection<JcloudsLocationCustomizer> customizers) {
            buildTemplateCount++;
            return super.buildTemplate(computeService, config, customizers);
        }
    }

    public static CountingBailOutJcloudsLocation newCountingBailOutJcloudsLocation(ManagementContext mgmt, Map flags) {
        LocationSpec<CountingBailOutJcloudsLocation> spec = LocationSpec.create(CountingBailOutJcloudsLocation.class)
                .configure(flags);
        return mgmt.getLocationManager().createLocation(spec);
    }

    /** @see #newBailOutJcloudsLocationForLiveTest(LocalManagementContext, Map)} */
    public static BailOutJcloudsLocation newBailOutJcloudsLocationForLiveTest(LocalManagementContext mgmt) {
        return newBailOutJcloudsLocationForLiveTest(mgmt, Collections.<ConfigKey<?>, Object>emptyMap());
    }

    /**
     * Takes identity and access credential from management context's Brooklyn properties and sets
     * inbound ports to [22, 80, 9999].
     */
    public static BailOutJcloudsLocation newBailOutJcloudsLocationForLiveTest(LocalManagementContext mgmt, Map<ConfigKey<?>, ?> config) {
        BrooklynProperties brooklynProperties = mgmt.getBrooklynProperties();
        String identity = (String) brooklynProperties.getConfig("brooklyn.location.jclouds.aws-ec2.identity");
        if (identity == null) identity = (String) brooklynProperties.getConfig("brooklyn.jclouds.aws-ec2.identity");
        String credential = (String) brooklynProperties.getConfig("brooklyn.location.jclouds.aws-ec2.credential");
        if (credential == null) credential = (String) brooklynProperties.getConfig("brooklyn.jclouds.aws-ec2.credential");

        Map<ConfigKey<?>, ?> allConfig = MutableMap.<ConfigKey<?>, Object>builder()
                .put(CLOUD_PROVIDER, AbstractJcloudsLiveTest.AWS_EC2_PROVIDER)
                .put(CLOUD_REGION_ID, AbstractJcloudsLiveTest.AWS_EC2_USEAST_REGION_NAME)
                .put(IMAGE_ID, US_EAST_IMAGE_ID) // so Brooklyn does not attempt to load all EC2 images
                .put(ACCESS_IDENTITY, identity)
                .put(ACCESS_CREDENTIAL, credential)
                .put(INBOUND_PORTS, "[22, 80, 9999]")
                .put(BUILD_TEMPLATE, true)
                .putAll(config)
                .build();

        return newBailOutJcloudsLocation(mgmt, allConfig);
    }

}
