package net.java.sip.communicator.slick.contactlist;

import junit.framework.*;
import org.osgi.framework.*;
import net.java.sip.communicator.slick.contactlist.mockprovider.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.protocol.*;
import java.util.*;

/**
 * Tests in this class verify whether storing and reloading the meta contact
 * list after killing the meta contact list bundle work well.
 * @author Emil Ivov
 */
public class TestMetaContactListPersistence extends TestCase
{
    /**
     * A reference to the SLICK fixture.
     */
    private MclSlickFixture fixture = new MclSlickFixture(getClass().getName());

    public TestMetaContactListPersistence(String name)
    {
        super(name);
    }

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

        fixture.setUp();
    }

    protected void tearDown() throws Exception
    {
        fixture.tearDown();

        fixture = null;
        super.tearDown();
    }

    /**
     * Returns tests in this class in the order that they are supposed to be
     * executed.
     * @return a Test suite containing tests in this class in the order they are
     * to be executed.
     */
    public static Test suite()
    {
        TestSuite suite = new TestSuite();

        suite.addTest(new TestMetaContactListPersistence(
            "testCreateAndMoveMetaContact"));

        suite.addTest(new TestMetaContactListPersistence(
            "testReloadMetaContactListBundle"));

        suite.addTest( new TestMetaContactListPersistence(
            "testPartialContactListRestauration"));

        suite.addTest( new TestMetaContactListPersistence(
            "testCompleteContactListRestauration"));

        suite.addTest( new TestMetaContactListPersistence(
            "testPurgeLocalContactListCopy"));

        return suite;
    }

    /**
     * In this test we only create a meta contact and move it somewhere else.
     * This is a delicate operation since any underlying storage method would
     * have to not only move the meta contact itslef but also make sure that all
     * proto contacts have been moved to protogroups accordingly. We don't do
     * any real testing inside this method. The testing would be happening
     * during reload and it would fail if the contacts moved in this method
     * were not properly moved in the underlying storage utilities.
     */
    public void testCreateAndMoveMetaContact()
    {
        String newContactID ="testCreateAndMoveMetaContact.ContactID";
        MetaContactGroup parentMetaGroup = fixture.metaClService.getRoot()
            .getMetaContactSubgroup(MetaContactListServiceLick.topLevelGroupName);

        //create a new metacontact and, hence mock contact, in the meta
        //"SomePeople" non-toplevel group
        fixture.metaClService.createMetaContact(fixture.mockProvider
            , parentMetaGroup
            , newContactID);

        //check that the contact has been successfully created in the meta cl
        MetaContact newMetaContact  =
            parentMetaGroup.getMetaContact(fixture.mockProvider, newContactID);

        assertNotNull("create failed. couldn't find the new contact."
            , newMetaContact);

        //move the meta contact somewhere else
        fixture.metaClService.moveMetaContact(
            newMetaContact, fixture.metaClService.getRoot());
    }


    /**
     * Uninstalls the meta contact list bundle so that it would be forced to
     * persistently store its contents and load it again later.
     *
     * @throws java.lang.Exception in case stopping or uninstalling the bundle
     * fails.
     */
    public void testReloadMetaContactListBundle()
        throws Exception
    {
        Bundle metaClBundle = findMetaClBundle();

        //uninstall the meta contact list service
        assertNotNull("Couldn't find the bundle that exports the meta "
                      + "contact list servce implementation that we're "
                      + "currently testing"
                      , metaClBundle);

        metaClBundle.stop();

        assertTrue("Couldn't stop the meta cl bundle. State was "
                   + metaClBundle.getState()
                   ,    Bundle.ACTIVE   != metaClBundle.getState()
                     && Bundle.STOPPING != metaClBundle.getState());

        metaClBundle.uninstall();

        assertEquals("Couldn't stop the meta cl bundle."
                     , Bundle.UNINSTALLED, metaClBundle.getState());

        //unregister all mock providers
        fixture.mockPrServiceRegistration.unregister();
        fixture.mockP1ServiceRegistration.unregister();
        fixture.mockP2ServiceRegistration.unregister();

        //remove existing mock providers.
        fixture.replacementMockPr = new MockProvider(
            fixture.mockProvider.getAccountID().getAccountUserID());
        fixture.replacementMockP1 = new MockProvider(
            fixture.mockP1.getAccountID().getAccountUserID());
        fixture.replacementMockP2 = new MockProvider(
            fixture.mockP2.getAccountID().getAccountUserID());

        //reinstall only one of the existing mock providers
        //we will reinstall the other mock providers later. our purpose is to
        //verify that the contact list would only reload contacts for the
        //re-registered mock provider upon startup and that it would later
        //complete its list with the rest of the mock providers once we
        //reregister them.
        fixture.mockPrServiceRegistration = MetaContactListServiceLick
            .registerMockProviderService(fixture.replacementMockPr);

        //reinstall the metacontactlist bundle
        metaClBundle = fixture.bundleContext.installBundle(
                        metaClBundle.getLocation());

        assertEquals("Couldn't re-install meta cl bundle."
                     , Bundle.INSTALLED, metaClBundle.getState());

        metaClBundle.start();
        assertEquals("Couldn't re-start meta cl bundle."
                     , Bundle.ACTIVE, metaClBundle.getState());

        fixture.metaClService = (MetaContactListService)fixture.bundleContext
            .getService( fixture.bundleContext.getServiceReference(
                            MetaContactListService.class.getName()));

        assertNotNull("The meta contact list service was not re-registered "
                      +"after reinstalling its bundle."
                      , fixture.metaClService);
    }

    /**
     * Tests whether the freshly reloaded meta contact list has properly
     * reloaded contacts for the provider that was registered at the time of its
     * re-installation
     */
    public void testPartialContactListRestauration()
    {
        //verify that contents of the meta contact list matches contents of
        //the mock provider we removed.
        ContactGroup oldProtoRoot = ((OperationSetPersistentPresence)fixture
            .mockProvider.getSupportedOperationSets().get(
                OperationSetPersistentPresence.class.getName()))
                    .getServerStoredContactListRoot();

        fixture.assertGroupEquals(
            (MockContactGroup)oldProtoRoot
            , fixture.metaClService.getRoot()
            , true);//we might have trailing empty meta groups here remaining
                    //from previous testing so ignore them.

        //verify that the new mock provider has created unresolved contacts
        //for all contacts in the meta cl.
        ContactGroup newProtoRoot = ((OperationSetPersistentPresence)fixture
            .replacementMockPr.getSupportedOperationSets().get(
                OperationSetPersistentPresence.class.getName()))
                    .getServerStoredContactListRoot();

        assertEquals("Newly loaded provider does not match the old one."
                     , oldProtoRoot
                     , newProtoRoot);

        //verify that all contacts in the replacement provider are unresolved
        //as otherwise this would mean that the meta contact list has not
        //used the createUnresolvedContact() when creating them.
        Iterator subgroups = newProtoRoot.subgroups();

        while(subgroups.hasNext())
        {
            assertUnresolvedContents( (MockContactGroup) subgroups.next() );
        }
    }

    /**
     * Register the remaing protocol providers and make sure that they too are
     * properly loaded inside the contact list. We also need to verify that
     * proto contacts that have been merged in a single meta contact are still
     * merged.
     */
    public void testCompleteContactListRestauration()
    {
        //reinstall remaining mock providers
        //we will reinstall the other mock providers later. our purpose is to
        fixture.mockP1ServiceRegistration = MetaContactListServiceLick
            .registerMockProviderService(fixture.replacementMockP1);

        fixture.mockP2ServiceRegistration = MetaContactListServiceLick
            .registerMockProviderService(fixture.replacementMockP2);

        //Get references to the root groups of the 2 providers we removed
        ContactGroup oldProtoMockP1Root = ((OperationSetPersistentPresence)
            fixture.mockP1.getSupportedOperationSets().get(
                OperationSetPersistentPresence.class.getName()))
                    .getServerStoredContactListRoot();

        ContactGroup oldProtoMockP2Root = ( (OperationSetPersistentPresence)
            fixture.mockP2.getSupportedOperationSets().
                get(OperationSetPersistentPresence.class.getName()))
                    .getServerStoredContactListRoot();

        //verify that contacts tnat unresolved contacts that have been created
        //inside that the replacement mock providers match those in the
        //providers we removed.
        ContactGroup newProtoMockP1Root = ((OperationSetPersistentPresence)fixture
            .replacementMockP1.getSupportedOperationSets().get(
                OperationSetPersistentPresence.class.getName()))
                    .getServerStoredContactListRoot();

        assertEquals("Newly loaded provider does not match the old one."
                     , oldProtoMockP1Root
                     , newProtoMockP1Root);

        ContactGroup newProtoMockP2Root = ((OperationSetPersistentPresence)fixture
            .replacementMockP2.getSupportedOperationSets().get(
                OperationSetPersistentPresence.class.getName()))
                    .getServerStoredContactListRoot();

        assertEquals("Newly loaded provider does not match the old one."
                     , oldProtoMockP2Root
                     , newProtoMockP2Root);

        //verify that all contacts in the replacement providers are unresolved
        //as otherwise this would mean that the meta contact list has not
        //used the createUnresolvedContact() when creating them.
        Iterator subgroups = newProtoMockP1Root.subgroups();
        while(subgroups.hasNext())
        {
            assertUnresolvedContents( (MockContactGroup) subgroups.next() );
        }


        subgroups = newProtoMockP2Root.subgroups();
        while(subgroups.hasNext())
        {
            assertUnresolvedContents( (MockContactGroup) subgroups.next() );
        }

    }


    /**
     * Traverses all contacts and groups of <tt>root</tt> and throws a failure
     * exception the moment if finds one of them not to be unresolved.
     *
     * @param root the contact group where the recursive assertion should begin
     */
    private void assertUnresolvedContents(MockContactGroup root)
    {
        assertEquals("isResolved for grp:" + root.getGroupName()
                     , false, root.isResolved());

        // verify all contacts
        Iterator contacts = root.contacts();

        while(contacts.hasNext())
        {
            Contact contact = (Contact)contacts.next();
            assertEquals("isResolved for contact:" + contact.getDisplayName()
                     , false, contact.isResolved());
        }

        //recurse all subgroups
        Iterator subgroups = root.subgroups();

        while(subgroups.hasNext()){
            assertUnresolvedContents((MockContactGroup)subgroups.next());
        }
    }

    /**
     * Removes the locally stored contact list copy. The purpose of this is to
     * leave the local list empty for a next round of testing.
     */
    public void testPurgeLocalContactListCopy()
    {
        fixture.metaClService.purgeLocallyStoredContactListCopy();
    }

    /**
     * Returns the bundle that has registered the meta contact list service
     * implementation that we're currently testing. The method would go through
     * all bundles currently installed in the framework and return the first
     * one that exports the same meta cl instance as the one we use in this
     * slick.
     * @return the Bundle that has registered the meta contact list service
     * we're using in the slick.
     */
    private Bundle findMetaClBundle()
    {
        Bundle[] bundles = fixture.bundleContext.getBundles();

        for (int i = 0; i < bundles.length; i++)
        {
            ServiceReference[] registeredServices
                = bundles[i].getRegisteredServices();

            if(registeredServices == null)
                continue;

            for (int j = 0; j < registeredServices.length; j++)
            {
                Object service
                    = fixture.bundleContext.getService(registeredServices[j]);
                if(service == fixture.metaClService)
                    return bundles[i];
            }
        }

        return null;
    }
}
