/*
 * 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.behavior;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.IComponentAwareEventSink;
import org.apache.wicket.IRequestListener;
import org.apache.wicket.event.IEvent;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.head.IHeaderResponse;
import org.apache.wicket.markup.html.IComponentAwareHeaderContributor;
import org.apache.wicket.markup.parser.XmlTag.TagType;
import org.apache.wicket.util.io.IClusterable;
import org.apache.wicket.util.lang.Args;
import org.danekja.java.util.function.serializable.SerializableBiConsumer;
import org.danekja.java.util.function.serializable.SerializableFunction;

/**
 * Behaviors are kind of plug-ins for Components. They allow functionality to be added to a
 * component and get essential events forwarded by the component. They can be bound to a concrete
 * component (using the bind method which is called when the behavior is attached), but they don't
 * need to. They can modify the components markup by changing the rendered ComponentTag. Behaviors
 * can have their own models as well, and they are notified when these are to be detached by the
 * component.
 * <p>
 * You also cannot modify a components model with a behavior.
 * </p>
 * 
 * @see IRequestListener
 * @see org.apache.wicket.markup.html.IHeaderContributor
 * @see org.apache.wicket.behavior.AbstractAjaxBehavior
 * @see org.apache.wicket.AttributeModifier
 * 
 * @author Ralf Ebert
 * @author Eelco Hillenius
 * @author Igor Vaynberg (ivaynberg)
 */
