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

import java.io.FileWriter;
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.loader.RuleLoaderContext;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.operation.GraphOperation;
import org.jboss.windup.config.phase.PostFinalizePhase;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.model.performance.RulePhaseExecutionStatisticsModel;
import org.jboss.windup.graph.model.performance.RuleProviderExecutionStatisticsModel;
import org.jboss.windup.graph.service.GraphService;
import org.jboss.windup.graph.service.RuleProviderExecutionStatisticsService;
import org.jboss.windup.graph.service.WindupConfigurationService;
import org.jboss.windup.util.ExecutionStatistics;
import org.jboss.windup.util.PathUtil;
import org.jboss.windup.util.exception.WindupException;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;

/**
 * Produces a simple text report of how long each RuleProvider's rule took to execute, the time spent in each phase, and
 * any other timing data that was been stored in {@link ExecutionStatistics}.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 *
 * This is basically a reporting rule, but we execute it during finalize in order to also report on
 * the time it took to generate reports.
 */
@RuleMetadata(phase = PostFinalizePhase.class)
public class ExecutionTimeReportRuleProvider extends AbstractRuleProvider
{
    @Override
    public Configuration getConfiguration(RuleLoaderContext ruleLoaderContext)
    {
        return ConfigurationBuilder.begin()
        .addRule()
        .perform(new GraphOperation()
        {
            @Override
            public void perform(GraphRewrite event, EvaluationContext context)
            {
                WindupConfigurationModel cfg = WindupConfigurationService.getConfigurationModel(event.getGraphContext());
                String outputDir = cfg.getOutputPath().getFilePath();

                // create a directory for the output
                Path statsDir = Paths.get(outputDir, "stats");
                FileUtils.deleteQuietly(statsDir.toFile());
                PathUtil.createDirectory(statsDir, "stats folder");

                Path detailedExecutionStatsOutputPath = statsDir.resolve("detailed_stats.csv");
                ExecutionStatistics.get().serializeTimingData(detailedExecutionStatsOutputPath);

                Path ruleTimingOutputPath = statsDir.resolve("timing.txt");
                try (FileWriter fw = new FileWriter(ruleTimingOutputPath.toFile()))
                {
                    Iterable<RuleProviderExecutionStatisticsModel> ruleProviderStatModels =
                            new RuleProviderExecutionStatisticsService(event.getGraphContext()).findAllOrderedByIndex();

                    // rule execution timings
                    fw.write("-----------------------------------------------------------\n");
                    fw.write("Rule execution timings:\n\n");
                    for (RuleProviderExecutionStatisticsModel model : ruleProviderStatModels)
                    {
                        int ms = model.getTimeTaken();
                        fw.write(String.format("% 5d.%03d, %s\n", ms / 1000, ms % 1000, model.getRuleProviderID()));
                    }
                    fw.write("-----------------------------------------------------------\n\n");

                    // phase execution timings
                    fw.write("Phase execution timings:\n\n");
                    GraphService<RulePhaseExecutionStatisticsModel> statsByPhaseService =
                        new GraphService<>(event.getGraphContext(), RulePhaseExecutionStatisticsModel.class);
                    Iterable<RulePhaseExecutionStatisticsModel> rulePhaseStatModelIterable = statsByPhaseService.findAll();
                    List<RulePhaseExecutionStatisticsModel> rulePhaseStatModelList = new ArrayList<>();
                    for (RulePhaseExecutionStatisticsModel model : rulePhaseStatModelIterable)
                    {
                        rulePhaseStatModelList.add(model);
                    }
                    Collections.sort(rulePhaseStatModelList, RulePhaseExecutionStatisticsModel.BY_ORDER_EXECUTED);

                    for (RulePhaseExecutionStatisticsModel model : rulePhaseStatModelList)
                    {
                        int ms = model.getTimeTaken();
                        fw.write(String.format("% 6d.%03d, %s\n", ms / 1000, ms % 1000, model.getRulePhase()));
                    }
                    fw.write("-----------------------------------------------------------\n");
                }
                catch (IOException e)
                {
                    throw new WindupException("Error creating output file: " + ruleTimingOutputPath
                                + " due to: " + e.getMessage(), e);
                }

            }

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