/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License, Version 1.0 only
 * (the "License").  You may not use this file except in compliance
 * with the License.
 *
 * You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
 * or http://forgerock.org/license/CDDLv1.0.html.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at legal-notices/CDDLv1_0.txt.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information:
 *      Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 *
 *
 *      Copyright 2007-2008 Sun Microsystems, Inc.
 */
package org.forgerock.opendj.config.server;

import static org.fest.assertions.Assertions.assertThat;
import static org.forgerock.opendj.ldif.LDIF.makeEntry;
import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isA;
import static org.mockito.Matchers.same;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import java.util.Arrays;

import org.forgerock.opendj.config.AdminTestCase;
import org.forgerock.opendj.config.TestCfg;
import org.forgerock.opendj.config.TestParentCfg;
import org.forgerock.opendj.config.server.spi.ConfigAddListener;
import org.forgerock.opendj.config.server.spi.ConfigChangeListener;
import org.forgerock.opendj.config.server.spi.ConfigDeleteListener;
import org.forgerock.opendj.config.server.spi.ConfigurationRepository;
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.Entry;
import org.forgerock.opendj.server.config.server.RootCfg;
import org.mockito.ArgumentCaptor;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;

@SuppressWarnings({ "javadoc", "rawtypes", "unchecked" })
public class ListenerTest extends AdminTestCase {

    private static final DN ROOT_CONFIG_DN = DN.valueOf("cn=config");
    private static final DN TEST_PARENTS_DN = DN.valueOf("cn=test parents,cn=config");

    @BeforeClass
    public void setUp() throws Exception {
        TestCfg.setUp();
    }

    @AfterClass
    public void tearDown() throws Exception {
        TestCfg.cleanup();
    }

    private Entry getTestParentEntry() throws Exception {
        return makeEntry("dn: cn=test parents,cn=config", "objectclass: top", "objectclass: ds-cfg-branch",
            "cn: test parents");
    }

    /**
     * Create a mock of ConfigurationRepository with provided DNs registered.
     */
    private ConfigurationRepository createConfigRepositoryWithDNs(DN... dns) throws ConfigException {
        ConfigurationRepository configRepository = mock(ConfigurationRepository.class);
        for (DN dn : dns) {
            when(configRepository.hasEntry(dn)).thenReturn(true);
        }
        return configRepository;
    }

    /**
     * Register a listener for test parent entry and return the actual
     * registered listener.
     */
    private ConfigAddListener registerAddListenerForTestParent(ConfigurationRepository configRepository,
        ServerManagedObject<RootCfg> root, ConfigurationAddListener<TestParentCfg> parentListener) throws Exception {
        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        ArgumentCaptor<ConfigAddListener> registered = ArgumentCaptor.forClass(ConfigAddListener.class);
        verify(configRepository).registerAddListener(eq(TEST_PARENTS_DN), registered.capture());
        return registered.getValue();
    }

