/**
 * Copyright (C) 2009 eXo Platform SAS.
 *
 * 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.exoplatform.portal.pom.config;

import java.io.Serializable;
import java.lang.reflect.UndeclaredThrowableException;

import org.exoplatform.commons.chromattic.ChromatticLifeCycle;
import org.exoplatform.commons.chromattic.ChromatticManager;
import org.exoplatform.commons.chromattic.SessionContext;
import org.exoplatform.commons.scope.ScopedKey;
import org.exoplatform.portal.pom.config.cache.DataCache;
import org.exoplatform.portal.pom.config.cache.PortalNamesCache;
import org.exoplatform.portal.pom.data.OwnerKey;
import org.exoplatform.portal.pom.data.PortalKey;
import org.exoplatform.services.cache.CacheService;
import org.exoplatform.services.cache.CachedObjectSelector;
import org.exoplatform.services.cache.ExoCache;
import org.exoplatform.services.cache.ObjectCacheInfo;
import org.exoplatform.services.jcr.RepositoryService;
import org.gatein.common.logging.Logger;
import org.gatein.common.logging.LoggerFactory;
import org.gatein.mop.core.api.MOPService;
import org.picocontainer.Startable;

/**
 * @author <a href="mailto:julien.viet@exoplatform.com">Julien Viet</a>
 * @version $Revision$
 */
public class POMSessionManager implements Startable {

    /** . */
    private final Logger log = LoggerFactory.getLogger(POMSessionManager.class);

    private final static String CACHE_NAME = "portal.MOPSessionManager";

    /** . */
    private MOPService pomService;

    /** . */
    private final ExoCache<ScopedKey<?>, Object> cache;

    /** . */
    final ChromatticManager manager;

    /** . */
    private ChromatticLifeCycle configurator;

    /** . */
    private final TaskExecutionDecorator executor;

    /** . */
    private final RepositoryService repositoryService;

    public POMSessionManager(RepositoryService repositoryService, ChromatticManager manager, CacheService cacheService) {
        //
        this.repositoryService = repositoryService;
        this.manager = manager;
        this.cache = cacheService.getCacheInstance(CACHE_NAME);
        this.pomService = null;
        this.executor = new PortalNamesCache(new DataCache(new ExecutorDispatcher()));
    }

    public ChromatticLifeCycle getLifeCycle() {
        return configurator;
    }

    public void cachePut(Serializable key, Object value) {
        ScopedKey<?> globalKey = ScopedKey.create(key);

        //
        if (log.isTraceEnabled()) {
            log.trace("Updating cache key=" + globalKey + " with value=" + value);
        }

        //
        cache.put(globalKey, value);
    }

    public Object cacheGet(Serializable key) {
        ScopedKey globalKey = ScopedKey.create(key);

        //
        Object value = cache.get(globalKey);

        //
        if (log.isTraceEnabled()) {
            log.trace("Obtained for cache key=" + globalKey + " value=" + value);
        }

        //
        return value;
    }

    public void cacheRemove(Serializable key) {
        final ScopedKey<?> globalKey = ScopedKey.create(key);

        //
        if (log.isTraceEnabled()) {
            log.trace("Removing cache key=" + globalKey);
        }

        //
        if (key instanceof PortalKey) {
            // This code seems complex but actually it tries to find all objects in cache that have the same
            // owner key than the portal key, for instance if we remove (portal,classic) then all pages
            // related to (portal,classic) are also evicted
            final PortalKey portalKey = (PortalKey) key;
            try {
                cache.select(new CachedObjectSelector<ScopedKey<?>, Object>() {
                    public boolean select(ScopedKey<?> selectedGlobalKey, ObjectCacheInfo<?> ocinfo) {
                        if (globalKey.getScope().equals(selectedGlobalKey.getScope())) {
                            Serializable selectedLocalKey = selectedGlobalKey.getKey();
                            if (selectedLocalKey instanceof OwnerKey) {
                                OwnerKey selectedOwnerKey = (OwnerKey) selectedLocalKey;
                                if (selectedOwnerKey.getType().equals(portalKey.getType())
                                        && selectedOwnerKey.getId().equals(portalKey.getId())) {
                                    return true;
                                }
                            }
                        }
                        return false;
                    }

                    public void onSelect(ExoCache<? extends ScopedKey<?>, ?> exoCache, ScopedKey<?> key,
                            ObjectCacheInfo<?> ocinfo) throws Exception {
                        cache.remove(key);
                    }
                });
            } catch (Exception e) {
                log.error("Unexpected error when clearing pom cache", e);
            }
        } else {
            cache.remove(globalKey);
        }
    }

    public void start() {
        try {
            MOPChromatticLifeCycle configurator = (MOPChromatticLifeCycle) manager.getLifeCycle("mop");
            configurator.manager = this;

            //
            PortalMOPService pomService = new PortalMOPService(configurator.getChromattic());
            pomService.start();

            //
            this.pomService = pomService;
            this.configurator = configurator;
        } catch (Exception e) {
            throw new UndeclaredThrowableException(e);
        }
    }

    public void stop() {
    }

    public void clearCache() {
        if (log.isTraceEnabled()) {
            log.trace("Clearing cache");
        }

        //
        cache.clearCache();
    }

    public MOPService getPOMService() {
        return pomService;
    }

    public <E extends TaskExecutionDecorator> E getDecorator(Class<E> decoratorClass) {
        return executor.getDecorator(decoratorClass);
    }

    /**
     * <p>
     * Returns the session currently associated with the current thread of execution.
     * </p>
     *
     * @return the current session
     */
    public POMSession getSession() {
        SessionContext context = configurator.getContext();
        return context != null ? (POMSession) context.getAttachment("mopsession") : null;
    }

    /**
     * <p>
     * Open and returns a session to the model. When the current thread is already associated with a previously opened session
     * the method will throw an <tt>IllegalStateException</tt>.
     * </p>
     *
     * @return a session to the model.
     */
    public POMSession openSession() {
        SessionContext context = configurator.openContext();
        return (POMSession) context.getAttachment("mopsession");
    }

    /**
     * <p>
     * Execute the task with a session.
     * </p>
     *
     * @param task the task to execute
     * @throws Exception any exception thrown by the task
     * @return the value
     */
    public <V> V execute(POMTask<V> task) throws Exception {
        POMSession session = getSession();

        //
        return executor.execute(session, task);
    }

}
