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

import java.io.File;
import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.List;
import java.util.Set;

import javax.inject.Inject;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.jboss.forge.furnace.Furnace;
import org.jboss.forge.furnace.addons.Addon;
import org.jboss.forge.furnace.addons.AddonDependency;
import org.jboss.forge.furnace.addons.AddonFilter;
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.ReportRenderingPhase;
import org.jboss.windup.graph.GraphContext;
import org.jboss.windup.graph.model.WindupConfigurationModel;
import org.jboss.windup.graph.service.WindupConfigurationService;
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;

@RuleMetadata(phase = ReportRenderingPhase.class)
public class CssJsResourceRenderingRuleProvider extends AbstractRuleProvider {
    @Inject
    private Addon addon;
    @Inject
    private Furnace furnace;

    @Override
    public Configuration getConfiguration(RuleLoaderContext ruleLoaderContext) {
        GraphOperation copyCssOperation = new GraphOperation() {
            @Override
            public void perform(GraphRewrite event, EvaluationContext context) {
                final WindupConfigurationModel cfg = WindupConfigurationService.getConfigurationModel(event
                        .getGraphContext());
                copyCssResourcesToOutput(event.getGraphContext());
            }

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

        Configuration configuration = ConfigurationBuilder.begin()
                .addRule()
                .perform(copyCssOperation);
        return configuration;
    }

    private void copyCssResourcesToOutput(GraphContext context) {
        ReportService reportService = new ReportService(context);
        Path outputPath = reportService.getReportDirectory().resolve("resources");

        // iterate through the addons to scan
        for (Addon addonToScan : getAddonsToScan()) {
            List<File> addonResources = addon.getRepository().getAddonResources(addonToScan.getId());
            for (File addonResource : addonResources) {
                try {
                    if (addonResource.isDirectory()) {
                        Path addonReportsResourcesPath = addonResource.toPath().resolve("reports").resolve("resources");
                        if (Files.isDirectory(addonReportsResourcesPath)) {
                            recursePath(addonReportsResourcesPath, outputPath);
                        }
                    } else {
                        try (FileSystem fs = FileSystems.newFileSystem(addonResource.toPath(),
                                addonToScan.getClassLoader())) {
                            Path p = fs.getPath("reports", "resources");
                            try {
                                recursePath(p, outputPath);
                            } catch (NoSuchFileException e) {
                                // ignore ... this just means this archive did not contain report resources
                            }
                        }
                    }
                } catch (Exception e) {
                    throw new RuntimeException("Exception reading resource.", e);
                }
            }
        }
    }

    private void recursePath(final Path path, final Path resultPath) throws IOException {
        Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
            @Override
            public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                String relativePath = StringUtils.substringAfter(file.toString(), path.toString());

                // needed on windows, as for some reason the path from a zip still uses s'/' sometimes
                relativePath = FilenameUtils.separatorsToSystem(relativePath);

                relativePath = StringUtils.removeStart(relativePath, File.separator);
                Path resultFile = resultPath.resolve(relativePath);

                FileUtils.forceMkdir(resultFile.getParent().toFile());
                Files.copy(file, resultFile, StandardCopyOption.REPLACE_EXISTING);
                return FileVisitResult.CONTINUE;
            }
        });
    }

    /**
     * Get all {@link Addon} instances that depend on the reporting addon
     */
    private Set<Addon> getAddonsToScan() {
        AddonFilter filter = new AddonFilter() {
            @Override
            public boolean accept(Addon addon) {
                // make sure to include ourselves as well (even though we don't technically depend on ourselves)
                return addonDependsOnReporting(addon) || addon.equals(CssJsResourceRenderingRuleProvider.this.addon);
            }
        };

        return furnace.getAddonRegistry().getAddons(filter);
    }

    /**
     * Returns true if the addon depends on reporting.
     */
    private boolean addonDependsOnReporting(Addon addon) {
        for (AddonDependency dep : addon.getDependencies()) {
            if (dep.getDependency().equals(this.addon)) {
                return true;
            }
            boolean subDep = addonDependsOnReporting(dep.getDependency());
            if (subDep) {
                return true;
            }
        }
        return false;
    }

}
