/*
 * JBoss, a division of Red Hat
 * Copyright 2011, Red Hat Middleware, LLC, and individual
 * contributors as indicated by the @authors tag. See the
 * copyright.txt in the distribution for a full listing of
 * individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.gatein.integration.wsrp.structure;

import static org.mockito.Mockito.*;

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

import junit.framework.TestCase;

import org.exoplatform.portal.config.DataStorage;
import org.exoplatform.portal.mop.Described;
import org.exoplatform.portal.mop.EventType;
import org.exoplatform.portal.mop.page.PageContext;
import org.exoplatform.portal.mop.page.PageKey;
import org.exoplatform.portal.mop.page.PageService;
import org.exoplatform.portal.mop.page.PageState;
import org.exoplatform.portal.pom.spi.wsrp.WSRP;
import org.exoplatform.services.listener.Event;
import org.gatein.mop.api.content.Customization;
import org.gatein.mop.api.workspace.ObjectType;
import org.gatein.mop.api.workspace.Page;
import org.gatein.mop.api.workspace.Site;
import org.gatein.mop.api.workspace.ui.UIComponent;
import org.gatein.mop.api.workspace.ui.UIContainer;
import org.gatein.mop.api.workspace.ui.UIWindow;
import org.gatein.pc.api.PortletContext;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;

/**
 * @author <a href="mailto:chris.laprun@jboss.com">Chris Laprun</a>
 * @version $Revision$
 */
public class MOPConsumerStructureProviderTestCase extends TestCase {
    public static final String SITE_NAME = "site";
    private MOPConsumerStructureProvider provider;
    private PortalStructureAccess structureAccess;
    private Page page1;

    public void testGetPageIdentifiers() {
        List<String> pageIdentifiers = provider.getPageIdentifiers();
        assertEquals(5, pageIdentifiers.size());
        assertTrue(pageIdentifiers.contains(getPageIdFor("page1")));
        assertTrue(pageIdentifiers.contains(getPageIdFor("page11")));
        assertTrue(pageIdentifiers.contains(getPageIdFor("page12")));
        assertTrue(pageIdentifiers.contains(getPageIdFor("page2")));
        assertTrue(pageIdentifiers.contains(getPageIdFor("page21")));
    }

    private String getPageIdFor(String pageName) {
        return MOPConsumerStructureProvider.PageInfo.getPageIdFor(SITE_NAME, pageName);
    }

    public void testGetWindowIdentifiersForInexistingPage() {
        try {
            provider.getWindowIdentifiersFor("inexisting");
            fail("Cannot retrieve windows for an inexistent page");
        } catch (IllegalArgumentException e) {
            // expected
        }
    }

    public void testGetWindowIdentifiersFor() {
        checkWindows("page1", "window11", "window12");
        checkWindows("page2");
        checkWindows("page11", "window111", "window112");
        checkWindows("page12", "window121");
        checkWindows("page21", "window211");
    }

    public void testAssignPortletToWindow() {
        String newCustomizationId = "/app:new";
        String newWindowName = "portlet";
        provider.assignPortletToWindow(PortletContext.createPortletContext(newCustomizationId), "window11", getPageIdFor("page1"),
                newWindowName);
        verify(structureAccess).getWindowFrom(getIdFor("window11"));

        UIWindow window11 = structureAccess.getWindowFrom(getIdFor("window11"));
        verify(structureAccess).saveChangesTo(window11);

        Described described = window11.adapt(Described.class);
        verify(described).setName(newWindowName + " (remote)");

        WSRP state = new WSRP();
        state.setPortletId(newCustomizationId);
        verify(window11).customize(WSRP.CONTENT_TYPE, newCustomizationId, state);

        Customization<?> customization = window11.getCustomization();
        assertEquals(WSRP.CONTENT_TYPE, customization.getType());
    }

    public void testPageCreationEvent() throws Exception {
        Page foo = createPage("foo", new String[] { "foo1" }, new String[] { "windowfoo1" });
        Page foo1 = foo.getChild("foo1");
        addWindows(foo1, "windowfoo11");
        org.exoplatform.portal.config.model.Page portalPage = mock(org.exoplatform.portal.config.model.Page.class);
        PageKey pageKey = mock(PageKey.class);
        when(structureAccess.getPageFrom(portalPage)).thenReturn(foo);
        when(structureAccess.getPageFrom(pageKey)).thenReturn(foo);

        int pageNumber = provider.getPageIdentifiers().size();

        // when a page is created, the events being sent are now EventType.PAGE_CREATED and DataStorage.PAGE_UPDATED in that
        // order
        provider.onEvent(new Event<PageService, PageKey>(EventType.PAGE_CREATED, null, pageKey));
        provider.onEvent(new Event<DataStorage, org.exoplatform.portal.config.model.Page>(DataStorage.PAGE_UPDATED, null,
                portalPage));

        List<String> identifiers = provider.getPageIdentifiers();
        assertEquals(pageNumber + 2, identifiers.size());
        assertTrue(identifiers.contains(getPageIdFor("foo")));
        assertTrue(identifiers.contains(getPageIdFor("foo1")));

        checkWindows("foo", "windowfoo1");
        checkWindows("foo1", "windowfoo11");

        assertEquals(foo1.getRootComponent().get("windowfoo11"), structureAccess.getWindowFrom(getIdFor("windowfoo11")));
    }

