/*
 * Copyright (C) 2010-2011 Mathias Doenitz
 *
 * Based on peg-markdown (C) 2008-2010 John MacFarlane
 *
 * Licensed 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.pegdown;

import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.parboiled.Parboiled;
import org.pegdown.ast.RootNode;
import org.pegdown.plugins.PegDownPlugins;
import org.pegdown.plugins.ToHtmlSerializerPlugin;

/**
 * A clean and lightweight Markdown-to-HTML filter based on a PEG parser implemented with parboiled.
 * Note: A PegDownProcessor is not thread-safe (since it internally reused the parboiled parser instance).
 * If you need to process markdown source in parallel create one PegDownProcessor per thread!
 *
 * @see <a href="http://daringfireball.net/projects/markdown/">Markdown</a>
 * @see <a href="http://www.parboiled.org/">parboiled.org</a>
 */
public class PegDownProcessor {
    public static final long DEFAULT_MAX_PARSING_TIME = 2000;

    public final Parser parser;

    /**
     * Creates a new processor instance without any enabled extensions and the default parsing timeout.
     */
    public PegDownProcessor() {
        this(DEFAULT_MAX_PARSING_TIME);
    }

    /**
     * Creates a new processor instance without any enabled extensions and the given parsing timeout.
     */
    public PegDownProcessor(long maxParsingTimeInMillis) {
        this(Extensions.NONE, maxParsingTimeInMillis);
    }

    /**
     * Creates a new processor instance with the given {@link org.pegdown.Extensions} and the default parsing timeout.
     *
     * @param options the flags of the extensions to enable as a bitmask
     */
    public PegDownProcessor(int options) {
        this(options, DEFAULT_MAX_PARSING_TIME, PegDownPlugins.NONE);
    }

    /**
     * Creates a new processor instance with the given {@link org.pegdown.Extensions} and parsing timeout.
     *
     * @param options the flags of the extensions to enable as a bitmask
     * @param maxParsingTimeInMillis the parsing timeout
     */
    public PegDownProcessor(int options, long maxParsingTimeInMillis) {
        this(options, maxParsingTimeInMillis, PegDownPlugins.NONE);
    }

    /**
     * Creates a new processor instance with the given {@link org.pegdown.Extensions} and plugins.
     *
     * @param options the flags of the extensions to enable as a bitmask
     * @param plugins the plugins to use
     */
    public PegDownProcessor(int options, PegDownPlugins plugins) {
        this(options, DEFAULT_MAX_PARSING_TIME, plugins);
    }

    /**
     * Creates a new processor instance with the given {@link org.pegdown.Extensions}, parsing timeout and plugins.
     *
     * @param options the flags of the extensions to enable as a bitmask
     * @param maxParsingTimeInMillis the parsing timeout
     * @param plugins the plugins to use
     */
    public PegDownProcessor(int options, long maxParsingTimeInMillis, PegDownPlugins plugins) {
        this(Parboiled.createParser(Parser.class, options, maxParsingTimeInMillis, Parser.DefaultParseRunnerProvider, plugins));
    }

    /**
     * Creates a new processor instance using the given Parser.
     *
     * @param parser the parser instance to use
     */
    public PegDownProcessor(Parser parser) {
        this.parser = parser;
    }

    /**
     * Converts the given markdown source to HTML.
     * If the input cannot be parsed within the configured parsing timeout the method returns null.
     *
     * @param markdownSource the markdown source to convert
     * @return the HTML
     */
    public String markdownToHtml(String markdownSource) {
        return markdownToHtml(markdownSource.toCharArray());
    }

    /**
     * Converts the given markdown source to HTML.
     * If the input cannot be parsed within the configured parsing timeout the method returns null.
     *
     * @param markdownSource the markdown source to convert
     * @param linkRenderer the LinkRenderer to use
     * @return the HTML
     */
    public String markdownToHtml(String markdownSource, LinkRenderer linkRenderer) {
        return markdownToHtml(markdownSource.toCharArray(), linkRenderer);
    }

	public String markdownToHtml(String markdownSource, LinkRenderer linkRenderer, Map<String, VerbatimSerializer> verbatimSerializerMap) {
		return markdownToHtml(markdownSource.toCharArray(), linkRenderer, verbatimSerializerMap);
	}

    /**
     * Converts the given markdown source to HTML.
     * If the input cannot be parsed within the configured parsing timeout the method returns null.
     *
     * @param markdownSource the markdown source to convert
     * @return the HTML
     */
    public String markdownToHtml(char[] markdownSource) {
        return markdownToHtml(markdownSource, new LinkRenderer());
    }

    /**
     * Converts the given markdown source to HTML.
     * If the input cannot be parsed within the configured parsing timeout the method returns null.
     *
     * @param markdownSource the markdown source to convert
     * @param linkRenderer the LinkRenderer to use
     * @return the HTML
     */
    public String markdownToHtml(char[] markdownSource, LinkRenderer linkRenderer) {
	    return markdownToHtml(markdownSource, linkRenderer, Collections.<String, VerbatimSerializer>emptyMap());
    }

	public String markdownToHtml(char[] markdownSource, LinkRenderer linkRenderer, Map<String, VerbatimSerializer> verbatimSerializerMap) {
        return markdownToHtml(markdownSource, linkRenderer,
				verbatimSerializerMap, parser.plugins.getHtmlSerializerPlugins());
    }

	public String markdownToHtml(char[] markdownSource,
			LinkRenderer linkRenderer,
			Map<String, VerbatimSerializer> verbatimSerializerMap, 
			List<ToHtmlSerializerPlugin> plugins) {
		try {
            RootNode astRoot = parseMarkdown(markdownSource);
            return new ToHtmlSerializer(linkRenderer, verbatimSerializerMap, plugins).toHtml(astRoot);
        } catch(ParsingTimeoutException e) {
            return null;
        }
	}

    /**
     * Parses the given markdown source and returns the root node of the generated Abstract Syntax Tree.
     * If the input cannot be parsed within the configured parsing timeout the method throws a ParsingTimeoutException.
     *
     * @param markdownSource the markdown source to convert
     * @return the AST root
     */
    public RootNode parseMarkdown(char[] markdownSource) {
        return parser.parse(prepareSource(markdownSource));
    }

    /**
     * Adds two trailing newlines.
     *
     * @param source the markdown source to process
     * @return the processed source
     */
    public char[] prepareSource(char[] source) {
        char[] src = new char[source.length + 2];
        System.arraycopy(source, 0, src, 0, source.length);
        src[source.length] = '\n';
        src[source.length + 1] = '\n';
        return src;
    }
}
