/*
 * Copyright 2012 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.data.gemfire.config.annotation;

import java.util.Map;

import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.annotation.AnnotationAttributes;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.data.gemfire.GemfireUtils;
import org.springframework.data.gemfire.client.PoolFactoryBean;
import org.springframework.data.gemfire.support.ConnectionEndpoint;
import org.springframework.data.gemfire.support.ConnectionEndpointList;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

/**
 * The {@link AddCacheServerConfiguration} class is a Spring {@link ImportBeanDefinitionRegistrar} that registers
 * a {@link PoolFactoryBean} definition for the {@link com.gemstone.gemfire.cache.client.Pool}
 * configuration meta-data defined in {@link EnablePool}.
 *
 * @author John Blum
 * @see org.springframework.context.annotation.ImportBeanDefinitionRegistrar
 * @see org.springframework.data.gemfire.config.annotation.EnablePool
 * @since 1.9.0
 */
public class AddPoolConfiguration implements ImportBeanDefinitionRegistrar {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
		if (importingClassMetadata.hasAnnotation(EnablePool.class.getName())) {
			Map<String, Object> enablePoolAttributes = importingClassMetadata.getAnnotationAttributes(
				EnablePool.class.getName());

			registerPoolFactoryBeanDefinition(enablePoolAttributes, registry);
		}
	}

	/**
	 * Registers a {@link PoolFactoryBean} definition in the Spring application context configured with
	 * the {@link EnablePool} annotation meta-data.
	 *
	 * @param enablePoolAttributes {@link EnablePool} annotation attributes.
	 * @param registry Spring {@link BeanDefinitionRegistry used to register the {@link PoolFactoryBean} definition.
	 * @see org.springframework.beans.factory.support.BeanDefinitionRegistry
	 * @see org.springframework.data.gemfire.client.PoolFactoryBean
	 * @see org.springframework.data.gemfire.config.annotation.EnablePool
	 * @see java.util.Map
	 */
	protected void registerPoolFactoryBeanDefinition(Map<String, Object> enablePoolAttributes,
			BeanDefinitionRegistry registry) {

		String poolName = getAndValidatePoolName(enablePoolAttributes);

		BeanDefinitionBuilder poolFactoryBean = BeanDefinitionBuilder.genericBeanDefinition(PoolFactoryBean.class);

		poolFactoryBean.addPropertyValue("freeConnectionTimeout", enablePoolAttributes.get("freeConnectionTimeout"));
		poolFactoryBean.addPropertyValue("idleTimeout", enablePoolAttributes.get("idleTimeout"));
		poolFactoryBean.addPropertyValue("loadConditioningInterval", enablePoolAttributes.get("loadConditioningInterval"));
		poolFactoryBean.addPropertyValue("maxConnections", enablePoolAttributes.get("maxConnections"));
		poolFactoryBean.addPropertyValue("minConnections", enablePoolAttributes.get("minConnections"));
		poolFactoryBean.addPropertyValue("multiUserAuthentication", enablePoolAttributes.get("multiUserAuthentication"));
		poolFactoryBean.addPropertyValue("pingInterval", enablePoolAttributes.get("pingInterval"));
		poolFactoryBean.addPropertyValue("prSingleHopEnabled", enablePoolAttributes.get("prSingleHopEnabled"));
		poolFactoryBean.addPropertyValue("readTimeout", enablePoolAttributes.get("readTimeout"));
		poolFactoryBean.addPropertyValue("retryAttempts", enablePoolAttributes.get("retryAttempts"));
		poolFactoryBean.addPropertyValue("serverGroup", enablePoolAttributes.get("serverGroup"));
		poolFactoryBean.addPropertyValue("socketBufferSize", enablePoolAttributes.get("socketBufferSize"));
		poolFactoryBean.addPropertyValue("statisticInterval", enablePoolAttributes.get("statisticInterval"));
		poolFactoryBean.addPropertyValue("subscriptionAckInterval", enablePoolAttributes.get("subscriptionAckInterval"));
		poolFactoryBean.addPropertyValue("subscriptionEnabled", enablePoolAttributes.get("subscriptionEnabled"));
		poolFactoryBean.addPropertyValue("subscriptionMessageTrackingTimeout", enablePoolAttributes.get("subscriptionMessageTrackingTimeout"));
		poolFactoryBean.addPropertyValue("subscriptionRedundancy", enablePoolAttributes.get("subscriptionRedundancy"));
		poolFactoryBean.addPropertyValue("threadLocalConnections", enablePoolAttributes.get("threadLocalConnections"));

		configurePoolConnections(enablePoolAttributes, poolFactoryBean);

		registry.registerBeanDefinition(poolName, poolFactoryBean.getBeanDefinition());
	}

	protected String getAndValidatePoolName(Map<String, Object> enablePoolAttributes) {
		String poolName = (String) enablePoolAttributes.get("name");
		Assert.hasText(poolName, "Pool name must be specified");
		return poolName;
	}

	/**
	 * Uses the list of GemFire Locator and Server connection endpoint definitions and meta-data to configure
	 * the GemFire client {@link com.gemstone.gemfire.cache.client.Pool} used to communicate with the servers
	 * in the GemFire cluster.
	 *
	 * @param enablePoolAttributes {@link EnablePool} annotation containing
	 * {@link com.gemstone.gemfire.cache.client.Pool} Locator/Server connection endpoint meta-data.
	 * @see org.springframework.data.gemfire.config.annotation.ClientCacheApplication
	 * @see java.util.Map
	 */
	protected BeanDefinitionBuilder configurePoolConnections(Map<String, Object> enablePoolAttributes,
			BeanDefinitionBuilder poolFactoryBean) {

		configureLocators(enablePoolAttributes, poolFactoryBean);
		configureServers(enablePoolAttributes, poolFactoryBean);

		return poolFactoryBean;
	}

	protected BeanDefinitionBuilder configureLocators(Map<String, Object> enablePoolAttributes,
			BeanDefinitionBuilder poolFactoryBean) {

		poolFactoryBean.addPropertyValue("locators", parseConnectionEndpoints(enablePoolAttributes,
			"locators", "locatorsString", GemfireUtils.DEFAULT_LOCATOR_PORT));

		return poolFactoryBean;
	}

	protected BeanDefinitionBuilder configureServers(Map<String, Object> enablePoolAttributes,
			BeanDefinitionBuilder poolFactoryBean) {

		poolFactoryBean.addPropertyValue("servers", parseConnectionEndpoints(enablePoolAttributes,
			"servers", "serversString", GemfireUtils.DEFAULT_CACHE_SERVER_PORT));

		return poolFactoryBean;
	}

	protected ConnectionEndpointList parseConnectionEndpoints(Map<String, Object> enablePoolAttributes,
			String arrayAttributeName, String stringAttributeName, int defaultPort) {

		ConnectionEndpointList connectionEndpoints = new ConnectionEndpointList();

		AnnotationAttributes[] connectionEndpointsMetaData =
			(AnnotationAttributes[]) enablePoolAttributes.get(arrayAttributeName);

		for (AnnotationAttributes annotationAttributes : connectionEndpointsMetaData) {
			connectionEndpoints.add(newConnectionEndpoint((String) annotationAttributes.get("host"),
				(Integer) annotationAttributes.get("port")));
		}

		String hostsPorts = (String) enablePoolAttributes.get(stringAttributeName);

		if (StringUtils.hasText(hostsPorts)) {
			connectionEndpoints.add(ConnectionEndpointList.parse(defaultPort, hostsPorts.split(",")));
		}

		return connectionEndpoints;
	}

	protected ConnectionEndpoint newConnectionEndpoint(String host, Integer port) {
		return new ConnectionEndpoint(host, port);
	}
}
