//  Copyright 2004 The Apache Software Foundation
//
// Licensed 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.tapestry.record;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpSession;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.request.RequestContext;
import org.apache.tapestry.util.StringSplitter;

/**
 * Simple implementation of {@link org.apache.tapestry.engine.IPageRecorder}that stores page
 * changes as {@link javax.servlet.http.HttpSession}attributes.
 * 
 * @author Howard Ship
 * @version $Id: SessionPageRecorder.java 244059 2005-01-25 00:10:51Z hlship $
 */

public class SessionPageRecorder extends PageRecorder
{
    private static final Log LOG = LogFactory.getLog(SessionPageRecorder.class);

    /**
     * Dictionary of changes, keyed on an instance of {@link ChangeKey}(which enapsulates component
     * path and property name). The value is the new value for the object. The same information is
     * stored into the {@link HttpSession}, which is used as a kind of write-behind cache.
     */

    private Map _changes;

    /**
     * The session into which changes are recorded.
     * 
     * @since 3.0
     */

    private HttpSession _session;

    /**
     * The fully qualified name of the page being recorded.
     * 
     * @since 3.0
     */

    private String _pageName;

    /**
     * The prefix (for {@link HttpSession}attributes) used by this page recorder.
     */

    private String _attributePrefix;

    public void initialize(String pageName, IRequestCycle cycle)
    {
        if (LOG.isDebugEnabled())
            LOG.debug("Initializing for " + pageName);

        RequestContext context = cycle.getRequestContext();

        _pageName = pageName;
        _session = context.getSession();

        _attributePrefix = context.getServlet().getServletName() + "/" + _pageName + "/";

        restorePageChanges();
    }

    public void discard()
    {
        if (Tapestry.isEmpty(_changes))
            return;

        Iterator i = _changes.keySet().iterator();

        while (i.hasNext())
        {
            ChangeKey key = (ChangeKey) i.next();

            String attributeKey = constructAttributeKey(key.getComponentPath(), key
                    .getPropertyName());

            if (LOG.isDebugEnabled())
                LOG.debug("Removing session attribute " + attributeKey);

            _session.removeAttribute(attributeKey);
        }
    }

    /**
     * Simply clears the dirty flag, because there is no external place to store changed page
     * properties. Sets the locked flag to prevent subsequent changes from occuring now.
     */

    public void commit()
    {
        setDirty(false);
        setLocked(true);
    }

    /**
     * Returns true if the recorder has any changes recorded.
     */

    public boolean getHasChanges()
    {
        if (_changes == null)
            return false;

        return (_changes.size() > 0);
    }

    public Collection getChanges()
    {
        if (_changes == null)
            return Collections.EMPTY_LIST;

        int count = _changes.size();
        Collection result = new ArrayList(count);

        Iterator i = _changes.entrySet().iterator();
        while (i.hasNext())
        {
            Map.Entry entry = (Map.Entry) i.next();

            ChangeKey key = (ChangeKey) entry.getKey();

            Object value = entry.getValue();

            PageChange change = new PageChange(key.getComponentPath(), key.getPropertyName(), value);

            result.add(change);
        }

        return result;
    }

    protected void recordChange(String componentPath, String propertyName, Object newValue)
    {
        ChangeKey key = new ChangeKey(componentPath, propertyName);

        if (_changes == null)
            _changes = new HashMap();

        setDirty(true);

        _changes.put(key, newValue);

        // Now, build a key used to store the new value
        // in the HttpSession

        String attributeKey = constructAttributeKey(componentPath, propertyName);

        if (newValue == null)
            _session.removeAttribute(attributeKey);
        else
            _session.setAttribute(attributeKey, newValue);

        if (LOG.isDebugEnabled())
            LOG.debug("Stored session attribute " + attributeKey + " = " + newValue);
    }

    private String constructAttributeKey(String componentPath, String propertyName)
    {
        StringBuffer buffer = new StringBuffer(_attributePrefix);

        if (componentPath != null)
        {
            buffer.append(componentPath);
            buffer.append('/');
        }

        buffer.append(propertyName);

        return buffer.toString();
    }

    private void restorePageChanges()
    {
        int count = 0;
        Enumeration e = _session.getAttributeNames();
        StringSplitter splitter = null;

        while (e.hasMoreElements())
        {
            String key = (String) e.nextElement();

            if (!key.startsWith(_attributePrefix))
                continue;

            if (LOG.isDebugEnabled())
                LOG.debug("Restoring page change from session attribute " + key);

            if (_changes == null)
            {
                _changes = new HashMap();

                splitter = new StringSplitter('/');
            }

            String[] names = splitter.splitToArray(key);

            // The first name is the servlet name, which allows
            // multiple Tapestry apps to share a HttpSession, even
            // when they use the same page names. The second name
            // is the page name, which we already know.

            int i = 2;

            String componentPath = (names.length == 4) ? names[i++] : null;
            String propertyName = names[i++];
            Object value = _session.getAttribute(key);

            ChangeKey changeKey = new ChangeKey(componentPath, propertyName);

            _changes.put(changeKey, value);

            count++;
        }

        if (LOG.isDebugEnabled())
            LOG.debug(count == 0 ? "No recorded changes." : "Restored " + count
                    + " recorded changes.");
    }

}