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

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyBoolean;
import static org.mockito.Matchers.anyString;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.springframework.data.gemfire.util.CacheUtils.toRegionPath;
import static org.springframework.data.gemfire.util.CollectionUtils.nullSafeList;

import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.apache.geode.cache.Cache;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.DiskStore;
import org.apache.geode.cache.FixedPartitionAttributes;
import org.apache.geode.cache.GemFireCache;
import org.apache.geode.cache.PartitionAttributes;
import org.apache.geode.cache.PartitionResolver;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.RegionAttributes;
import org.apache.geode.cache.RegionExistsException;
import org.apache.geode.cache.RegionFactory;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.client.ClientCache;
import org.apache.geode.cache.client.ClientRegionFactory;
import org.apache.geode.cache.client.ClientRegionShortcut;
import org.junit.After;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.Lazy;
import org.springframework.data.gemfire.PartitionedRegionFactoryBean;
import org.springframework.data.gemfire.ReplicatedRegionFactoryBean;
import org.springframework.data.gemfire.client.ClientRegionShortcutWrapper;
import org.springframework.data.gemfire.config.annotation.test.entities.ClientRegionEntity;
import org.springframework.data.gemfire.config.annotation.test.entities.CollocatedPartitionRegionEntity;
import org.springframework.data.gemfire.config.annotation.test.entities.GenericRegionEntity;
import org.springframework.data.gemfire.config.annotation.test.entities.LocalRegionEntity;
import org.springframework.data.gemfire.config.annotation.test.entities.NonEntity;
import org.springframework.data.gemfire.config.annotation.test.entities.PartitionRegionEntity;
import org.springframework.data.gemfire.config.annotation.test.entities.ReplicateRegionEntity;
import org.springframework.data.gemfire.config.xml.GemfireConstants;
import org.springframework.data.gemfire.mapping.annotation.ClientRegion;
import org.springframework.data.gemfire.mapping.annotation.LocalRegion;
import org.springframework.data.gemfire.mapping.annotation.PartitionRegion;
import org.springframework.data.gemfire.mapping.annotation.ReplicateRegion;

/**
 * Unit tests for the {@link EnableEntityDefinedRegions} annotation and {@link EntityDefinedRegionsConfiguration} class.
 *
 * @author John Blum
 * @see org.junit.Test
 * @see org.mockito.Mockito
 * @see org.springframework.data.gemfire.config.annotation.EnableEntityDefinedRegions
 * @see org.springframework.data.gemfire.config.annotation.EntityDefinedRegionsConfiguration
 * @since 1.9.0
 */
public class EnableEntityDefinedRegionsConfigurationUnitTests {

	private static final AtomicInteger MOCK_ID = new AtomicInteger(0);

	private static final Set<Region<?, ?>> cacheRegions = new HashSet<>();

	private ConfigurableApplicationContext applicationContext;

	@After
	public void tearDown() {
		Optional.ofNullable(this.applicationContext).ifPresent(ConfigurableApplicationContext::close);
		cacheRegions.clear();
	}

	/* (non-Javadoc) */
	protected void assertRegion(Region<?, ?> region, String name) {
		assertRegion(region, name, toRegionPath(name), null, null);
	}

