package org.jboss.windup.reporting.freemarker;

import java.lang.ref.SoftReference;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.util.Logging;
import org.pegdown.Extensions;
import org.pegdown.LinkRenderer;
import org.pegdown.PegDownProcessor;
import org.pegdown.ToHtmlSerializer;
import org.pegdown.VerbatimSerializer;
import org.pegdown.ast.RootNode;
import org.pegdown.plugins.PegDownPlugins;
import org.pegdown.plugins.ToHtmlSerializerPlugin;

import freemarker.template.SimpleScalar;
import freemarker.template.TemplateModelException;

/**
 * Converts from an input string in Markdown format to an output string in HTML.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
public class MarkdownToHtmlMethod implements WindupFreeMarkerMethod {
    private static final Logger LOG = Logging.get(MarkdownToHtmlMethod.class);

    public static final long MAX_PARSING_TIME_MILLIS = 100000;
    private static final Map<String, SoftReference<String>> cache = new ConcurrentHashMap<>();

    @Override
    public Object exec(@SuppressWarnings("rawtypes") List arguments) throws TemplateModelException {
        if (arguments.size() != 1) {
            throw new TemplateModelException("Error, method expects one argument (String)");
        }
        SimpleScalar freemarkerArg = (SimpleScalar) arguments.get(0);
        String markdownSource = freemarkerArg.getAsString();

        SoftReference<String> cachedResult = cache.get(markdownSource);
        String cachedResultString;
        if (cachedResult != null && (cachedResultString = cachedResult.get()) != null) {
            return cachedResultString;
        }

        try {
            // build the plugins object with our extensions
            PegDownPlugins plugins = PegDownPlugins.builder().build();
            PegDownProcessor processor = new PegDownProcessor(Extensions.FENCED_CODE_BLOCKS, MAX_PARSING_TIME_MILLIS, plugins);

            // build the node and then serialize it so that we can make sure the serializer uses our plugins
            RootNode outputNode = processor.parseMarkdown(markdownSource.toCharArray());

            // Our plugin is also a serializer, so build a plugins list for serialization as well
            List<ToHtmlSerializerPlugin> serializerPlugins = new ArrayList<>(1);

            ToHtmlSerializer serializer = new ToHtmlSerializerExtended(new LinkRenderer(), Collections.<String, VerbatimSerializer>emptyMap(),
                    serializerPlugins);
            String result = serializer.toHtml(outputNode);
            cache.put(markdownSource, new SoftReference<>(result));
            return result;
        } catch (Throwable t) {
            LOG.log(Level.WARNING, "Failed to parse markdown due to: " + t.getMessage() + " markdown source: " + markdownSource, t);
            // Return the unformatted markdown, as this is better than failing the report completely.
            return markdownSource;
        }
    }

    @Override
    public String getMethodName() {
        return "markdownToHtml";
    }

    @Override
    public String getDescription() {
        return "Converts from an input string in Markdown format to an output string in HTML format";
    }

    @Override
    public void setContext(GraphRewrite event) {
    }
}
