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

import java.util.ArrayList;

import org.apache.wicket.Application;
import org.apache.wicket.Page;
import org.apache.wicket.markup.html.form.AutoLabelTagHandler;
import org.apache.wicket.markup.parser.IMarkupFilter;
import org.apache.wicket.markup.parser.IXmlPullParser;
import org.apache.wicket.markup.parser.filter.EnclosureHandler;
import org.apache.wicket.markup.parser.filter.HeadForceTagIdHandler;
import org.apache.wicket.markup.parser.filter.HtmlHandler;
import org.apache.wicket.markup.parser.filter.HtmlHeaderSectionHandler;
import org.apache.wicket.markup.parser.filter.InlineEnclosureHandler;
import org.apache.wicket.markup.parser.filter.OpenCloseTagExpander;
import org.apache.wicket.markup.parser.filter.RelativePathPrefixHandler;
import org.apache.wicket.markup.parser.filter.StyleAndScriptIdentifier;
import org.apache.wicket.markup.parser.filter.WicketContainerTagHandler;
import org.apache.wicket.markup.parser.filter.WicketLinkTagHandler;
import org.apache.wicket.markup.parser.filter.WicketMessageTagHandler;
import org.apache.wicket.markup.parser.filter.WicketNamespaceHandler;
import org.apache.wicket.markup.parser.filter.WicketRemoveTagHandler;
import org.apache.wicket.markup.parser.filter.WicketTagIdentifier;
import org.apache.wicket.util.lang.Objects;

/**
 * This is Wicket's default markup parser. It gets pre-configured with Wicket's default wicket
 * filters.
 * 
 * @see MarkupFactory
 * 
 * @author Juergen Donnerstag
 */
public class MarkupParser extends AbstractMarkupParser
{
	/** "wicket" */
	public final static String WICKET = "wicket";

	/**
	 * Constructor.
	 * 
	 * @param resource
	 *            The markup resource (file)
	 */
	public MarkupParser(final MarkupResourceStream resource)
	{
		super(resource);
	}

	/**
	 * Constructor. Usually for testing purposes only
	 * 
	 * @param markup
	 *            The markup resource.
	 */
	public MarkupParser(final String markup)
	{
		super(markup);
	}

	/**
	 * Constructor.
	 * 
	 * @param xmlParser
	 *            The streaming xml parser to read and parse the markup
	 * @param resource
	 *            The markup resource (file)
	 */
	public MarkupParser(final IXmlPullParser xmlParser, final MarkupResourceStream resource)
	{
		super(xmlParser, resource);
	}

	@Override
	public MarkupFilterList getMarkupFilters()
	{
		return (MarkupFilterList)super.getMarkupFilters();
	}

	/**
	 * Add a markup filter
	 * 
	 * @param filter
	 * @return true, if successful
	 */
	public final boolean add(final IMarkupFilter filter)
	{
		return getMarkupFilters().add(filter);
	}

	/**
	 * Add a markup filter before the 'beforeFilter'
	 * 
	 * @param filter
	 * @param beforeFilter
	 * @return true, if successful
	 */
	public final boolean add(final IMarkupFilter filter,
		final Class<? extends IMarkupFilter> beforeFilter)
	{
		return getMarkupFilters().add(filter, beforeFilter);
	}

	/**
	 * a) Allow subclasses to configure individual Wicket filters
	 * <p>
	 * b) Allows to disable Wicket filters via returning false
	 * 
	 * @param filter
	 * @return The filter to be added. Null to ignore.
	 */
	protected IMarkupFilter onAppendMarkupFilter(final IMarkupFilter filter)
	{
		return filter;
	}

	/**
	 * Initialize Wicket's MarkupParser with all necessary markup filters. You may subclass this
	 * method, to add your own filters to the list.
	 * 
	 * @param markup
	 * @return The list of markup filter
	 */
	@Override
	protected MarkupFilterList initializeMarkupFilters(final Markup markup)
	{
		// MarkupFilterList is a simple extension of ArrayList providing few additional helpers
		final MarkupFilterList filters = new MarkupFilterList();

		MarkupResourceStream markupResourceStream = markup.getMarkupResourceStream();

		filters.add(new WicketTagIdentifier(markupResourceStream));
		filters.add(new HtmlHandler());
		filters.add(new WicketRemoveTagHandler(markupResourceStream));
		filters.add(new WicketLinkTagHandler(markupResourceStream));
		filters.add(new AutoLabelTagHandler(markupResourceStream));
		filters.add(new WicketNamespaceHandler(markupResourceStream));
		filters.add(new WicketMessageTagHandler(markupResourceStream));

		// Provided the wicket component requesting the markup is known ...
		if ((markupResourceStream != null) && (markupResourceStream.getResource() != null))
		{
			final ContainerInfo containerInfo = markupResourceStream.getContainerInfo();
			if (containerInfo != null)
			{
				// Pages require additional handlers
				if (Page.class.isAssignableFrom(containerInfo.getContainerClass()))
				{
					filters.add(new HtmlHeaderSectionHandler(markup));
				}

				filters.add(new HeadForceTagIdHandler(containerInfo.getContainerClass()));
			}
		}

		filters.add(new OpenCloseTagExpander());
		filters.add(new RelativePathPrefixHandler(markupResourceStream));
		filters.add(new EnclosureHandler(markupResourceStream));
		filters.add(new InlineEnclosureHandler(markupResourceStream));

		// Append it. See WICKET-4390
		filters.add(new StyleAndScriptIdentifier(), StyleAndScriptIdentifier.class);
		filters.add(new WicketContainerTagHandler(markupResourceStream, Application.get().usesDevelopmentConfig()));

		return filters;
	}

	/**
	 * A simple extension to ArrayList to manage Wicket MarkupFilter's more easily
	 */
	public class MarkupFilterList extends ArrayList<IMarkupFilter>
	{
		private static final long serialVersionUID = 1L;

		@Override
		public boolean add(final IMarkupFilter filter)
		{
			return add(filter, RelativePathPrefixHandler.class);
		}

		/**
		 * Insert a markup filter before a another one.
		 * 
		 * @param filter
		 * @param beforeFilter
		 * @return true, if successful
		 */
		public boolean add(IMarkupFilter filter, final Class<? extends IMarkupFilter> beforeFilter)
		{
			filter = onAdd(filter);
			if (filter == null)
			{
				return false;
			}

			int index = firstIndexOfClass(beforeFilter);
			if (index < 0)
			{
				return super.add(filter);
			}

			super.add(index, filter);
			return true;
		}

		/**
		 * Finds the index of the first entry which is from the same type as the passed
		 * {@literal filterClass} argument.
		 *
		 * @param filterClass
		 *      the class to search for
		 * @return the index of the first match or -1 otherwise
		 */
		private int firstIndexOfClass(final Class<? extends IMarkupFilter> filterClass)
		{
			int result = -1;
			if (filterClass != null)
			{
				final int size = size();
				for (int index = 0; index < size; index++) {
					Class<? extends IMarkupFilter> currentFilterClass = get(index).getClass();
					if (Objects.equal(filterClass, currentFilterClass))
					{
						result = index;
						break;
					}
				}
			}
			return result;
		}

		/**
		 * a) Allow subclasses to configure individual Wicket filters which otherwise can not be
		 * accessed.
		 * <p>
		 * b) Allows to disable Wicket filters via returning false
		 * 
		 * @param filter
		 * @return The filter to be added. Null to ignore
		 */
		protected IMarkupFilter onAdd(final IMarkupFilter filter)
		{
			return onAppendMarkupFilter(filter);
		}
	}
}
