/*
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.apache.qpid.server.store.berkeleydb;

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

import java.io.File;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.qpid.server.configuration.updater.TaskExecutor;
import org.apache.qpid.server.model.Broker;
import org.apache.qpid.server.model.ConfigurationChangeListener;
import org.apache.qpid.server.model.ConfiguredObject;
import org.apache.qpid.server.model.State;
import org.apache.qpid.server.model.VirtualHost;
import org.apache.qpid.server.model.VirtualHostNode;
import org.apache.qpid.server.plugin.ConfiguredObjectTypeFactory;
import org.apache.qpid.server.store.DurableConfigurationStore;
import org.apache.qpid.server.util.BrokerTestHelper;
import org.apache.qpid.server.virtualhostnode.berkeleydb.BDBHAVirtualHostNode;
import org.apache.qpid.server.virtualhostnode.berkeleydb.BDBHAVirtualHostNodeFactory;
import org.apache.qpid.test.utils.QpidTestCase;
import org.apache.qpid.util.FileUtils;

import com.sleepycat.je.rep.ReplicatedEnvironment;
import com.sleepycat.je.rep.ReplicationConfig;

public class BDBHAVirtualHostNodeTest extends QpidTestCase
{

    private Broker<?> _broker;
    private File _bdbStorePath;
    private VirtualHostNode<?> _virtualHostNode;
    private TaskExecutor _taskExecutor;

    @Override
    protected void setUp() throws Exception
    {
        super.setUp();

        _broker = BrokerTestHelper.createBrokerMock();

        _taskExecutor = new TaskExecutor();
        _taskExecutor.start();
        when(_broker.getTaskExecutor()).thenReturn(_taskExecutor);

        _bdbStorePath = new File(TMP_FOLDER, getTestName() + "." + System.currentTimeMillis());
        _bdbStorePath.deleteOnExit();
    }

    @Override
    protected void tearDown() throws Exception
    {
        try
        {
            if (_virtualHostNode != null)
            {
                _virtualHostNode.setDesiredState(_virtualHostNode.getState(), State.STOPPED);
            }
        }
        finally
        {
            if (_taskExecutor != null)
            {
                _taskExecutor.stopImmediately();
            }
            if (_bdbStorePath != null)
            {
                FileUtils.delete(_bdbStorePath, true);
            }
            super.tearDown();
        }
    }

    public void testCreateAndActivateVirtualHostNode() throws Exception
    {
        String repStreamTimeout = "2 h";
        String nodeName = "node";
        String groupName = "group";
        String nodeHostPort = "localhost:" + findFreePort();
        String helperHostPort = nodeHostPort;
        String durability = "NO_SYNC,SYNC,NONE";
        UUID id = UUID.randomUUID();

        Map<String, Object> attributes = new HashMap<String, Object>();
        attributes.put(BDBHAVirtualHostNode.TYPE, "BDB_HA");
        attributes.put(BDBHAVirtualHostNode.ID, id);
        attributes.put(BDBHAVirtualHostNode.NAME, nodeName);
        attributes.put(BDBHAVirtualHostNode.GROUP_NAME, groupName);
        attributes.put(BDBHAVirtualHostNode.ADDRESS, nodeHostPort);
        attributes.put(BDBHAVirtualHostNode.HELPER_ADDRESS, helperHostPort);
        attributes.put(BDBHAVirtualHostNode.DURABILITY, durability);
        attributes.put(BDBHAVirtualHostNode.STORE_PATH, _bdbStorePath);
        attributes.put(BDBHAVirtualHostNode.REPLICATED_ENVIRONMENT_CONFIGURATION,
                Collections.singletonMap(ReplicationConfig.REP_STREAM_TIMEOUT, repStreamTimeout));

        ConfiguredObjectTypeFactory<?> factory = new BDBHAVirtualHostNodeFactory();

        BDBHAVirtualHostNode<?> node = (BDBHAVirtualHostNode<?>) factory.create(null, attributes, _broker);

        final CountDownLatch virtualHostAddedLatch = new CountDownLatch(1);
        final CountDownLatch virtualHostStateChangeLatch = new CountDownLatch(1);
        node.addChangeListener(new ConfigurationChangeListener()
        {
            @Override
            public void stateChanged(ConfiguredObject object, State oldState, State newState)
            {
                if (object instanceof VirtualHost)
                {
                    virtualHostStateChangeLatch.countDown();
                }
            }

            @Override
            public void childRemoved(ConfiguredObject object, ConfiguredObject child)
            {
            }

            @Override
            public void childAdded(ConfiguredObject object, ConfiguredObject child)
            {
                child.addChangeListener(this);
                virtualHostAddedLatch.countDown();
            }

            @Override
            public void attributeSet(ConfiguredObject object, String attributeName, Object oldAttributeValue, Object newAttributeValue)
            {
            }
        });
        assertEquals(State.ACTIVE, node.setDesiredState(node.getState(), State.ACTIVE));

        DurableConfigurationStore store = node.getConfigurationStore();
        assertNotNull(store);

        BDBMessageStore bdbMessageStore = (BDBMessageStore) store;
        ReplicatedEnvironment environment = (ReplicatedEnvironment) bdbMessageStore.getEnvironmentFacade().getEnvironment();
        ReplicationConfig replicationConfig = environment.getRepConfig();

        assertEquals(nodeName, environment.getNodeName());
        assertEquals(groupName, environment.getGroup().getName());
        assertEquals(nodeHostPort, replicationConfig.getNodeHostPort());
        assertEquals(helperHostPort, replicationConfig.getHelperHosts());
        assertEquals(durability, environment.getConfig().getDurability().toString());
        assertEquals("Unexpected JE replication stream timeout", repStreamTimeout, replicationConfig.getConfigParam(ReplicationConfig.REP_STREAM_TIMEOUT));

        assertTrue("Virtual host child has not been added", virtualHostAddedLatch.await(30, TimeUnit.SECONDS));
        assertTrue("Virtual host child has not had a state change", virtualHostStateChangeLatch.await(30, TimeUnit.SECONDS));
        VirtualHost<?, ?, ?> virtualHost = node.getVirtualHost();
        assertNotNull("Virtual host child was not added", virtualHost);
        assertEquals("Unexpected virtual host name", groupName, virtualHost.getName());
        assertEquals("Unexpected virtual host store", store, virtualHost.getMessageStore());
        assertEquals("Unexpected virtual host state", State.ACTIVE, virtualHost.getState());

        State currentState = node.setDesiredState(State.ACTIVE, State.STOPPED);
        assertEquals("Unexpected state returned after stop", State.STOPPED, currentState);
        assertEquals("Unexpected state", State.STOPPED, node.getState());

        assertNull("Virtual host is not destroyed", node.getVirtualHost());

        currentState = node.setDesiredState(State.STOPPED, State.DELETED);
        assertEquals("Unexpected state returned after delete", State.DELETED, currentState);
        assertEquals("Unexpected state", State.DELETED, node.getState());
        assertFalse("Store still exists", _bdbStorePath.exists());
    }

}


