package org.jboss.windup.rules.apps.java.dependencyreport;

import java.util.HashMap;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.jboss.windup.config.AbstractRuleProvider;
import org.jboss.windup.config.GraphRewrite;
import org.jboss.windup.config.metadata.RuleMetadata;
import org.jboss.windup.config.operation.GraphOperation;
import org.jboss.windup.config.phase.ReportGenerationPhase;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.ArchiveModel;
import org.jboss.windup.graph.model.DuplicateArchiveModel;
import org.jboss.windup.graph.model.ProjectModel;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.model.resource.FileModel;
import org.jboss.windup.graph.service.ProjectService;
import org.jboss.windup.graph.service.WindupConfigurationService;
import org.jboss.windup.graph.traversal.ProjectModelTraversal;
import org.jboss.windup.reporting.model.TemplateType;
import org.jboss.windup.reporting.service.ApplicationReportService;
import org.jboss.windup.reporting.service.ReportService;
import org.ocpsoft.rewrite.config.Configuration;
import org.ocpsoft.rewrite.config.ConfigurationBuilder;
import org.ocpsoft.rewrite.context.EvaluationContext;

/**
 * Creates a report of JAR dependencies.
 *
 * @author <a href="mailto:jesse.sightler@gmail.com">Jesse Sightler</a>
 */
@RuleMetadata(phase = ReportGenerationPhase.class, id = "Create Java Dependency Report")
public class CreateDependencyReportRuleProvider extends AbstractRuleProvider
{
    public static final String REPORT_NAME = "Dependencies";
    public static final String TEMPLATE = "/reports/templates/dependency_report.ftl";
    public static final String REPORT_DESCRIPTION = "This report displays all Java packaged dependencies found within the application.";

    @Override
    public Configuration getConfiguration(GraphContext context)
    {
        // @formatter:off
        return ConfigurationBuilder.begin()
        .addRule()
        .perform(new GraphOperation()
        {
            @Override
            public void perform(GraphRewrite event, EvaluationContext context)
            {
                // configuration of current execution
                WindupConfigurationModel configurationModel = WindupConfigurationService.getConfigurationModel(event.getGraphContext());

                int count = 0;
                for (FileModel inputPath : configurationModel.getInputPaths())
                {
                    ProjectModel projectModel = inputPath.getProjectModel();
                    createReport(event.getGraphContext(), projectModel);
                    count++;
                }

                // only create a global report if there is more than one application
                if (count > 1)
                    createGlobalReport(event.getGraphContext(), configurationModel);
            }

            @Override
            public String toString()
            {
                return "CreateDependencyReport";
            }
        });
        // @formatter:on
    }

    private void addAll(GraphContext context,
                DependenciesReportModel reportModel,
                ProjectModelTraversal traversal,
                Map<String, DependencyReportDependencyGroupModel> groupsBySHA1)
    {
        FileModel rootFileModel = traversal.getCurrent().getRootFileModel();

        // Don't create a dependency entry for the entire application (root project)
        boolean isRootProject = traversal.getCurrent().getParentProject() == null;
        if (!isRootProject && rootFileModel instanceof ArchiveModel)
        {
            ArchiveModel archiveModel = (ArchiveModel) rootFileModel;
            ArchiveModel canonicalArchive;
            if (archiveModel instanceof DuplicateArchiveModel)
                canonicalArchive = ((DuplicateArchiveModel) archiveModel).getCanonicalArchive();
            else
                canonicalArchive = archiveModel;

            // 1. Get SHA1
            String sha1 = archiveModel.getSHA1Hash();

            // 2. Get the group model for this sha1
            DependencyReportDependencyGroupModel groupModel = groupsBySHA1.get(sha1);
            if (groupModel == null)
            {
                groupModel = context.service(DependencyReportDependencyGroupModel.class).create();
                groupModel.setSHA1(sha1);
                groupModel.setCanonicalProject(canonicalArchive.getProjectModel());
                reportModel.addArchiveGroup(groupModel);

                groupsBySHA1.put(sha1, groupModel);
            }

            // 3. If the group already has this archive, don't do anything
            String path = traversal.getFilePath(rootFileModel);
            boolean archiveAlreadyLinked = false;
            for (DependencyReportToArchiveEdgeModel groupEdge : groupModel.getArchives())
            {
                if (StringUtils.equals(groupEdge.getFullPath(), path))
                {
                    archiveAlreadyLinked = true;
                    break;
                }
            }

            // Don't add projects that have already been added
            if (!archiveAlreadyLinked)
            {
                DependencyReportToArchiveEdgeModel edge = groupModel.addArchiveModel(archiveModel);
                edge.setFullPath(path);
            }
        }

        for (ProjectModelTraversal child : traversal.getChildren())
            addAll(context, reportModel, child, groupsBySHA1);
    }

    private void createGlobalReport(GraphContext context, WindupConfigurationModel configuration)
    {
        ReportService reportService = new ReportService(context);
        DependenciesReportModel reportModel = createReportModel(context);

        Map<String, DependencyReportDependencyGroupModel> sha1ToGroup = new HashMap<>();
        for (FileModel inputApplication : configuration.getInputPaths())
        {
            ProjectModel projectModel = inputApplication.getProjectModel();

            // Do not include shared libs in the global report as this is not really a user app
            if (StringUtils.equals(projectModel.getUniqueID(), ProjectService.SHARED_LIBS_UNIQUE_ID))
                continue;

            ProjectModelTraversal traversal = new ProjectModelTraversal(inputApplication.getProjectModel());
            addAll(context, reportModel, traversal, sha1ToGroup);
        }

        reportModel.setDisplayInGlobalApplicationIndex(true);
        reportService.setUniqueFilename(reportModel, "dependency_report_global", "html");
    }

    private void createReport(GraphContext context, ProjectModel application)
    {
        ReportService reportService = new ReportService(context);
        DependenciesReportModel reportModel = createReportModel(context);

        addAll(context, reportModel, new ProjectModelTraversal(application), new HashMap<String, DependencyReportDependencyGroupModel>());

        reportModel.setProjectModel(application);
        reportService.setUniqueFilename(reportModel, "dependency_report_" + application.getName(), "html");
    }

    private DependenciesReportModel createReportModel(GraphContext context)
    {
        ApplicationReportService service = new ApplicationReportService(context);
        DependenciesReportModel applicationReportModel = context
                    .service(DependenciesReportModel.class)
                    .addTypeToModel(service.create());

        applicationReportModel.setDisplayInApplicationReportIndex(true);
        applicationReportModel.setReportPriority(120);
        applicationReportModel.setReportName(REPORT_NAME);
        applicationReportModel.setDescription(REPORT_DESCRIPTION);
        applicationReportModel.setReportIconClass("glyphicon glyphicon-compressed");
        applicationReportModel.setTemplatePath(TEMPLATE);
        applicationReportModel.setTemplateType(TemplateType.FREEMARKER);
        return applicationReportModel;

    }
}