    public void testPageDeletionEvent() throws Exception {
        String pageToRemove = "page1";

        // mocking internal behavior
        PageKey pageKey = mock(PageKey.class);
        when(pageKey.getName()).thenReturn(createInternalNameFrom(pageToRemove));
        PageState pageState = mock(PageState.class);
        when(pageState.getDisplayName()).thenReturn(pageToRemove);
        PageContext pageContext = mock(PageContext.class);
        when(pageContext.getState()).thenReturn(pageState);
        PageService pageService = mock(PageService.class);
        when(pageService.loadPage(pageKey)).thenReturn(pageContext);

        // on delete, we actually get the event after the page has been removed from JCR so we don't have an actual page
        when(structureAccess.getPageFrom(pageKey)).thenReturn(null);

        int pageNumber = provider.getPageIdentifiers().size();

        // when a page is deleted the event being sent is now EventType.PAGE_DESTROYED
        provider.onEvent(new Event<PageService, PageKey>(EventType.PAGE_DESTROYED, pageService, pageKey));

        List<String> identifiers = provider.getPageIdentifiers();
        assertEquals(pageNumber - 1, identifiers.size());
        // deleting a page doesn't delete its children, see GTNPORTAL-1630
        assertFalse(identifiers.contains(getPageIdFor(pageToRemove)));
        assertTrue(identifiers.contains(getPageIdFor("page11")));
        assertTrue(identifiers.contains(getPageIdFor("page12")));
    }

    public void testPageUpdatedEvent() throws Exception {
        // todo!
    }

    @Override
    protected void setUp() throws Exception {
        structureAccess = mock(PortalStructureAccess.class);

        page1 = createPage("page1", new String[] { "page11", "page12" }, new String[] { "window11", "window12" });
        Page page2 = createPage("page2", new String[] { "page21" }, null);

        Page page11 = page1.getChild("page11");
        addWindows(page11, "window111", "window112");

        Page page12 = page1.getChild("page12");
        addWindows(page12, "window121");

        Page page21 = page2.getChild("page21");
        addWindows(page21, "window211");

        ArrayList<Page> pages = new ArrayList<Page>();
        pages.add(page1);
        pages.add(page11);
        pages.add(page12);
        pages.add(page2);
        pages.add(page21);
        when(structureAccess.getPages()).thenReturn(pages);

        provider = new MOPConsumerStructureProvider(structureAccess);

        // needed to initialize state
        provider.getPageIdentifiers();
    }

    private void checkWindows(final String pageName, String... windowNames) {
        List<String> windows = provider.getWindowIdentifiersFor(getPageIdFor(pageName));

        if (windowNames != null) {
            assertEquals(windowNames.length, windows.size());
            for (String windowName : windowNames) {
                assertTrue(windows.contains(windowName));
            }
        }
    }

    private Page createPage(String name, String[] childrenPages, String[] windowNames) {
        Site site = mock(Site.class);
        when(site.getName()).thenReturn(SITE_NAME);

        Page page = mock(Page.class);
        when(page.getName()).thenReturn(createInternalNameFrom(name));
        when(page.getSite()).thenReturn(site);

        // mock call to adapt
        Described described = mock(Described.class);
        when(described.getName()).thenReturn(name);

        when(page.adapt(Described.class)).thenReturn(described);

        if (childrenPages != null) {
            List<Page> children = new ArrayList<Page>(childrenPages.length);
            for (String pageId : childrenPages) {
                Page childPage = createPage(pageId, null, null);
                when(page.getChild(pageId)).thenReturn(childPage);
                children.add(childPage);
            }

            when(page.getChildren()).thenReturn(children);
        }

        addWindows(page, windowNames);

        return page;
    }

    private String createInternalNameFrom(String name) {
        return name + "internal";
    }

    private void addWindows(Page page, String... windowNames) {
        if (windowNames != null) {
            // mock page container
            UIContainer root = mock(UIContainer.class);
            when(page.getRootComponent()).thenReturn(root);

            // for each provided window name, create a mock UIWindow...
            List<UIComponent> children = new ArrayList<UIComponent>(windowNames.length);
            for (String windowName : windowNames) {
                UIWindow window = mock(UIWindow.class);
                when(window.getName()).thenThrow(
                        new RuntimeException("Window.getName returns the internal name, not the human readable one"));
                when(window.getObjectId()).thenReturn(getIdFor(windowName));

                // need to use thenAnswer instead of thenReturn here because it doesn't play well with generics
                when(window.getObjectType()).thenAnswer(new Answer<Object>() {
                    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                        return ObjectType.WINDOW;
                    }
                });

                // mock call to adapt
                Described described = mock(Described.class);
                when(described.getName()).thenReturn(windowName);

                when(window.adapt(Described.class)).thenReturn(described);

                // mock Customization
                final Customization<WSRP> customization = mock(Customization.class);
                when(customization.getType()).thenReturn(WSRP.CONTENT_TYPE);
                when(window.getCustomization()).thenAnswer(new Answer<Object>() {
                    public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
                        return customization;
                    }
                });

                // add it to the list of windows for this page
                children.add(window);

                // make sure that we return the window when we ask for it from its uuid
                when(structureAccess.getWindowFrom(getIdFor(windowName))).thenReturn(window);

                // make sure that we return the window if we ask the root component for it
                when(root.get(windowName)).thenReturn(window);
            }

            // the container should return the list of windows when asked for its components
            when(root.getComponents()).thenReturn(children);
        }
    }

    private String getIdFor(String windowName) {
        return windowName + "Id";
    }
}
