/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.wicket.markup.html.form;

import org.apache.wicket.Component;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.core.request.handler.ComponentNotFoundException;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.markup.WicketTag;
import org.apache.wicket.markup.html.WebMarkupContainer;
import org.apache.wicket.markup.html.basic.Label;
import org.apache.wicket.markup.html.form.AutoLabelResolver.AutoLabel;
import org.apache.wicket.markup.html.internal.ResponseBufferZone;
import org.apache.wicket.markup.parser.XmlTag;
import org.apache.wicket.markup.resolver.IComponentResolver;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.LoadableDetachableModel;
import org.apache.wicket.model.Model;
import org.apache.wicket.model.StringResourceModel;
import org.apache.wicket.request.cycle.RequestCycle;
import org.apache.wicket.util.string.Strings;

/**
 * Resolver that provides the <code>{@literal <wicket:label>}</code> tag, which will output a
 * FormComponent's {@link FormComponent#getLabel() label} without requiring a manual extra component
 * such as {@link Label} or {@link FormComponentLabel}.
 * 
 * <code>{@literal <wicket:label>}</code> can be used
 * <ul>
 * <li>together with <code>{@literal <label wicket:for="...">}</code>:
 * 
 * <pre>
 * {@literal
 * <label wicket:for="myFormComponent">some other markup, optionally<wicket:label/></label>
 * }
 * </pre>
 * 
 * </li>
 * <li>
 * standalone, with a <code>for</code> attribute:
 * 
 * <pre>
 * {@literal
 * <wicket:label for="myFormComponent"/>
 * }
 * </pre>
 * 
 * </li>
 * </ul>
 * <p>
 * It also supports both input and output:
 * <ul>
 * <li>If the FormComponent has a label model, the <code>{@literal <wicket:label>}</code> tag will
 * be replaced by the contents of that label.</li>
 * <li>If the FormComponent's label model is null, it can be picked up from
 * <code>{@literal <wicket:label>}</code>:
 * <ul>
 * <li><code>{@literal <wicket:label>}</code> can contain some raw markup, like this:
 * 
 * <pre>
 * {@literal
 * <wicket:label>I will become the component's label!</wicket:label>
 * }
 * </pre>
 * 
 * </li>
 * <li>Or it can be a message pulled from resources, similar to
 * <code>{@literal <wicket:message/>}</code>:
 * 
 * <pre>
 * {@literal
 * <wicket:label key="messagekey"/>
 * }
 * </pre>
 * 
 * </li>
 * </ul>
 * </li>
 * </ul>
 * 
 * 
 * @author Carl-Eric Menzel <cmenzel@wicketbuch.de>
 * @author igor
 */
public class AutoLabelTextResolver implements IComponentResolver
{
	public static final String LABEL = "label";

	/**
	 * This is inserted by the resolver to render the label.
	 */
	private static class TextLabel extends WebMarkupContainer
	{

		private final Component labeled;

		public TextLabel(String id, Component labeled)

		{
			super(id);
			this.labeled = labeled;
			setRenderBodyOnly(true);
		}

		@Override
		protected void onComponentTag(final ComponentTag tag)
		{
			if (tag.isOpenClose())
			{
				tag.setType(XmlTag.TagType.OPEN);
			}
			super.onComponentTag(tag);
		}

		@Override
		public void onComponentTagBody(final MarkupStream markupStream, final ComponentTag openTag)
		{

			// try and find some form of label content...
			IModel<String> labelModel = findLabelContent(markupStream, openTag);
			// print the label text
			replaceComponentTagBody(markupStream, openTag,
				labelModel != null ? labelModel.getObject() : "");

			// store the label text in FormComponent's label model so its available to errors
			if (labelModel != null)
			{
				if (labeled instanceof FormComponent)
				{
					FormComponent<?> fc = (FormComponent<?>)labeled;
					fc.setLabel(labelModel);
				}
				else
				{
					// if we can't hand off the labelmodel to a component, we have to detach it
					labelModel.detach();
				}
			}
		}

		private IModel<String> findLabelContent(final MarkupStream markupStream,
			final ComponentTag tag)
		{
			if (labeled instanceof ILabelProvider)
			{
				// check if the labeled component is a label provider
				ILabelProvider<String> provider = (ILabelProvider<String>)labeled;
				if (provider.getLabel() != null)
				{
					if (!Strings.isEmpty(provider.getLabel().getObject()))

					{
						return provider.getLabel();
					}
				}
			}

			// check if the labeled component is a form component
			if (labeled instanceof FormComponent)
			{
				final FormComponent<?> formComponent = (FormComponent<?>)labeled;
				String text = formComponent.getDefaultLabel("wicket:unknown");
				if (!"wicket:unknown".equals(text) && !Strings.isEmpty(text))
				{
					return new LoadableDetachableModel<String>()
					{
						@Override
						protected String load()
						{
							return formComponent.getDefaultLabel("wicket:unknown");
						}
					};
				}
			}

			// check if wicket:label tag has a message key
			{
				String resourceKey = tag.getAttribute("key");
				if (resourceKey != null)
				{
					String text = labeled.getString(resourceKey);
					if (!Strings.isEmpty(text))
					{
						return new StringResourceModel(resourceKey, labeled);
					}
				}
			}

			// as last resort use the tag body
			{
				String text = new ResponseBufferZone(RequestCycle.get(), markupStream)
				{
					@Override
					protected void executeInsideBufferedZone()
					{
						TextLabel.super.onComponentTagBody(markupStream, tag);
					}
				}.execute().toString();

				if (!Strings.isEmpty(text))
				{
					return Model.of(text);
				}
			}

			return null;
		}
	}

	@Override
	public Component resolve(MarkupContainer container, MarkupStream markupStream, ComponentTag tag)
	{
		if (tag instanceof WicketTag && "label".equals(tag.getName()))
		{
			// We need to find a FormComponent...
			Component related = null;
			// ...which could be explicitly specified...
			String forAttributeValue = tag.getAttribute("for");
			if (forAttributeValue != null)
			{
				Component component = AutoLabelResolver.findRelatedComponent(container, forAttributeValue);
				related = component;
			}
			if (related == null)
			{
				// ...or available through an AutoLabel, either directly above us...
				if (container instanceof AutoLabel)
				{
					related = ((AutoLabel)container).getRelatedComponent();
				}
				if (related == null)
				{
					// ...or perhaps further up...
					AutoLabel autoLabel = container.findParent(AutoLabel.class);
					if (autoLabel != null)
					{
						related = autoLabel.getRelatedComponent();
					}
				}
			}
			if (related == null)
			{
				// ...or it might just not be available.
				String forAttr = forAttributeValue != null ? " for=\"" + forAttributeValue + "\"" : "";
				throw new ComponentNotFoundException("no related component found for <wicket:label"+forAttr+">");
			}
			else
			{
				// ...found the form component, so we can return our label.
				return new TextLabel(tag.getId(), related);
			}
		}
		return null;
	}

}