	/* (non-Javadoc) */
	protected <K, V> void assertRegion(Region<?, ?> region, String name,
			Class<K> keyConstraint, Class<V> valueConstraint) {

		assertRegion(region, name, toRegionPath(name), keyConstraint, valueConstraint);
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unused")
	protected void assertRegion(Region<?, ?> region, String name, String fullPath) {
		assertRegion(region, name, fullPath, null, null);
	}

	/* (non-Javadoc) */
	protected <K, V> void assertRegion(Region<?, ?> region, String name, String fullPath,
			Class<K> keyConstraint, Class<V> valueConstraint) {

		assertThat(region).isNotNull();
		assertThat(region.getName()).isEqualTo(name);
		assertThat(region.getFullPath()).isEqualTo(fullPath);
		assertThat(region.getAttributes()).isNotNull();
		assertThat(region.getAttributes().getKeyConstraint()).isEqualTo(keyConstraint);
		assertThat(region.getAttributes().getValueConstraint()).isEqualTo(valueConstraint);
	}

	/* (non-Javadoc) */
	protected void assertRegionAttributes(RegionAttributes<?, ?> regionAttributes, DataPolicy dataPolicy,
			String diskStoreName, Boolean diskSynchronous, Boolean ignoreJta, String poolName, Scope scope) {

		assertThat(regionAttributes).isNotNull();
		assertThat(regionAttributes.getDataPolicy()).isEqualTo(dataPolicy);
		assertThat(regionAttributes.getDiskStoreName()).isEqualTo(diskStoreName);
		assertThat(regionAttributes.isDiskSynchronous()).isEqualTo(diskSynchronous);
		assertThat(regionAttributes.getIgnoreJTA()).isEqualTo(ignoreJta);
		assertThat(regionAttributes.getPoolName()).isEqualToIgnoringCase(poolName);
		assertThat(regionAttributes.getScope()).isEqualTo(scope);
	}

	/* (non-Javadoc) */
	protected void assertPartitionAttributes(PartitionAttributes<?, ?> partitionAttributes,
			String collocatedWith, PartitionResolver partitionResolver, Integer redundantCopies) {

		assertThat(partitionAttributes).isNotNull();
		assertThat(partitionAttributes.getColocatedWith()).isEqualTo(collocatedWith);
		assertThat(partitionAttributes.getPartitionResolver()).isEqualTo(partitionResolver);
		assertThat(partitionAttributes.getRedundantCopies()).isEqualTo(redundantCopies);
	}

	/* (non-Javadoc) */
	protected void assertFixedPartitionAttributes(FixedPartitionAttributes fixedPartitionAttributes,
			String partitionName, boolean primary, int numBuckets) {

		assertThat(fixedPartitionAttributes).isNotNull();
		assertThat(fixedPartitionAttributes.getPartitionName()).isEqualTo(partitionName);
		assertThat(fixedPartitionAttributes.isPrimary()).isEqualTo(primary);
		assertThat(fixedPartitionAttributes.getNumBuckets()).isEqualTo(numBuckets);
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	protected FixedPartitionAttributes findFixedPartitionAttributes(PartitionAttributes partitionAttributes,
			String partitionName) {

		assertThat(partitionAttributes).isNotNull();

		List<FixedPartitionAttributes> fixedPartitionAttributes =
			nullSafeList(partitionAttributes.getFixedPartitionAttributes());

		for (FixedPartitionAttributes attributes : fixedPartitionAttributes) {
			if (attributes.getPartitionName().equals(partitionName)) {
				return attributes;
			}
		}

		return null;
	}

	/* (non-Javadoc) */
	protected ConfigurableApplicationContext newApplicationContext(Class<?>... annotatedClasses) {
		ConfigurableApplicationContext applicationContext = new AnnotationConfigApplicationContext(annotatedClasses);
		applicationContext.registerShutdownHook();
		return applicationContext;
	}

	@Test
	@SuppressWarnings("unchecked")
	public void entityClientRegionsDefined() {
		applicationContext = newApplicationContext(ClientPersistentEntitiesConfiguration.class);

		Region<String, ClientRegionEntity> sessions = applicationContext.getBean("Sessions", Region.class);

		assertRegion(sessions, "Sessions", String.class, ClientRegionEntity.class);
		assertRegionAttributes(sessions.getAttributes(), DataPolicy.NORMAL, null, true,
			false, GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME, null);

		Region<Long, GenericRegionEntity> genericRegionEntity =
			applicationContext.getBean("GenericRegionEntity", Region.class);

		assertRegion(genericRegionEntity, "GenericRegionEntity", Long.class, GenericRegionEntity.class);
		assertRegionAttributes(genericRegionEntity.getAttributes(), DataPolicy.EMPTY, null,
			true, false, GemfireConstants.DEFAULT_GEMFIRE_POOL_NAME, null);

		assertThat(applicationContext.containsBean("CollocatedPartitionRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("ContactEvents")).isFalse();
		assertThat(applicationContext.containsBean("NonEntity")).isFalse();
		assertThat(applicationContext.containsBean("Accounts")).isFalse();
		assertThat(applicationContext.containsBean("Customers")).isFalse();
		assertThat(applicationContext.containsBean("LocalRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("PartitionRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("ReplicateRegionEntity")).isFalse();
	}

	@Test
	@SuppressWarnings("unchecked")
	public void entityPeerPartitionRegionsDefined() {
		applicationContext = newApplicationContext(PeerPartitionRegionPersistentEntitiesConfiguration.class);

		Region<Object, Object> customers = applicationContext.getBean("Customers", Region.class);

		assertRegion(customers, "Customers");
		assertRegionAttributes(customers.getAttributes(), DataPolicy.PERSISTENT_PARTITION, null,
			true, false, null, Scope.DISTRIBUTED_NO_ACK);
		assertPartitionAttributes(customers.getAttributes().getPartitionAttributes(), null,
			null, 1);
		assertFixedPartitionAttributes(findFixedPartitionAttributes(customers.getAttributes().getPartitionAttributes(),
			"one"), "one", true, 16);
		assertFixedPartitionAttributes(findFixedPartitionAttributes(customers.getAttributes().getPartitionAttributes(),
			"two"), "two", false, 21);

		Region<Object, Object> contactEvents = applicationContext.getBean("ContactEvents", Region.class);

		assertRegion(contactEvents, "ContactEvents");
		assertRegionAttributes(contactEvents.getAttributes(), DataPolicy.PERSISTENT_PARTITION,
			"mockDiskStore", false, true, null, Scope.DISTRIBUTED_NO_ACK);
		assertPartitionAttributes(contactEvents.getAttributes().getPartitionAttributes(), "Customers",
			applicationContext.getBean("mockPartitionResolver", PartitionResolver.class), 2);

		assertThat(applicationContext.getBean("mockDiskStore")).isInstanceOf(DiskStore.class);
		assertThat(applicationContext.containsBean("ClientRegion")).isFalse();
		assertThat(applicationContext.containsBean("NonEntity")).isFalse();
		assertThat(applicationContext.containsBean("Accounts")).isFalse();
		assertThat(applicationContext.containsBean("LocalRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("PartitionRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("ReplicateRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("Sessions")).isFalse();
	}

	@Test(expected = RegionExistsException.class)
	public void entityPeerPartitionRegionAlreadyDefinedThrowsRegionExistsException() {
		try {
			applicationContext = newApplicationContext(ExistingPartitionRegionPersistentEntitiesConfiguration.class);
		}
		catch (BeanCreationException expected) {
			assertThat(expected).hasCauseInstanceOf(RegionExistsException.class);
			assertThat(expected.getCause()).hasMessage("/Customers");

			throw (RegionExistsException) expected.getCause();
		}
	}

	@Test
	@SuppressWarnings("unchecked")
	public void entityReplicateRegionAlreadyDefinedIgnoresEntityDefinedRegionDefinition() {
		applicationContext = newApplicationContext(ExistingReplicateRegionPersistentEntitiesConfiguration.class);

		Region<Object, Object> accounts = applicationContext.getBean("Accounts", Region.class);

		assertRegion(accounts, "Accounts");
		assertRegionAttributes(accounts.getAttributes(), DataPolicy.REPLICATE, null, true,
			false, null, Scope.DISTRIBUTED_NO_ACK);
	}

	@Test
	@SuppressWarnings("unchecked")
	public void entityServerRegionsDefined() {
		applicationContext = newApplicationContext(ServerPersistentEntitiesConfiguration.class);

		Region<Object, Object> accounts = applicationContext.getBean("Accounts", Region.class);

		assertRegion(accounts, "Accounts");
		assertRegionAttributes(accounts.getAttributes(), DataPolicy.REPLICATE, null, true,
			false, null, Scope.DISTRIBUTED_ACK);

		Region<Object, Object> customers = applicationContext.getBean("Customers", Region.class);

		assertRegion(customers, "Customers");
		assertRegionAttributes(customers.getAttributes(), DataPolicy.PERSISTENT_PARTITION,
			null, true, false, null, Scope.DISTRIBUTED_NO_ACK);
		assertPartitionAttributes(customers.getAttributes().getPartitionAttributes(), null,
			null, 1);

		Region<Object, Object> localRegionEntity = applicationContext.getBean("LocalRegionEntity", Region.class);

		assertRegion(localRegionEntity, "LocalRegionEntity");
		assertRegionAttributes(localRegionEntity.getAttributes(), DataPolicy.NORMAL,
			null, true, false, null, Scope.LOCAL);

		Region<Object, Object> genericRegionEntity =
			applicationContext.getBean("GenericRegionEntity", Region.class);

		assertRegion(genericRegionEntity, "GenericRegionEntity");
		assertRegionAttributes(genericRegionEntity.getAttributes(), DataPolicy.NORMAL,
			null, true, false, null, Scope.DISTRIBUTED_NO_ACK);

		assertThat(applicationContext.containsBean("CollocatedPartitionRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("ContactEvents")).isFalse();
		assertThat(applicationContext.containsBean("NonEntity")).isFalse();
		assertThat(applicationContext.containsBean("PartitionRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("ReplicateRegionEntity")).isFalse();
		assertThat(applicationContext.containsBean("Sessions")).isFalse();
	}

	/* (non-Javadoc) */
	protected static String mockName(String baseMockName) {
		return String.format("%s%d", baseMockName, MOCK_ID.incrementAndGet());
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	protected static <K, V> Cache mockCache() {
		Cache mockCache = mock(Cache.class);

		Answer<RegionFactory<K, V>> createRegionFactory = invocation -> {
			RegionAttributes<K, V> defaultRegionAttributes = mockRegionAttributes(null,
				null, true, false, null, null,
					null, Scope.DISTRIBUTED_NO_ACK, null);

				RegionAttributes<K, V> regionAttributes = (invocation.getArguments().length == 1
					? invocation.getArgument(0) : defaultRegionAttributes);

			return mockRegionFactory(mockCache, regionAttributes);
		};

		when(mockCache.createRegionFactory()).thenAnswer(createRegionFactory);
		when(mockCache.createRegionFactory(any(RegionAttributes.class))).thenAnswer(createRegionFactory);

		return mockCache;
	}

	protected static <K, V> ClientCache mockClientCache() {
		ClientCache mockClientCache = mock(ClientCache.class, mockName("ClientCache"));

		Answer<ClientRegionFactory<K, V>> createClientRegionFactory = new Answer<ClientRegionFactory<K, V>>() {
			@Override @SuppressWarnings("unchecked")
			public ClientRegionFactory<K, V> answer(InvocationOnMock invocation) throws Throwable {
				return mockClientRegionFactory(invocation.getArgument(0));
			}
		};

		when(mockClientCache.createClientRegionFactory(any(ClientRegionShortcut.class)))
			.thenAnswer(createClientRegionFactory);

		return mockClientCache;
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	protected static <K, V> ClientRegionFactory<K, V> mockClientRegionFactory(ClientRegionShortcut shortcut) {
		ClientRegionFactory<K, V> mockClientRegionFactory =
			mock(ClientRegionFactory.class, mockName("MockClientRegionFactory"));

		AtomicReference<String> diskStoreName = new AtomicReference<>();
		AtomicReference<Boolean> diskSynchronous = new AtomicReference<>(true);
		AtomicReference<Class> keyConstraint = new AtomicReference<>(null);
		AtomicReference<String> poolName = new AtomicReference<>();
		AtomicReference<Class> valueConstraint = new AtomicReference<>(null);

		when(mockClientRegionFactory.setDiskStoreName(anyString())).thenAnswer(
			newSetter(String.class, diskStoreName, mockClientRegionFactory));

		when(mockClientRegionFactory.setDiskSynchronous(anyBoolean())).thenAnswer(
			newSetter(Boolean.TYPE, diskSynchronous, mockClientRegionFactory));

		when(mockClientRegionFactory.setKeyConstraint(any(Class.class))).thenAnswer(
			newSetter(Class.class, keyConstraint, mockClientRegionFactory));

		when(mockClientRegionFactory.setPoolName(anyString())).thenAnswer(
			newSetter(String.class, poolName, mockClientRegionFactory));

		when(mockClientRegionFactory.setValueConstraint(any(Class.class))).thenAnswer(
			newSetter(Class.class, valueConstraint, mockClientRegionFactory));

		RegionAttributes<K, V> mockRegionAttributes =
			mock(RegionAttributes.class, mockName("MockClientRegionAttributes"));

		when(mockRegionAttributes.getDataPolicy()).thenReturn(
			ClientRegionShortcutWrapper.valueOf(shortcut).getDataPolicy());
		when(mockRegionAttributes.getDiskStoreName()).thenAnswer(newGetter(diskStoreName));
		when(mockRegionAttributes.isDiskSynchronous()).thenAnswer(newGetter(diskSynchronous));
		when(mockRegionAttributes.getKeyConstraint()).thenAnswer(newGetter(keyConstraint));
		when(mockRegionAttributes.getPoolName()).thenAnswer(newGetter(poolName));
		when(mockRegionAttributes.getValueConstraint()).thenAnswer(newGetter(valueConstraint));

		when(mockClientRegionFactory.create(anyString())).thenAnswer(invocation -> {
			String regionName = invocation.getArgument(0);

			cacheRegions.stream().filter(region -> region.getName().equals(regionName)).findAny()
				.ifPresent(region -> { throw new RegionExistsException(region); });

			Region<K, V> region = mockRegion(regionName, mockRegionAttributes);

			cacheRegions.add(region);

			return region;
		});

		return mockClientRegionFactory;
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	protected static <K, V> RegionAttributes<K, V> mockRegionAttributes(DataPolicy dataPolicy,
			String diskStoreName, boolean diskSynchronous, boolean ignoreJta, Class<K> keyConstraint,
			PartitionAttributes<K, V> partitionAttributes, String poolName, Scope scope, Class<V> valueConstraint) {

		RegionAttributes<K, V> mockRegionAttributes =
			mock(RegionAttributes.class, mockName("MockRegionAttributes"));

		when(mockRegionAttributes.getDataPolicy()).thenReturn(dataPolicy);
		when(mockRegionAttributes.getDiskStoreName()).thenReturn(diskStoreName);
		when(mockRegionAttributes.isDiskSynchronous()).thenReturn(diskSynchronous);
		when(mockRegionAttributes.getIgnoreJTA()).thenReturn(ignoreJta);
		when(mockRegionAttributes.getKeyConstraint()).thenReturn(keyConstraint);
		when(mockRegionAttributes.getPartitionAttributes()).thenReturn(partitionAttributes);
		when(mockRegionAttributes.getPoolName()).thenReturn(poolName);
		when(mockRegionAttributes.getScope()).thenReturn(scope);
		when(mockRegionAttributes.getValueConstraint()).thenReturn(valueConstraint);

		return mockRegionAttributes;
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	protected static <K, V> RegionFactory<K, V> mockRegionFactory(GemFireCache mockCache,
			RegionAttributes<K, V> regionAttributes) {

		RegionFactory<K, V> mockRegionFactory = mock(RegionFactory.class, mockName("MockRegionFactory"));

		AtomicReference<DataPolicy> dataPolicy = new AtomicReference<>(regionAttributes.getDataPolicy());
		AtomicReference<String> diskStoreName = new AtomicReference<>(regionAttributes.getDiskStoreName());
		AtomicReference<Boolean> diskSynchronous = new AtomicReference<>(regionAttributes.isDiskSynchronous());
		AtomicReference<Boolean> ignoreJta = new AtomicReference<>(regionAttributes.getIgnoreJTA());
		AtomicReference<Class> keyConstraint = new AtomicReference<>(null);
		AtomicReference<PartitionAttributes> partitionAttributes =
			new AtomicReference<>(regionAttributes.getPartitionAttributes());
		AtomicReference<Scope> scope = new AtomicReference<>(regionAttributes.getScope());
		AtomicReference<Class> valueConstraint = new AtomicReference<>(null);

		when(mockRegionFactory.setDataPolicy(any(DataPolicy.class))).thenAnswer(
			newSetter(DataPolicy.class, dataPolicy, mockRegionFactory));

		when(mockRegionFactory.setDiskStoreName(anyString())).thenAnswer(
			newSetter(String.class, diskStoreName, mockRegionFactory));

		when(mockRegionFactory.setDiskSynchronous(anyBoolean())).thenAnswer(
			newSetter(Boolean.TYPE, diskSynchronous, mockRegionFactory));

		when(mockRegionFactory.setIgnoreJTA(anyBoolean())).thenAnswer(
			newSetter(Boolean.TYPE, ignoreJta, mockRegionFactory));

		when(mockRegionFactory.setKeyConstraint(any(Class.class))).thenAnswer(
			newSetter(Class.class, keyConstraint, mockRegionFactory));

		when(mockRegionFactory.setPartitionAttributes(any(PartitionAttributes.class))).thenAnswer(
			newSetter(PartitionAttributes.class, partitionAttributes, mockRegionFactory));

		when(mockRegionFactory.setScope(any(Scope.class))).thenAnswer(
			newSetter(Scope.class, scope, mockRegionFactory));

		when(mockRegionFactory.setValueConstraint(any(Class.class))).thenAnswer(
			newSetter(Class.class, valueConstraint, mockRegionFactory));

		RegionAttributes<K, V> mockRegionAttributes =
			mock(RegionAttributes.class, mockName("MockRegionAttributes"));

		when(mockRegionAttributes.getDataPolicy()).thenAnswer(newGetter(dataPolicy));
		when(mockRegionAttributes.getDiskStoreName()).thenAnswer(newGetter(diskStoreName));
		when(mockRegionAttributes.isDiskSynchronous()).thenAnswer(newGetter(diskSynchronous));
		when(mockRegionAttributes.getIgnoreJTA()).thenAnswer(newGetter(ignoreJta));
		when(mockRegionAttributes.getKeyConstraint()).thenAnswer(newGetter(keyConstraint));
		when(mockRegionAttributes.getPartitionAttributes()).thenAnswer(newGetter(partitionAttributes));
		when(mockRegionAttributes.getScope()).thenAnswer(newGetter(scope));
		when(mockRegionAttributes.getValueConstraint()).thenAnswer(newGetter(valueConstraint));

		when(mockRegionFactory.create(anyString())).thenAnswer(invocation -> {
			String regionName = invocation.getArgument(0);

			cacheRegions.stream().filter(region -> region.getName().equals(regionName)).findAny()
				.ifPresent(region -> { throw new RegionExistsException(region); });

			Region<K, V> mockRegion = mockRegion(regionName, mockRegionAttributes);

			cacheRegions.add(mockRegion);

			when(mockCache.getRegion(eq(regionName))).thenReturn((Region<Object, Object>) mockRegion);
			when(mockRegion.getRegionService()).thenReturn(mockCache);

			return mockRegion;
		});

		return mockRegionFactory;
	}

	/* (non-Javadoc) */
	@SuppressWarnings("unchecked")
	protected static <K, V> Region<K, V> mockRegion(String name, RegionAttributes<K, V> regionAttributes) {
		Region<K, V> mockRegion = mock(Region.class, mockName(name));

		when(mockRegion.getName()).thenReturn(name);
		when(mockRegion.getFullPath()).thenReturn(toRegionPath(name));
		when(mockRegion.getAttributes()).thenReturn(regionAttributes);

		return mockRegion;
	}

	/* (non-Javadoc) */
	protected static <R> Answer<R> newGetter(AtomicReference<R> returnValue) {
		return invocation -> returnValue.get();
	}

	/* (non-Javadoc) */
	protected static <T, R> Answer<R> newSetter(Class<T> parameterType, AtomicReference<T> argument, R returnValue) {
		return invocation -> {
			argument.set(invocation.getArgument(0));
			return returnValue;
		};
	}

	@Configuration
	@SuppressWarnings("unused")
	static abstract class ClientCacheConfiguration {

		@Bean
		ClientCache gemfireCache() {
			return mockClientCache();
		}
	}

	@Configuration
	@SuppressWarnings("unused")
	static abstract class ServerCacheConfiguration {

		@Bean
		Cache gemfireCache() {
			return mockCache();
		}
	}

	@SuppressWarnings("all")
	@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class, strict = true,
		excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
			classes = { LocalRegion.class, PartitionRegion.class, ReplicateRegion.class }))
	static class ClientPersistentEntitiesConfiguration extends ClientCacheConfiguration {
	}

	@SuppressWarnings("all")
	@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
		excludeFilters = @ComponentScan.Filter(type = FilterType.ANNOTATION,
			classes = { ClientRegion.class, LocalRegion.class, ReplicateRegion.class }))
	static class PeerPartitionRegionPersistentEntitiesConfiguration extends ServerCacheConfiguration {

		@Bean @Lazy
		DiskStore mockDiskStore() {
			return mock(DiskStore.class, mockName("MockDiskStore"));
		}

		@Bean @Lazy
		PartitionResolver mockPartitionResolver() {
			return mock(PartitionResolver.class, mockName("MockPartitionResolver"));
		}
	}

	@SuppressWarnings("all")
	@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
		excludeFilters = { @ComponentScan.Filter(type = FilterType.ANNOTATION, classes = ClientRegion.class),
			@ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = CollocatedPartitionRegionEntity.class) })
	static class ServerPersistentEntitiesConfiguration extends ServerCacheConfiguration {
	}

	@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
		excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
			ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
			LocalRegionEntity.class, ReplicateRegionEntity.class
		})
	)
	static class ExistingPartitionRegionPersistentEntitiesConfiguration extends ServerCacheConfiguration {

		@Bean
		@SuppressWarnings("unused")
		PartitionedRegionFactoryBean<Long, PartitionRegionEntity> customersRegion(GemFireCache gemfireCache) {
			PartitionedRegionFactoryBean<Long, PartitionRegionEntity> customers = new PartitionedRegionFactoryBean<>();

			customers.setCache(gemfireCache);
			customers.setClose(false);
			customers.setPersistent(false);
			customers.setRegionName("Customers");

			return customers;
		}
	}

	@SuppressWarnings("all")
	@EnableEntityDefinedRegions(basePackageClasses = NonEntity.class,
		excludeFilters = @ComponentScan.Filter(type = FilterType.ASSIGNABLE_TYPE, classes = {
			ClientRegionEntity.class, CollocatedPartitionRegionEntity.class, GenericRegionEntity.class,
			LocalRegionEntity.class, PartitionRegionEntity.class
		})
	)
	static class ExistingReplicateRegionPersistentEntitiesConfiguration extends ServerCacheConfiguration {

		@Bean
		@SuppressWarnings("unused")
		ReplicatedRegionFactoryBean<Long, ReplicateRegionEntity> accountsRegion(GemFireCache gemfireCache) {
			ReplicatedRegionFactoryBean<Long, ReplicateRegionEntity> accounts = new ReplicatedRegionFactoryBean<>();

			accounts.setCache(gemfireCache);
			accounts.setClose(false);
			accounts.setLookupEnabled(true);
			accounts.setPersistent(false);
			accounts.setRegionName("Accounts");
			accounts.setScope(Scope.DISTRIBUTED_NO_ACK);

			return accounts;
		}
	}
}
