/*
 * Copyright 2013-2016 the original author or authors.
 *
 * 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.springframework.cloud.netflix.turbine;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import com.netflix.appinfo.AmazonInfo;
import com.netflix.appinfo.DataCenterInfo;
import com.netflix.appinfo.InstanceInfo;
import com.netflix.appinfo.InstanceInfo.InstanceStatus;
import com.netflix.discovery.EurekaClient;
import com.netflix.discovery.shared.Application;
import com.netflix.turbine.discovery.Instance;

import lombok.extern.apachecommons.CommonsLog;

/**
 * Class that encapsulates an {@link com.netflix.turbine.discovery.InstanceDiscovery}
 * implementation that uses Eureka (see https://github.com/Netflix/eureka) The plugin
 * requires a list of applications configured. It then queries the set of instances for
 * each application. Instance information retrieved from Eureka must be translated to
 * something that Turbine can understand i.e the
 * {@link com.netflix.turbine.discovery.Instance} class.
 * <p>
 * All the logic to perform this translation can be overriden here, so that you can
 * provide your own implementation if needed.
 *
 * @author Spencer Gibb
 */
@CommonsLog
public class EurekaInstanceDiscovery extends CommonsInstanceDiscovery {

	private static final String EUREKA_DEFAULT_CLUSTER_NAME_EXPRESSION = "appName";
	private static final String ASG_KEY = "asg";

	private final EurekaClient eurekaClient;


	public EurekaInstanceDiscovery(TurbineProperties turbineProperties, EurekaClient eurekaClient) {
		super(turbineProperties, EUREKA_DEFAULT_CLUSTER_NAME_EXPRESSION);
		this.eurekaClient = eurekaClient;
	}

	/**
	 * Private helper that fetches the Instances for each application.
	 * @param serviceId
	 * @return List<Instance>
	 * @throws Exception
	 */
	@Override
	protected List<Instance> getInstancesForApp(String serviceId) throws Exception {
		List<Instance> instances = new ArrayList<>();
		log.info("Fetching instances for app: " + serviceId);
		Application app = eurekaClient.getApplication(serviceId);
		if (app == null) {
			log.warn("Eureka returned null for app: " + serviceId);
			return instances;
		}
		try {
			List<InstanceInfo> instancesForApp = app.getInstances();
			if (instancesForApp != null) {
				log.info("Received instance list for app: " + serviceId + ", size="
						+ instancesForApp.size());
				for (InstanceInfo iInfo : instancesForApp) {
					Instance instance = marshall(iInfo);
					if (instance != null) {
						instances.add(instance);
					}
				}
			}
		}
		catch (Exception e) {
			log.warn("Failed to retrieve instances from Eureka", e);
		}
		return instances;
	}

	/**
	 * Private helper that marshals the information from each instance into something that
	 * Turbine can understand. Override this method for your own implementation for
	 * parsing Eureka info.
	 * @param instanceInfo
	 * @return Instance
	 */
	Instance marshall(InstanceInfo instanceInfo) {
		String hostname = instanceInfo.getHostName();
		final String managementPort = instanceInfo.getMetadata().get("management.port");
		String port = managementPort == null ? String.valueOf(instanceInfo.getPort()) : managementPort;
		String cluster = getClusterName(instanceInfo);
		Boolean status = parseInstanceStatus(instanceInfo.getStatus());
		if (hostname != null && cluster != null && status != null) {
			Instance instance = getInstance(hostname, port, cluster, status);

			Map<String, String> metadata = instanceInfo.getMetadata();
			boolean securePortEnabled = instanceInfo.isPortEnabled(InstanceInfo.PortType.SECURE);
			String securePort = String.valueOf(instanceInfo.getSecurePort());

			addMetadata(instance, hostname, port, securePortEnabled, securePort, metadata);

			// add amazon metadata
			String asgName = instanceInfo.getASGName();
			if (asgName != null) {
				instance.getAttributes().put(ASG_KEY, asgName);
			}

			DataCenterInfo dcInfo = instanceInfo.getDataCenterInfo();
			if (dcInfo != null && dcInfo.getName().equals(DataCenterInfo.Name.Amazon)) {
				AmazonInfo amznInfo = (AmazonInfo) dcInfo;
				instance.getAttributes().putAll(amznInfo.getMetadata());
			}

			return instance;
		}
		else {
			return null;
		}
	}

	/**
	 * Helper that returns whether the instance is Up of Down
	 */
	protected Boolean parseInstanceStatus(InstanceStatus status) {
		if (status == null) {
			return null;
		}
		return status == InstanceStatus.UP;
	}


}
