// Copyright 2006, 2007, 2008, 2009, 2010, 2011 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.tapestry5.internal.structure;

import java.util.List;
import java.util.Locale;

import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.internal.services.PersistentFieldManager;
import org.apache.tapestry5.ioc.internal.util.CollectionFactory;
import org.apache.tapestry5.ioc.internal.util.InternalUtils;
import org.apache.tapestry5.ioc.internal.util.OneShotLock;
import org.apache.tapestry5.ioc.services.PerThreadValue;
import org.apache.tapestry5.ioc.services.PerthreadManager;
import org.apache.tapestry5.runtime.Component;
import org.apache.tapestry5.runtime.PageLifecycleListener;
import org.apache.tapestry5.services.PersistentFieldBundle;
import org.slf4j.Logger;

public class PageImpl implements Page
{
    private final String name;

    private final Locale locale;

    private final PersistentFieldManager persistentFieldManager;

    private ComponentPageElement rootElement;

    private final List<PageLifecycleListener> lifecycleListeners = CollectionFactory.newThreadSafeList();

    private final List<PageResetListener> resetListeners = CollectionFactory.newList();

    private boolean loadComplete;

    private final OneShotLock lock = new OneShotLock();

    /**
     * Obtained from the {@link org.apache.tapestry5.internal.services.PersistentFieldManager} when
     * first needed,
     * discarded at the end of the request.
     */
    private final PerThreadValue<PersistentFieldBundle> fieldBundle;

    /**
     * @param name
     *            canonicalized page name
     * @param locale
     *            locale for page and all components
     * @param persistentFieldManager
     *            for access to cross-request persistent values
     * @param perThreadManager
     *            for managing per-request mutable state
     */
    public PageImpl(String name, Locale locale, PersistentFieldManager persistentFieldManager,
            PerthreadManager perThreadManager)
    {
        this.name = name;
        this.locale = locale;
        this.persistentFieldManager = persistentFieldManager;

        fieldBundle = perThreadManager.createValue();
    }

    @Override
    public String toString()
    {
        return String.format("Page[%s %s]", name, locale);
    }

    public ComponentPageElement getComponentElementByNestedId(String nestedId)
    {
        assert nestedId != null;
        ComponentPageElement element = rootElement;

        if (InternalUtils.isNonBlank(nestedId))
        {
            for (String id : nestedId.split("\\."))
                element = element.getEmbeddedElement(id);
        }

        return element;
    }

    public Locale getLocale()
    {
        return locale;
    }

    public void setRootElement(ComponentPageElement component)
    {
        lock.check();

        rootElement = component;
    }

    public ComponentPageElement getRootElement()
    {
        return rootElement;
    }

    public Component getRootComponent()
    {
        return rootElement.getComponent();
    }

    public void addLifecycleListener(PageLifecycleListener listener)
    {
        lock.check();

        lifecycleListeners.add(listener);
    }

    public void removeLifecycleListener(PageLifecycleListener listener)
    {
        lock.check();

        lifecycleListeners.remove(listener);
    }

    public boolean detached()
    {
        boolean result = false;

        for (PageLifecycleListener listener : lifecycleListeners)
        {
            try
            {
                listener.containingPageDidDetach();
            }
            catch (RuntimeException ex)
            {
                getLogger().error(StructureMessages.detachFailure(listener, ex), ex);
                result = true;
            }
        }

        return result;
    }

    public void loaded()
    {
        lock.check();

        for (PageLifecycleListener listener : lifecycleListeners)
            listener.containingPageDidLoad();

        loadComplete = true;

        lock.lock();
    }

    public void attached()
    {
        for (PageLifecycleListener listener : lifecycleListeners)
            listener.restoreStateBeforePageAttach();

        for (PageLifecycleListener listener : lifecycleListeners)
            listener.containingPageDidAttach();
    }

    public Logger getLogger()
    {
        return rootElement.getLogger();
    }

    public void persistFieldChange(ComponentResources resources, String fieldName, Object newValue)
    {
        if (!loadComplete)
            throw new RuntimeException(StructureMessages.persistChangeBeforeLoadComplete());

        persistentFieldManager.postChange(name, resources, fieldName, newValue);
    }

    public Object getFieldChange(String nestedId, String fieldName)
    {
        if (!fieldBundle.exists())
            fieldBundle.set(persistentFieldManager.gatherChanges(name));

        return fieldBundle.get().getValue(nestedId, fieldName);
    }

    public void discardPersistentFieldChanges()
    {
        persistentFieldManager.discardChanges(name);
    }

    public String getName()
    {
        return name;
    }

    public void addResetListener(PageResetListener listener)
    {
        assert listener != null;
        lock.check();

        resetListeners.add(listener);
    }

    public void pageReset()
    {
        for (PageResetListener l : resetListeners)
        {
            l.containingPageDidReset();
        }
    }

    public boolean hasResetListeners()
    {
        return !resetListeners.isEmpty();
    }

}

@@ -724,16 +724,70 @@
 .Locale;
+%0Aimport java.util.Map;%0Aimport java.util.regex.Pattern;
 %0A%0Aimport
@@ -967,69 +967,8 @@
 ry;%0A
-import org.apache.tapestry5.ioc.internal.util.InternalUtils;%0A
 impo
@@ -1833,24 +1833,136 @@
 hotLock();%0A%0A
+    private final Map%3CString, ComponentPageElement%3E idToComponent = CollectionFactory.newCaseInsensitiveMap();%0A%0A
     /**%0A    
@@ -2196,24 +2196,97 @@
 eldBundle;%0A%0A
+    private static final Pattern SPLIT_ON_DOT = Pattern.compile(%22%5C%5C.%22);%0A%0A
     /**%0A    
@@ -3059,32 +3059,45 @@
   %7D%0A%0A    public 
+synchronized 
 ComponentPageEle
@@ -3191,168 +3191,275 @@
 ll;%0A
-        ComponentPageElement element = rootElement;%0A%0A        if (InternalUtils.isNonBlank(nestedId))%0A        %7B%0A            for (String id : nestedId.split(%22%5C%5C.%22
+%0A        if (nestedId.equals(%22%22))%0A            return rootElement;%0A%0A        ComponentPageElement element = idToComponent.get(nestedId);%0A%0A        if (element == null)%0A        %7B%0A            element = rootElement;%0A%0A            for (String id : SPLIT_ON_DOT.split(nestedId
 ))%0A 
@@ -3456,32 +3456,39 @@
 stedId))%0A       
+     %7B%0A
          element
@@ -3472,32 +3472,39 @@
      %7B%0A         
+       
 element = elemen
@@ -3529,16 +3529,81 @@
 nt(id);%0A
+            %7D%0A%0A            idToComponent.put(nestedId, element);%0A
         

Create a mapping of nested component ids to component page elements, inside PageImpl