package org.exoplatform.portal.mop.page;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;

import org.exoplatform.portal.mop.Described;
import org.exoplatform.portal.mop.ProtectedResource;
import org.exoplatform.portal.mop.QueryResult;
import org.exoplatform.portal.mop.SiteKey;
import org.exoplatform.portal.mop.SiteType;
import org.exoplatform.portal.mop.Utils;
import org.exoplatform.portal.pom.config.POMSession;
import org.exoplatform.portal.pom.config.POMSessionManager;
import org.exoplatform.portal.pom.data.MappedAttributes;
import org.gatein.mop.api.Attributes;
import org.gatein.mop.api.content.ContentType;
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.Workspace;
import org.gatein.mop.api.workspace.WorkspaceCustomizationContext;
import org.gatein.mop.api.workspace.ui.UIComponent;
import org.gatein.mop.api.workspace.ui.UIContainer;
import org.gatein.mop.api.workspace.ui.UIWindow;

/**
 * This class implements the {@link PageService} business methods.
 *
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 */
public class PageServiceImpl implements PageService {

    /** . */
    final POMSessionManager manager;

    /** . */
    private final DataCache dataCache;

    /**
     * Create an instance that uses a simple data cache, such instance should be used for testing purposes.
     *
     * @param manager the mop session manager
     * @throws NullPointerException if the manager argument is null
     */
    public PageServiceImpl(POMSessionManager manager) throws NullPointerException {
        this(manager, new SimpleDataCache());
    }

    /**
     * Create an instance that will use a specified data cache instance.
     *
     * @param manager the mop session manager
     * @param dataCache the data cache
     * @throws NullPointerException if any argument is null
     */
    public PageServiceImpl(POMSessionManager manager, DataCache dataCache) throws NullPointerException {
        if (manager == null) {
            throw new NullPointerException("No null pom session manager allowed");
        }
        if (dataCache == null) {
            throw new NullPointerException("No null data cache allowed");
        }
        this.manager = manager;
        this.dataCache = dataCache;
    }

    @Override
    public PageContext loadPage(PageKey key) {
        if (key == null) {
            throw new NullPointerException();
        }

        //
        POMSession session = manager.getSession();
        PageData data = dataCache.getPageData(session, key);
        return data != null && data != PageData.EMPTY ? new PageContext(data) : null;
    }

    /**
     * <p>
     * Load all the pages of a specific site. Note that this method can potentially raise performance issues if the number of
     * pages is very large and should be used with cautions. That's the motiviation for not having this method on the
     * {@link PageService} interface.
     * </p>
     *
     * @param siteKey the site key
     * @return the list of pages
     * @throws NullPointerException if the site key argument is null
     * @throws PageServiceException anything that would prevent the operation to succeed
     */
    public List<PageContext> loadPages(SiteKey siteKey) throws NullPointerException, PageServiceException {
        if (siteKey == null) {
            throw new NullPointerException("No null site key accepted");
        }

        //
        POMSession session = manager.getSession();
        ObjectType<Site> objectType = Utils.objectType(siteKey.getType());
        Workspace workspace = session.getWorkspace();
        Site site = workspace.getSite(objectType, siteKey.getName());

        //
        if (site == null) {
            throw new PageServiceException(PageError.NO_SITE);
        }

        //
        org.gatein.mop.api.workspace.Page root = site.getRootPage();
        Collection<org.gatein.mop.api.workspace.Page> pages = root.getChild("pages").getChildren();
        List<PageContext> list = new ArrayList<PageContext>(pages.size());
        for (Page page : pages) {
            list.add(loadPage(new PageKey(siteKey, page.getName())));
        }

        return list;
    }

