package org.jboss.windup.reporting.freemarker;

import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.jboss.forge.furnace.Furnace;
import org.jboss.forge.furnace.lock.LockMode;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.Variables;
import org.jboss.windup.config.operation.Iteration;
import org.jboss.windup.config.operation.iteration.AbstractIterationOperation;
import org.jboss.windup.reporting.model.ReportModel;
import org.jboss.windup.reporting.service.ReportService;
import org.jboss.windup.util.ExecutionStatistics;
import org.ocpsoft.rewrite.context.EvaluationContext;

import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapperBuilder;
import freemarker.template.Template;
import freemarker.template.TemplateException;

/**
 * This class is used to produce a freemarker report from inside of a Windup {@link Iteration}.
 * 
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a> <jesse.sightler@gmail.com)
 * 
 */
public class FreeMarkerIterationOperation extends AbstractIterationOperation<ReportModel>
{
    private static final String DEFAULT_ITERATION_PAYLOAD_NAME = "reportModel";

    private static final Logger LOG = Logger.getLogger(FreeMarkerIterationOperation.class.getName());

    private final Furnace furnace;
    private final Set<String> variableNames = new HashSet<>();
    private final boolean useDefaultPayloadVariableName;

    protected FreeMarkerIterationOperation(Furnace furnace, String... varNames)
    {
        super();
        this.furnace = furnace;
        useDefaultPayloadVariableName = true;
        if (varNames != null)
        {
            variableNames.addAll(Arrays.asList(varNames));
        }
    }

    protected FreeMarkerIterationOperation(Furnace furnace, String iterationVarName, String... varNames)
    {
        super(iterationVarName);
        this.furnace = furnace;
        useDefaultPayloadVariableName = false;
        variableNames.add(iterationVarName);
        if (varNames != null)
        {
            variableNames.addAll(Arrays.asList(varNames));
        }
    }

    /**
     * Create a FreeMarkerIterationOperation with the provided furnace instance, the provided iteration var, as well as any other associated variables
     * (based upon variables in the Variables object).
     */
    public static FreeMarkerIterationOperation create(Furnace furnace, String... varNames)
    {
        return new FreeMarkerIterationOperation(furnace, varNames);
    }

    @Override
    public void perform(final GraphRewrite event, final EvaluationContext context, final ReportModel payload)
    {
        String templatePath = payload.getTemplatePath();
        String outputFilename = payload.getReportFilename();

        ExecutionStatistics.get().begin("FreeMarkerIterationOperation.render(" + templatePath + ", " + outputFilename + ")");
        try
        {
            ReportService reportService = new ReportService(event.getGraphContext());

            Path outputDir = reportService.getReportDirectory();

            if (!Files.isDirectory(outputDir))
            {
                Files.createDirectories(outputDir);
            }

            Path outputPath = outputDir.resolve(outputFilename);

            LOG.info("Reporting: Writing template \"" + templatePath + "\" to output file \"" + outputPath.toAbsolutePath().toString() + "\"");

            Configuration freemarkerConfig = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            DefaultObjectWrapperBuilder objectWrapperBuilder = new DefaultObjectWrapperBuilder(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
            objectWrapperBuilder.setUseAdaptersForContainers(true);
            freemarkerConfig.setObjectWrapper(objectWrapperBuilder.build());
            freemarkerConfig.setAPIBuiltinEnabled(true);
            freemarkerConfig.setTemplateLoader(new FurnaceFreeMarkerTemplateLoader());
            freemarkerConfig.setTemplateUpdateDelayMilliseconds(3600);

            Template template = freemarkerConfig.getTemplate(templatePath);

            Variables variables = Variables.instance(event);

            // just the variables
            Map<String, Object> vars = FreeMarkerUtil.findFreeMarkerContextVariables(variables,
                        variableNames.toArray(new String[variableNames
                                    .size()]));

            if (useDefaultPayloadVariableName)
            {
                vars.put(DEFAULT_ITERATION_PAYLOAD_NAME, payload);
            }

            // also, extension functions (these are kept separate from vars in order to prevent them
            // from being stored in the associated data with the reportmodel)
            final Map<String, Object> freeMarkerExtensions;
            freeMarkerExtensions = furnace.getLockManager().performLocked(LockMode.WRITE, new Callable<Map<String, Object>>()
            {
                @Override
                public Map<String, Object> call() throws Exception
                {
                    return FreeMarkerUtil.findFreeMarkerExtensions(furnace, event);
                }
            });

            Map<String, Object> objects = new HashMap<>(vars);
            objects.putAll(freeMarkerExtensions);

            try (FileWriter fw = new FileWriter(outputPath.toFile()))
            {
                template.process(objects, fw);
            }

            FreeMarkerUtil.addAssociatedReportData(event.getGraphContext(), payload, vars);
        }
        catch (IOException | TemplateException e)
        {
            LOG.log(Level.WARNING,
                        "Template \"" + templatePath + "\" Failed to write report at \"" + outputFilename + "\" due to: " + e.getMessage(), e);
        }
        finally
        {
            ExecutionStatistics.get().end("FreeMarkerIterationOperation.render(" + templatePath + ", " + outputFilename + ")");
        }
    }

    @Override
    public String toString()
    {
        return "RenderFreeMarkerTemplate";
    }
}
