/**
 * 
 */
package org.ajax4jsf.webapp;

import java.io.IOException;
import java.io.InputStream;
import java.util.EmptyStackException;
import java.util.Properties;
import java.util.regex.PatternSyntaxException;

import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.ajax4jsf.Messages;
import org.ajax4jsf.io.parser.FastHtmlParser;
import org.ajax4jsf.resource.util.URLToStreamHelper;
import org.ajax4jsf.webapp.nekko.NekkoParser;
import org.ajax4jsf.webapp.tidy.TidyParser;
import org.apache.commons.collections.ArrayStack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * @author asmirnov
 * 
 */
public class ConfigurableXMLFilter extends BaseXMLFilter {

    public static final String NONE = "NONE";

	public static final String NEKO = "NEKO";

	public static final String TIDY = "TIDY";

	private static final Log log = LogFactory
	    .getLog(ConfigurableXMLFilter.class);

    private static final String PARSERS_LIST_PARAMETER = "org.ajax4jsf.xmlparser.ORDER";

    private static final String VIEW_ID_PATTERN_PARAMETER = "org.ajax4jsf.xmlparser.";

    /**
     * map of the parsers names and viewId patterns for all parser type.
     */
    private ParserConfig parsers = new TidyParserConfig();

    // private ParserConfig passParserConfig = new PassParserConfig();

    public void init(FilterConfig config) throws ServletException {
	super.init(config);
	ServletContext servletContext = config.getServletContext();
	String parsersParameter = servletContext
		.getInitParameter(PARSERS_LIST_PARAMETER);
	if (null != parsersParameter) {
	    configureParsers(servletContext, parsersParameter);
	}
    }

    /**
     * @param servletContext
     * @param parsersParameter
     * @throws ServletException
     */
    public void configureParsers(ServletContext servletContext,
	    String parsersParameter) throws ServletException {
	String[] parsersNames = parsersParameter.split("\\s*,\\s*");
	for (int i = parsersNames.length - 1; i >= 0; i--) {
	    String parserName = parsersNames[i];
	    ParserConfig parserConfig;
	    if (TIDY.equals(parserName)) {
		parserConfig = new TidyParserConfig();
	    } else if (NEKO.equals(parserName)) {
		parserConfig = new NekoParserConfig();
	    } else if (NONE.equals(parserName)) {
		parserConfig = new PassParserConfig();
	    } else {
		throw new ServletException(
			"Unknown XML parser type in config parameter "
				+ parserName);
	    }
	    parserConfig.setNext(parsers);
	    if (null != servletContext) {
		try {
		    String parserViewPattern = servletContext
			    .getInitParameter(VIEW_ID_PATTERN_PARAMETER
				    + parserName);
		    parserConfig.setPatterns(parserViewPattern);

		} catch (PatternSyntaxException e) {
		    throw new ServletException("Invalid pattern for a parser "
			    + parserName + " :" + e.getMessage());
		}
	    }
	    parsers = parserConfig;
	}
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.ajax4jsf.webapp.BaseXMLFilter#getParser(java.lang.String,
     *      boolean, java.lang.String)
     */
    protected HtmlParser getParser(String mimetype, boolean isAjax,
	    String viewId) {

    	HtmlParser parser = null;
		
    	if (isAjax || mimetype.startsWith(TEXT_HTML) || mimetype.startsWith(APPLICATION_XHTML_XML)) {
			if (isAjax || isForcexml()) {
			    parser = parsers.getParser(viewId, mimetype);
			} else {
			    parser = new FastHtmlParser();
			}
		}

    	return parser;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.ajax4jsf.webapp.BaseXMLFilter#reuseParser(org.ajax4jsf.webapp.HtmlParser)
     */
    protected void reuseParser(HtmlParser parser) {
	parsers.reuseParser(parser);

    }

    private static final int STACK_SIZE = 100;

    private ArrayStack _xhtmlParsersPool = new ArrayStack(STACK_SIZE);

    private class NekoParserConfig extends ParserConfig {

	protected HtmlParser createParser(String mime) {
			NekkoParser parser;
			try {
				synchronized (_xhtmlParsersPool) {
					parser = (NekkoParser) _xhtmlParsersPool.pop();
				}
			} catch (EmptyStackException e) {
				parser = new NekkoParser();
				parser.setPublicId(getPublicid());
				parser.setSystemid(getSystemid());
				parser.setNamespace(getNamespace());
				// If tidy not handle all requests, disable reorganising
				// of html
				// parser.setMoveElements(isForcexml());
				parser.init();
			}
			parser.setMime(mime);
			// TODO - set header scripts/styles filter.
			return parser;
		}

	boolean storeParser(HtmlParser parser) {
	    if (null != parser && parser instanceof NekkoParser) {
		synchronized (_xhtmlParsersPool) {
		    if (_xhtmlParsersPool.size() < STACK_SIZE) {
			((NekkoParser) parser).reset();
			_xhtmlParsersPool.push(parser);
		    }

		}
		return true;
	    }
	    return false;
	}
    }

    private class TidyParserConfig extends ParserConfig {

	protected HtmlParser createParser(String mime) {
	    // TODO Auto-generated method stub
	    TidyParser tidyParser = new TidyParser(getTidyProperties());
	    tidyParser.setMoveElements(isForcexml());
	    tidyParser.setMime(mime);
	    return tidyParser;
	}

    }

    private class PassParserConfig extends ParserConfig {

	protected HtmlParser createParser(String mime) {
	    return new FastHtmlParser();
	}

    }

    private Properties _tidyProperties;

    private Properties getTidyProperties() {
	if (null == _tidyProperties) {
	    _tidyProperties = new Properties();
	    InputStream defaultprops = null;
	    InputStream props = null;
	    try {
		defaultprops = URLToStreamHelper.urlToStreamSafe(TidyParser.class
			.getResource("tidy.properties"));
		if (null != defaultprops) {
		    _tidyProperties.load(defaultprops);
		    if (log.isDebugEnabled()) {
			log.debug("default tidy parser properties loaded");
		    }
		} else if (log.isDebugEnabled()) {
			log.debug("No default tidy parser properties found");
		    }

		// Second part - user-defined properties.
		props = URLToStreamHelper.urlToStreamSafe(Thread.currentThread().getContextClassLoader()
			.getResource("tidy.properties"));
		if (null != props) {
		    _tidyProperties.load(props);
		    if (log.isDebugEnabled()) {
			log.debug("application-specific tidy parser properties loaded");
		    }
		}
	    } catch (IOException e) {
		// TODO Auto-generated catch block
		log.warn(Messages
			.getMessage(Messages.READING_TIDY_PROPERTIES_ERROR), e);
	    } finally {
		if (null != props) {
		    try {
			props.close();
		    } catch (IOException e) {
			// can be ignored
		    }
		}
		if (null != defaultprops) {
		    try {
			defaultprops.close();
		    } catch (IOException e) {
			// can be ignored
		    }
		}
	    }
	}
	return _tidyProperties;
    }

    public ParserConfig getParsers() {
        return parsers;
    }

}