    @Override
    public boolean savePage(PageContext page) {
        if (page == null) {
            throw new NullPointerException();
        }

        //
        POMSession session = manager.getSession();
        ObjectType<Site> objectType = Utils.objectType(page.key.getSite().getType());
        Workspace workspace = session.getWorkspace();
        Site site = workspace.getSite(objectType, page.key.getSite().getName());

        //
        if (site == null) {
            throw new PageServiceException(PageError.NO_SITE);
        }

        //
        org.gatein.mop.api.workspace.Page root = site.getRootPage();
        org.gatein.mop.api.workspace.Page pages = root.getChild("pages");
        org.gatein.mop.api.workspace.Page dst = pages.getChild(page.key.getName());

        //
        boolean created;
        if (dst == null) {
            dst = pages.addChild(page.key.getName());
            created = true;
        } else {
            created = false;
        }

        //
        PageState state = page.state;
        if (state != null) {
            ProtectedResource pr = dst.adapt(ProtectedResource.class);
            pr.setAccessPermissions(page.state.accessPermissions);
            pr.setEditPermission(page.state.editPermission);

            //
            Described described = dst.adapt(Described.class);
            described.setName(page.state.displayName);
            described.setDescription(page.state.description);

            //
            Attributes attrs = dst.getAttributes();
            attrs.setValue(MappedAttributes.FACTORY_ID, page.state.factoryId);
            attrs.setValue(MappedAttributes.SHOW_MAX_WINDOW, page.state.showMaxWindow);
        }

        //
        dataCache.removePage(session, page.key);

        // Update state
        page.data = dataCache.getPageData(session, page.key);
        page.state = null;

        //
        return created;
    }

    @Override
    public boolean destroyPage(PageKey key) {
        if (key == null) {
            throw new NullPointerException("No null page argument");
        }

        //
        POMSession session = manager.getSession();
        ObjectType<Site> objectType = Utils.objectType(key.getSite().getType());
        Workspace workspace = session.getWorkspace();
        Site site = workspace.getSite(objectType, key.getSite().getName());

        //
        if (site == null) {
            throw new PageServiceException(PageError.NO_SITE);
        }

        //
        org.gatein.mop.api.workspace.Page root = site.getRootPage();
        org.gatein.mop.api.workspace.Page pages = root.getChild("pages");
        org.gatein.mop.api.workspace.Page page = pages.getChild(key.getName());
        if (page != null) {
            page.destroy();
            dataCache.removePage(session, key);
            return true;
        } else {
            return false;
        }
    }

    @Override
    public PageContext clone(PageKey src, PageKey dst) {
        POMSession session = manager.getSession();
        Workspace workspace = session.getWorkspace();

        //
        org.gatein.mop.api.workspace.Page srcPage;
        ObjectType<Site> srcType = Utils.objectType(src.site.getType());
        Site srcSite = workspace.getSite(srcType, src.site.getName());
        if (srcSite == null) {
            throw new PageServiceException(PageError.CLONE_NO_SRC_SITE, "Could not clone page " + src.getName()
                    + "from non existing site of type " + src.site.getType() + " with id " + src.site.getName());
        } else {
            org.gatein.mop.api.workspace.Page root = srcSite.getRootPage();
            org.gatein.mop.api.workspace.Page pages = root.getChild("pages");
            srcPage = pages.getChild(src.getName());
        }

        //
        if (srcPage == null) {
            throw new PageServiceException(PageError.CLONE_NO_SRC_PAGE, "Could not clone non existing page " + src.getName()
                    + " from site of type " + src.site.getType() + " with id " + src.site.getName());
        }

        //
        ObjectType<Site> dstType = Utils.objectType(dst.getSite().getType());
        Site dstSite = workspace.getSite(dstType, dst.getSite().getName());
        if (dstSite == null) {
            throw new PageServiceException(PageError.CLONE_NO_DST_SITE, "Could not clone page " + dst.name
                    + "to non existing site of type " + dst.site.getType() + " with id " + dst.site.getName());
        }

        //
        org.gatein.mop.api.workspace.Page dstRoot = srcSite.getRootPage();
        org.gatein.mop.api.workspace.Page dstPages = dstRoot.getChild("pages");
        if (dstPages.getChild(dst.getName()) != null) {
            throw new PageServiceException(PageError.CLONE_DST_ALREADY_EXIST, "Could not clone page " + dst.name
                    + "to existing page " + dst.site.getType() + " with id " + dst.site.getName());
        }

        //
        org.gatein.mop.api.workspace.Page dstPage = dstPages.addChild(dst.getName());

        // Copy all attributes
        Attributes srcAttrs = srcPage.getAttributes();
        Attributes dstAttrs = dstPage.getAttributes();
        for (String key : srcAttrs.getKeys()) {
            Object value = srcAttrs.getObject(key);
            dstAttrs.setObject(key, value);
        }

        // Copy described
        Described srcDescribed = srcPage.adapt(Described.class);
        Described dstDescribed = dstPage.adapt(Described.class);
        dstDescribed.setName(srcDescribed.getName());
        dstDescribed.setDescription(srcDescribed.getDescription());

        // Copy src permissions to dst permission
        ProtectedResource srcPR = srcPage.adapt(ProtectedResource.class);
        ProtectedResource dstPR = dstPage.adapt(ProtectedResource.class);
        dstPR.setAccessPermissions(srcPR.getAccessPermissions());
        dstPR.setEditPermission(srcPR.getEditPermission());

        // Need to clone page data structure as well
        copy(srcPage, dstPage, srcPage.getRootComponent(), dstPage.getRootComponent());

        // Remove
        dataCache.removePage(session, dst);

        //
        return new PageContext(new PageData(dstPage));
    }