public abstract class Behavior
	implements
		IClusterable,
		IComponentAwareEventSink,
		IComponentAwareHeaderContributor
{
	private static final long serialVersionUID = 1L;

	/**
	 * Constructor
	 */
	public Behavior()
	{
		if (Application.exists())
		{
			Application.get().getBehaviorInstantiationListeners().onInstantiation(this);
		}
	}

	/**
	 * Called when a component is about to render.
	 * 
	 * @param component
	 *            the component that has this behavior coupled
	 */
	public void beforeRender(Component component)
	{
	}

	/**
	 * Called when a component that has this behavior coupled was rendered.
	 * 
	 * @param component
	 *            the component that has this behavior coupled
	 */
	public void afterRender(Component component)
	{
	}

	/**
	 * Bind this handler to the given component. This method is called by the host component
	 * immediately after this behavior is added to it. This method is useful if you need to do
	 * initialization based on the component it is attached and you can't wait to do it at render
	 * time. Keep in mind that if you decide to keep a reference to the host component, it is not
	 * thread safe anymore, and should thus only be used in situations where you do not reuse the
	 * behavior for multiple components.
	 * 
	 * @param component
	 *            the component to bind to
	 */
	public void bind(Component component)
	{
	}

	/**
	 * Notifies the behavior it is removed from the specified component
	 * 
	 * @param component
	 *            the component this behavior is unbound from
	 */
	public void unbind(Component component)
	{
	}

	/**
	 * Allows the behavior to detach any state it has attached during request processing.
	 * 
	 * @param component
	 *            the component that initiates the detachment of this behavior
	 */
	public void detach(Component component)
	{
	}

	/**
	 * In case an unexpected exception happened anywhere between
	 * {@linkplain #onComponentTag(org.apache.wicket.Component, org.apache.wicket.markup.ComponentTag)} and
	 * {@linkplain #afterRender(org.apache.wicket.Component)},
	 * onException() will be called for any behavior. Typically, if you clean up resources in
	 * {@link #afterRender(Component)}, you should do the same in the implementation of this method.
	 * 
	 * @param component
	 *            the component that has a reference to this behavior and during which processing
	 *            the exception occurred
	 * @param exception
	 *            the unexpected exception
	 */
	public void onException(Component component, RuntimeException exception)
	{
	}

	/**
	 * This method returns false if the behavior generates a callback url (for example ajax
	 * behaviors)
	 * 
	 * @param component
	 *            the component that has this behavior coupled.
	 * 
	 * @return boolean true or false.
	 */
	public boolean getStatelessHint(Component component)
	{
		if (this instanceof IRequestListener)
		{
			// this behavior implements a callback interface, so it cannot be stateless
			return false;
		}
		return true;
	}

	/**
	 * Called when a components is rendering and wants to render this behavior. If false is returned
	 * this behavior will be ignored.
	 * 
	 * @param component
	 *            the component that has this behavior coupled
	 * 
	 * @return true if this behavior must be executed/rendered
	 */
	public boolean isEnabled(Component component)
	{
		return true;
	}

	/**
	 * Called any time a component that has this behavior registered is rendering the component tag.
	 * 
	 * @param component
	 *            the component that renders this tag currently
	 * @param tag
	 *            the tag that is rendered
	 */
	public void onComponentTag(Component component, ComponentTag tag)
	{
	}

	/**
	 * Specifies whether or not this behavior is temporary. Temporary behaviors are removed at the
	 * end of request and never reattached. Such behaviors are useful for modifying component
	 * rendering only when it renders next. Usecases include javascript effects, initial clientside
	 * dom setup, etc.
	 * 
	 * @param component
	 * 
	 * @return true if this behavior is temporary
	 */
	public boolean isTemporary(Component component)
	{
		return false;
	}

	/**
	 * Checks whether or not an {@link IRequestListener} can be invoked on this behavior. For further
	 * information please read the javadoc on {@link Component#canCallListener()},
	 * this method has the same semantics.
	 * 
	 * WARNING: Read the javadoc of {@link Component#canCallListener()} for important
	 * security-related information.
	 * 
	 * @param component
	 *            component this behavior is attached to
	 * @return {@literal true} iff the listener method can be invoked
	 */
	public boolean canCallListener(Component component)
	{
		return isEnabled(component) && component.canCallListener();
	}


	/**
	 * Render to the web response whatever the component wants to contribute to the head section.
	 * 
	 * @param component
	 * 
	 * @param response
	 *            Response object
	 */
	@Override
	public void renderHead(Component component, IHeaderResponse response)
	{
	}

	/**
	 * Called immediately after the onConfigure method in a component. Since this is before the
	 * rendering cycle has begun, the behavior can modify the configuration of the component (i.e.
	 * setVisible(false))
	 * 
	 * @param component
	 *            the component being configured
	 */
	public void onConfigure(Component component)
	{
	}

	/**
	 * Called to notify the behavior about any events sent to the component
	 * 
	 * @see org.apache.wicket.IComponentAwareEventSink#onEvent(org.apache.wicket.Component,
	 *      org.apache.wicket.event.IEvent)
	 */
	@Override
	public void onEvent(Component component, IEvent<?> event)
	{
	}

	/**
	 * Called to notify that the component is being removed from its parent
	 * @param component
	 *      the removed component
	 */
	public void onRemove(Component component)
	{
	}

	/**
	 * Creates a {@link Behavior} that uses the given {@code SerializableConsumer consumer} to do
	 * something with the component's tag.
	 *
	 * <p>
	 * Usage:<br/>
	 * <code>component.add(onTag(tag -> tag.put(key, value)));</code>
	 * </p>
	 *
	 * @param onTagConsumer
	 *            the {@code SerializableConsumer} that accepts the {@link ComponentTag}
	 * @return The created behavior
	 */
	public static Behavior onTag(SerializableBiConsumer<Component, ComponentTag> onTagConsumer)
	{
		Args.notNull(onTagConsumer, "onTagConsumer");

		return new Behavior()
		{
			@Override
			public void onComponentTag(Component component, ComponentTag tag)
			{
				onTagConsumer.accept(component, tag);
			}
		};
	}

	/**
	 * Creates a {@link Behavior} that uses the given {@code SerializableFunction function} to do
	 * something with a component's attribute.
	 *
	 * <p>
	 * Usage:<br/>
	 * <code>component.add(onAttribute("class",
	 *              currentValue -> condition(currentValue) ? "positive" : "negative"));</code>
	 * </p>
	 *
	 * @param name
	 *            the name of the attribute to manipulate
	 * @param onAttribute
	 *            the {@code SerializableFunction} that accepts the old value of the attribute and
	 *            returns a new value
	 * @return The created behavior
	 */
	public static Behavior onAttribute(String name,
		SerializableFunction<String, CharSequence> onAttribute)
	{
		Args.notEmpty(name, "name");
		Args.notNull(onAttribute, "onAttribute");

		return new Behavior()
		{
			private static final long serialVersionUID = 1L;

			@Override
			public void onComponentTag(Component component, ComponentTag tag)
			{
				if (tag.getType() != TagType.CLOSE)
				{
					String oldValue = tag.getAttribute(name);
					tag.put(name, onAttribute.apply(oldValue));
				}
			}
		};
	}

}
