package org.jboss.windup.reporting.rules.rendering;

import java.io.File;
import java.io.IOException;
import java.nio.file.Path;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.jboss.forge.furnace.Furnace;
import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.Variables;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.operation.GraphOperation;
import org.jboss.windup.config.operation.Iteration;
import org.jboss.windup.config.operation.IterationProgress;
import org.jboss.windup.config.operation.iteration.AbstractIterationOperation;
import org.jboss.windup.config.phase.ReportRenderingPhase;
import org.jboss.windup.config.query.Query;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.WindupVertexFrame;
import org.jboss.windup.graph.model.resource.ReportResourceFileModel;
import org.jboss.windup.reporting.freemarker.FreeMarkerIterationOperation;
import org.jboss.windup.reporting.model.ReportModel;
import org.jboss.windup.reporting.model.TemplateType;
import org.jboss.windup.reporting.service.ReportService;
import org.jboss.windup.util.Logging;
import org.jboss.windup.util.exception.WindupException;
import org.jboss.windup.util.threading.WindupExecutors;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;

/**
 * This renders the ApplicationReport, along with all of its subapplications via freemarker.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 *
 */
@RuleMetadata(phase = ReportRenderingPhase.class)
public class RenderReportRuleProvider extends AbstractRuleProvider
{
    private static final Logger LOG = Logging.get(RenderReportRuleProvider.class);

    @Inject
    private Furnace furnace;

    // @formatter:off
    @Override
    public Configuration getConfiguration(GraphContext context)
    {
        return ConfigurationBuilder
        .begin()
        .addRule()
        .when(Query.fromType(ReportModel.class).withProperty(ReportModel.TEMPLATE_TYPE, TemplateType.FREEMARKER.toString()))
        .perform(new FreeMarkerThreadedRenderer(furnace))

        .addRule()
        .when(Query.fromType(ReportResourceFileModel.class))
        .perform(new AbstractIterationOperation<ReportResourceFileModel>() {
            @Override
            public void perform(GraphRewrite event, EvaluationContext context, ReportResourceFileModel payload) {
                ReportService reportService = new ReportService(event.getGraphContext());
                Path outputDir = reportService.getReportDirectory();

                File directory = outputDir.toFile();
                File fullPath = new File(directory, FilenameUtils.separatorsToSystem("resources/" + payload.getPrettyPath()));

                try {
                    FileUtils.forceMkdir(fullPath.getParentFile());
                    FileUtils.copyFile(payload.asFile(), fullPath);
                    LOG.info("Copied raw file: " + payload.getFilePath() + " to: " + fullPath.getAbsolutePath());
                } catch (IOException e) {
                    LOG.warning("IOException creating file: " + fullPath.getAbsolutePath() + ", due to: " + e.getMessage());
                }
            }
        });
    }
    // @formatter:on

    private class FreeMarkerThreadedRenderer extends GraphOperation
    {
        private final Furnace furnace;

        public FreeMarkerThreadedRenderer(Furnace furnace)
        {
            this.furnace = furnace;
        }

        @Override
        public void perform(final GraphRewrite event, final EvaluationContext context)
        {
            Iterable<? extends WindupVertexFrame> reportModelsIterable = Variables.instance(event)
                        .findVariable(Iteration.DEFAULT_VARIABLE_LIST_STRING);
            final Queue<WindupVertexFrame> reportModels = new ConcurrentLinkedDeque<>();
            for (WindupVertexFrame frame : reportModelsIterable)
                reportModels.add(frame);
            final FreeMarkerIterationOperation freeMarkerIterationOperation = FreeMarkerIterationOperation.create(furnace);
            final IterationProgress iterationProgress = IterationProgress.monitoring("Rendering Reports", 100);

            int threadCount = WindupExecutors.getDefaultThreadCount();
            ExecutorService executorService = WindupExecutors.newFixedThreadPool(WindupExecutors.getDefaultThreadCount());

            // Set the frames as iteration progress uses this
            event.getRewriteContext().put(Iteration.DEFAULT_VARIABLE_LIST_STRING, reportModels);
            iterationProgress.perform(event, context);

            for (int i = 0; i < threadCount; i++)
            {
                executorService.submit(new Callable<Void>()
                {
                    @Override
                    public Void call() throws Exception
                    {
                        while (true)
                        {
                            final ReportModel reportModel;
                            WindupVertexFrame reportModelObject = reportModels.remove();
                            if (reportModelObject == null)
                                return null;
                            reportModel = (ReportModel) reportModelObject;

                            try
                            {
                                Thread.currentThread().setName(reportModel.getTemplatePath() + "_" + reportModel.getReportFilename());
                                iterationProgress.perform(event, context);
                                freeMarkerIterationOperation.perform(event, context, reportModel);
                            }
                            catch (Throwable t)
                            {
                                LOG.log(Level.WARNING, "Failed to render freemarker report:\n    " + reportModel + "\n    " + t.getMessage(), t);
                            }
                        }
                    }
                });
            }
            executorService.shutdown();
            try
            {
                executorService.awaitTermination(2, TimeUnit.DAYS);
            }
            catch (InterruptedException e)
            {
                throw new WindupException("Failed to render reports due to a timeout: " + e.getMessage(), e);
            }

            // reset them
            event.getRewriteContext().put(Iteration.DEFAULT_VARIABLE_LIST_STRING, null);
        }
    }
}
