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

import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.wicket.MetaDataKey;
import org.apache.wicket.markup.ComponentTag;
import org.apache.wicket.markup.ContainerInfo;
import org.apache.wicket.markup.HtmlSpecialTag;
import org.apache.wicket.markup.Markup;
import org.apache.wicket.markup.MarkupElement;
import org.apache.wicket.markup.MarkupParser;
import org.apache.wicket.markup.MarkupResourceStream;
import org.apache.wicket.markup.MarkupStream;
import org.apache.wicket.request.cycle.RequestCycle;


/**
 * Base class for markup filters
 * 
 * @author Jonathan Locke
 * @author Juergen Donnerstag
 */
public abstract class AbstractMarkupFilter implements IMarkupFilter
{
	/** The markup created by reading the markup file */
	private final MarkupResourceStream markupResourceStream;

	/** The next MarkupFilter in the chain */
	private IMarkupFilter parent;

	/**
	 *  A key for a request-relative map of counters.
	 *  As map keys we use the class name of the {@link org.apache.wicket.markup.MarkupResourceStream}'s owner 
	 *  container (see {@link org.apache.wicket.markup.MarkupResourceStream#getContainerInfo()}), 
	 *  meaning that each container has its own counter. 
	 *  The counters are used by {@link #getRequestUniqueId()} to get unique ids for markup tags.
	 * **/
	protected final static MetaDataKey<Map<String, AtomicInteger>> REQUEST_COUNTER_KEY = new MetaDataKey<Map<String, AtomicInteger>>()
	{
		private static final long serialVersionUID = 1L;
	};

	/**
	 * Construct.
	 */
	public AbstractMarkupFilter()
	{
		this(null);
	}

	public AbstractMarkupFilter(final MarkupResourceStream markupResourceStream)
	{
		this.markupResourceStream = markupResourceStream;
	}


	/**
	 * @return The next MarkupFilter in the chain
	 */
	@Override
	public IMarkupFilter getNextFilter()
	{
		return parent;
	}

	/**
	 * Set new parent.
	 * 
	 * @param parent
	 *            The parent of this component The next element in the chain
	 */
	@Override
	public void setNextFilter(final IMarkupFilter parent)
	{
		this.parent = parent;
	}

	/**
	 * Get the next xml element from the markup. If eof, than retun null. Ignore raw markup. Invoke
	 * nextTag(tag) if a tag was found.
	 */
	@Override
	public MarkupElement nextElement() throws ParseException
	{
		MarkupElement elem = getNextFilter().nextElement();
		if (elem != null)
		{
			if (elem instanceof ComponentTag)
			{
				elem = onComponentTag((ComponentTag)elem);
			}
			else if (elem instanceof HtmlSpecialTag)
			{
				elem = onSpecialTag((HtmlSpecialTag)elem);
			}
		}
		return elem;
	}

	/**
	 * Invoked when a ComponentTag was found.
	 * <p>
	 * By default this method is also called for WicketTags.
	 * 
	 * @param tag
	 * @return Usually the same as the tag attribute
	 * @throws ParseException
	 */
	protected abstract MarkupElement onComponentTag(ComponentTag tag) throws ParseException;

	/**
	 * Invoked when a WicketTag was found.
	 * 
	 * @param tag
	 * @return Usually the same as the tag attribute
	 * @throws ParseException
	 */

	/**
	 * Invoked when a tags (e.g. DOCTYPE, PROCESSING_INSTRUCTIION, etc. which have been identified
	 * as special tags by the xml parser.
	 * 
	 * @param tag
	 * @return Usually the same as the tag attribute
	 * @throws ParseException
	 */
	protected MarkupElement onSpecialTag(final HtmlSpecialTag tag) throws ParseException
	{
		return tag;
	}

	@Override
	public void postProcess(final Markup markup)
	{
	}

	protected MarkupResourceStream getMarkupResourceStream() {
		return markupResourceStream;
	}

	/**
	 * Extracts the markup namespace from the MarkupResourceStream
	 * passed at creation time.
	 *
	 * <p>
	 *     There are two versions of this method because most IMarkupFilter's
	 *     have dual personality - {@link IMarkupFilter} (one instance per MarkupParser)
	 *     and {@link org.apache.wicket.markup.resolver.IComponentResolver} (one
	 *     instance per application).
	 * </p>
	 *
	 * @return the namespace of the loaded markup
	 */
	protected String getWicketNamespace()
	{
		return getWicketNamespace(null);
	}

	/**
	 * Extracts the markup namespace from the passed MarkupStream if available,
	 * or from the MarkupResourceStream passed at creation time.
	 *
	 * <p>
	 *     There are two versions of this method because most IMarkupFilter's
	 *     have dual personality - {@link IMarkupFilter} (one instance per MarkupParser)
	 *     and {@link org.apache.wicket.markup.resolver.IComponentResolver} (one
	 *     instance per application).
	 * </p>
	 *
	 * @param markupStream
	 *      the markup stream
	 * @return namespace extracted from the markup
	 */
	protected String getWicketNamespace(final MarkupStream markupStream)
	{
		String wicketNamespace = MarkupParser.WICKET;
		if (markupStream != null)
		{
			wicketNamespace = markupStream.getWicketNamespace();
		}
		else if (markupResourceStream != null)
		{
			wicketNamespace = markupResourceStream.getWicketNamespace();
		}
		return wicketNamespace;
	}

	/**
	 * Returns an id using the request-relative counter associated with the 
	 * underlying {@link org.apache.wicket.markup.MarkupResourceStream}'s owner container 
	 * (see {@link org.apache.wicket.markup.MarkupResourceStream#getContainerInfo()}). 
	 * This can be useful for autocomponent tags that need to get a tag id.
	 * 
	 * @return
	 * 		the request-relative id
	 */
	protected int getRequestUniqueId()
	{
		RequestCycle requestCycle = RequestCycle.get();
		Map<String, AtomicInteger> markupUniqueCounters = requestCycle.getMetaData(REQUEST_COUNTER_KEY);
		ContainerInfo containerInfo = getMarkupResourceStream().getContainerInfo();
		String cacheKey = containerInfo != null ? containerInfo.getContainerClass().getCanonicalName() : null;
		
		if (markupUniqueCounters == null)
		{
			markupUniqueCounters = new HashMap<>();
			
			requestCycle.setMetaData(REQUEST_COUNTER_KEY, markupUniqueCounters);
		}
		
		AtomicInteger counter = markupUniqueCounters.get(cacheKey);
		
		if (counter == null)
		{
			counter = new AtomicInteger();
			markupUniqueCounters.put(cacheKey, counter);
		}
		
 	    int cacheHash = cacheKey == null ? 0 : cacheKey.hashCode();
		
 	    //add the counter value to the string hash 
 	    //using the same algorithm of String#hashCode() 
 	    return  cacheHash * 31 + counter.getAndIncrement();
	}
}
