/*
 * Copyright 2002-2013 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.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionFactory;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.apache.geode.cache.execute.Function;
import org.apache.geode.management.internal.cli.domain.RegionInformation;
import org.apache.geode.management.internal.cli.functions.GetRegionsFunction;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.data.gemfire.client.function.ListRegionsOnServerFunction;
import org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate;
import org.springframework.util.Assert;
import org.springframework.util.ObjectUtils;

/**
 * A Spring {@link BeanFactoryPostProcessor} used to register a Client Region Proxy bean for each Region
 * accessible to a GemFire DataSource. If the Region is already defined, the bean definition will not be overridden.
 *
 * @author David Turanski
 * @author John Blum
 * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor
 * @see org.springframework.beans.factory.config.ConfigurableListableBeanFactory
 * @see org.springframework.data.gemfire.function.execution.GemfireOnServersFunctionTemplate
 * @see ListRegionsOnServerFunction
 * @see org.apache.geode.cache.Region
 * @see org.apache.geode.cache.client.ClientCache
 * @see org.apache.geode.cache.client.ClientRegionFactory
 * @see org.apache.geode.cache.execute.Function
 * @see org.apache.geode.management.internal.cli.functions.GetRegionsFunction
 * @since 1.2.0
 */
public class GemfireDataSourcePostProcessor implements BeanFactoryPostProcessor {

	protected final Log logger = LogFactory.getLog(getClass());

	private final ClientCache clientCache;

	/**
	 * Constructs an instance of the GemfireDataSourcePostProcessor BeanFactoryPostProcessor class initialized
	 * with the specified GemFire ClientCache instance for creating client PROXY Regions for all data Regions
	 * configured in the GemFire cluster.
	 *
	 * @param clientCache the GemFire ClientCache instance.
	 * @see org.apache.geode.cache.client.ClientCache
	 */
	public GemfireDataSourcePostProcessor(final ClientCache clientCache) {
		this.clientCache = clientCache;
	}

	/*
	 * (non-Javadoc)
	 * @see org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory(org.springframework.beans.factory.config.ConfigurableListableBeanFactory)
	 */
	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
		createClientRegionProxies(beanFactory, regionNames());
	}

	/* (non-Javadoc) */
	// TODO remove this logic and delegate to o.s.d.g.config.remote.GemfireAdminOperations
	Iterable<String> regionNames() {
		try {
			return execute(new ListRegionsOnServerFunction());
		}
		catch (Exception ignore) {
			try {
				Object results = execute(new GetRegionsFunction());
				List<String> regionNames = Collections.emptyList();

				if (containsRegionInformation(results)) {
					Object[] resultsArray = (Object[]) results;

					regionNames = new ArrayList<String>(resultsArray.length);

					for (Object result : resultsArray) {
						regionNames.add(((RegionInformation) result).getName());
					}
				}

				return regionNames;
			}
			catch (Exception e) {
				log("Failed to determine the Regions available on the Server: %n%1$s", e);
				return Collections.emptyList();
			}
		}
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	<T> T execute(Function gemfireFunction, Object... arguments) {
		return new GemfireOnServersFunctionTemplate(clientCache).executeAndExtract(gemfireFunction, arguments);
	}

	/* (non-Javadoc) */
	boolean containsRegionInformation(Object results) {
		return (results instanceof Object[] && ((Object[]) results).length > 0
			&& ((Object[]) results)[0] instanceof RegionInformation);
	}

	/* (non-Javadoc) */
	void createClientRegionProxies(ConfigurableListableBeanFactory beanFactory, Iterable<String> regionNames) {
		if (regionNames.iterator().hasNext()) {
			ClientRegionFactory<?, ?> clientRegionFactory = clientCache.createClientRegionFactory(ClientRegionShortcut.PROXY);

			for (String regionName : regionNames) {
				boolean createRegion = true;

				if (beanFactory.containsBean(regionName)) {
					Object existingBean = beanFactory.getBean(regionName);

					Assert.isTrue(existingBean instanceof Region, String.format(
						"Cannot create a client PROXY Region bean named '%1$s'. A bean with this name of type '%2$s' already exists.",
							regionName, ObjectUtils.nullSafeClassName(existingBean)));

					createRegion = false;
				}

				if (createRegion) {
					log("Creating Region bean with name '%s'...", regionName);
					beanFactory.registerSingleton(regionName, clientRegionFactory.create(regionName));
				}
				else {
					log("A Region with name '%s' is already defined.", regionName);
				}
			}
		}
	}

	/* (non-Javadoc) */
	void log(String message, Object... arguments) {
		if (logger.isDebugEnabled()) {
			logger.debug(String.format(message, arguments));
		}
	}

}