    /**
     * Register a listener for test parent entry in delayed scenario and return
     * the actual registered listener.
     */
    private DelayedConfigAddListener registerAddListenerForTestParentDelayed(
        ConfigurationRepository configRepository, ServerManagedObject<RootCfg> root,
        ConfigurationAddListener<TestParentCfg> parentListener) throws Exception {
        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        ArgumentCaptor<DelayedConfigAddListener> registered = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), registered.capture());
        return registered.getValue();
    }

    @Test
    public void testRegisterAddListenerWithInstantiableRelationImmediate() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN, TEST_PARENTS_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(),
            mock(ConfigurationAddListener.class));

        verify(configRepository).registerAddListener(eq(TEST_PARENTS_DN), isA(ConfigAddListener.class));
    }

    @Test
    public void testRegisterAddListenerWithInstantiableRelationDelayed() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        ArgumentCaptor<DelayedConfigAddListener> registered = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), registered.capture());
        // check that actual listener is the one provided to the root
        ConfigurationAddListener<?> actualListener =
            ((ServerManagedObjectAddListenerAdaptor<?>)
                ((ConfigAddListenerAdaptor<?>) registered.getValue().getDelayedAddListener()).
                    getServerManagedObjectAddListener()).getConfigurationAddListener();
        assertThat(actualListener).isEqualTo(parentListener);
    }

    @Test
    public void testRegisterAddListenerWithInstantiableRelationDelayedThenActualized() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        // register a listener to root
        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
        root.registerAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        // get the delayed listener registered to configuration repository
        ArgumentCaptor<DelayedConfigAddListener> registered = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), registered.capture());

        // now simulate the add of target entry
        String parentDN = "cn=test parents,cn=config";
        when(configRepository.hasEntry(DN.valueOf(parentDN))).thenReturn(true);
        registered.getValue().applyConfigurationAdd(getTestParentEntry());

        // check that listener is added for target entry and deleted for its
        // parent entry
        ConfigAddListenerAdaptor listener =
            (ConfigAddListenerAdaptor<?>) registered.getValue().getDelayedAddListener();
        verify(configRepository).registerAddListener(DN.valueOf(parentDN), listener);
        verify(configRepository).deregisterAddListener(ROOT_CONFIG_DN, registered.getValue());
    }

    @Test
    public void testRegisterAddListenerWithOptionalRelation() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        root.registerAddListener(TestCfg.getTestOneToZeroOrOneParentRelationDefinition(),
            mock(ConfigurationAddListener.class));

        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), isA(ConfigAddListener.class));
    }

    @Test
    public void testRegisterDeleteListenerWithInstantiableRelationImmediate() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN, TEST_PARENTS_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        root.registerDeleteListener(TestCfg.getTestOneToManyParentRelationDefinition(),
            mock(ConfigurationDeleteListener.class));

        verify(configRepository).registerDeleteListener(eq(TEST_PARENTS_DN), isA(ConfigDeleteListener.class));
    }

    @Test
    public void testRegisterDeleteListenerWithInstantiableRelationDelayed() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        ConfigurationDeleteListener<TestParentCfg> parentListener = mock(ConfigurationDeleteListener.class);
        root.registerDeleteListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        ArgumentCaptor<DelayedConfigAddListener> argument = ArgumentCaptor.forClass(DelayedConfigAddListener.class);
        verify(configRepository).registerAddListener(eq(ROOT_CONFIG_DN), argument.capture());
        // check that actual listener is the one provided to the root
        ConfigurationDeleteListener actualListener =
            ((ServerManagedObjectDeleteListenerAdaptor)
                ((ConfigDeleteListenerAdaptor) argument.getValue().getDelayedDeleteListener()).
                    getServerManagedObjectDeleteListener()).getConfigurationDeleteListener();
        assertThat(actualListener).isEqualTo(parentListener);
    }

    @Test
    public void testRegisterDeleteListenerWithOptionalRelation() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        root.registerDeleteListener(TestCfg.getTestOneToZeroOrOneParentRelationDefinition(),
            mock(ConfigurationDeleteListener.class));

        verify(configRepository).registerDeleteListener(eq(ROOT_CONFIG_DN), isA(ConfigDeleteListener.class));
    }

    @Test
    public void testRegisterChangeListener() throws Exception {
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();
        root.setConfigDN(ROOT_CONFIG_DN);

        root.registerChangeListener(mock(ConfigurationChangeListener.class));

        verify(configRepository).registerChangeListener(eq(ROOT_CONFIG_DN), isA(ConfigChangeListener.class));
    }

    @Test
    public void testDeregisterAddListenerWithInstantiableRelationImmediate() throws Exception {
        // arrange
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN, TEST_PARENTS_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
        ConfigAddListener registeredListener =
            registerAddListenerForTestParent(configRepository, root, parentListener);
        when(configRepository.getAddListeners(TEST_PARENTS_DN)).thenReturn(Arrays.asList(registeredListener));

        // act
        root.deregisterAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        // assert
        verify(configRepository).deregisterAddListener(eq(TEST_PARENTS_DN), same(registeredListener));
    }

    @Test
    public void testDeregisterAddListenerWithInstantiableRelationDelayed() throws Exception {
        // arrange
        ConfigurationRepository configRepository = createConfigRepositoryWithDNs(ROOT_CONFIG_DN);
        ServerManagementContext context =
            new ServerManagementContext(configRepository);
        ServerManagedObject<RootCfg> root = context.getRootConfigurationManagedObject();

        ConfigurationAddListener<TestParentCfg> parentListener = mock(ConfigurationAddListener.class);
        ConfigAddListener registeredListener =
            registerAddListenerForTestParentDelayed(configRepository, root, parentListener);
        when(configRepository.getAddListeners(ROOT_CONFIG_DN)).thenReturn(Arrays.asList(registeredListener));

        // act
        root.deregisterAddListener(TestCfg.getTestOneToManyParentRelationDefinition(), parentListener);

        // assert
        verify(configRepository).deregisterAddListener(eq(ROOT_CONFIG_DN), same(registeredListener));
    }

}