    private void copy(Page srcPage, Page dstPage, UIContainer src, UIContainer dst) {
        for (UIComponent srcChild : src.getComponents()) {
            UIComponent dstChild = dst.add(srcChild.getObjectType(), srcChild.getObjectId());

            //
            if (srcChild.isAdapted(Described.class)) {
                Described srcDescribed = srcChild.adapt(Described.class);
                Described dstDescribed = dstChild.adapt(Described.class);
                dstDescribed.setName(srcDescribed.getName());
                dstDescribed.setDescription(srcDescribed.getDescription());
            }

            //
            if (srcChild.isAdapted(ProtectedResource.class)) {
                ProtectedResource srcPR = srcChild.adapt(ProtectedResource.class);
                ProtectedResource dstPR = dstChild.adapt(ProtectedResource.class);
                dstPR.setAccessPermissions(srcPR.getAccessPermissions());
                dstPR.setEditPermission(srcPR.getEditPermission());
            }

            //
            Attributes srcAttrs = srcChild.getAttributes();
            Attributes dstAttrs = dstChild.getAttributes();
            for (String key : srcAttrs.getKeys()) {
                Object value = srcAttrs.getObject(key);
                dstAttrs.setObject(key, value);
            }

            //
            if (srcChild instanceof UIWindow) {
                UIWindow srcWindow = (UIWindow) srcChild;
                UIWindow dstWindow = (UIWindow) dstChild;
                Customization<?> customization = srcWindow.getCustomization();
                ContentType contentType = customization.getType();
                String contentId = customization.getContentId();
                Customization parent = customization.getParent();
                Customization dstParent = null;
                if (parent != null) {
                    WorkspaceCustomizationContext parentCtx = (WorkspaceCustomizationContext) parent.getContext();
                    String name = parentCtx.nameOf(parent);
                    if (parentCtx == srcPage) {
                        dstParent = dstPage.getCustomizationContext().getCustomization(name);
                        if (dstParent == null) {
                            Object state = parent.getVirtualState();
                            dstParent = dstPage.getCustomizationContext().customize(name, contentType, contentId, state);
                        }
                    }
                    if (dstParent != null) {
                        Object state = customization.getState();
                        Customization dstCustomization = dstWindow.customize(dstParent);
                        dstCustomization.setState(state);
                    } else {
                        Object state = customization.getVirtualState();
                        dstWindow.customize(contentType, contentId, state);
                    }
                } else {
                    Object state = customization.getVirtualState();
                    dstWindow.customize(contentType, contentId, state);
                }
            } else if (srcChild instanceof UIContainer) {
                UIContainer srcContainer = (UIContainer) srcChild;
                UIContainer dstContainer = (UIContainer) dstChild;
                copy(srcPage, dstPage, srcContainer, dstContainer);
            }
        }
    }

    @Override
    public QueryResult<PageContext> findPages(int from, int to, SiteType siteType, String siteName, String pageName,
            String pageTitle) {
        POMSession session = manager.getSession();
        org.chromattic.api.query.QueryResult<Page> a = session.findObjects(ObjectType.PAGE, Utils.objectType(siteType),
                siteName, pageTitle, from, to);
        int size = a.size();
        PageContext[] array = new PageContext[size];
        int ptr = 0;
        while (a.hasNext()) {
            Page page = a.next();
            PageData data = new PageData(page);
            dataCache.putPage(data);
            array[ptr++] = new PageContext(data);
        }
        return new QueryResult<PageContext>(from, size, Arrays.asList(array));
    }

    public void clearCache() {
        dataCache.clear();
    }
}
