/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.ivy.ant;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import org.apache.ivy.Ivy;
import org.apache.ivy.core.cache.ResolutionCacheManager;
import org.apache.ivy.core.module.descriptor.DefaultModuleDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleId;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.core.report.ResolveReport;
import org.apache.ivy.core.resolve.ResolveOptions;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.plugins.matcher.PatternMatcher;
import org.apache.ivy.plugins.report.XmlReportOutputter;
import org.apache.ivy.util.FileUtil;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.taskdefs.XSLTProcess;

/**
 * Generates a report of dependencies of a set of modules in the repository. The set of modules is
 * specified using organisation/module and matcher.
 */
public class IvyRepositoryReport extends IvyTask {
    private String organisation = "*";

    private String module;

    private String branch;

    private String revision = "latest.integration";

    private String matcher = PatternMatcher.EXACT_OR_REGEXP;

    private File todir;

    private boolean graph = false;

    private boolean dot = false;

    private boolean xml = true;

    private boolean xsl = false;

    private String xslFile;

    private String outputname = "ivy-repository-report";

    private String xslext = "html";

    private final List<XSLTProcess.Param> params = new ArrayList<>();

    public void doExecute() throws BuildException {
        Ivy ivy = getIvyInstance();
        IvySettings settings = ivy.getSettings();
        if (xsl && xslFile == null) {
            throw new BuildException("xsl file is mandatory when using xsl generation");
        }
        if (module == null && PatternMatcher.EXACT.equals(matcher)) {
            throw new BuildException("no module name provided for ivy repository graph task: "
                    + "It can either be set explicitly via the attribute 'module' or "
                    + "via 'ivy.module' property or a prior call to <resolve/>");
        } else if (module == null && !PatternMatcher.EXACT.equals(matcher)) {
            module = PatternMatcher.ANY_EXPRESSION;
        }
        ModuleRevisionId moduleRevisionId = ModuleRevisionId.newInstance(organisation, module, revision);

        try {
            ModuleRevisionId criteria = (revision == null) || settings.getVersionMatcher().isDynamic(moduleRevisionId)
                    ? new ModuleRevisionId(new ModuleId(organisation, module), branch, "*")
                    : new ModuleRevisionId(new ModuleId(organisation, module), branch, revision);

            ModuleRevisionId[] mrids = ivy.listModules(criteria, settings.getMatcher(matcher));

            // replace all found revisions with the original requested revision
            Set<ModuleRevisionId> modules = new HashSet<>();
            for (ModuleRevisionId mrid : mrids) {
                modules.add(ModuleRevisionId.newInstance(mrid, revision));
            }

            mrids = modules.toArray(new ModuleRevisionId[modules.size()]);
            ModuleDescriptor md = DefaultModuleDescriptor.newCallerInstance(mrids, true, false);
            String resolveId = ResolveOptions.getDefaultResolveId(md);
            ResolveReport report = ivy.resolve(md, new ResolveOptions().setResolveId(resolveId)
                    .setValidate(doValidate(settings)));

            ResolutionCacheManager cacheMgr = getIvyInstance().getResolutionCacheManager();
            new XmlReportOutputter().output(report, cacheMgr, new ResolveOptions());
            if (graph) {
                gengraph(cacheMgr, md.getModuleRevisionId().getOrganisation(),
                        md.getModuleRevisionId().getName());
            }
            if (dot) {
                gendot(cacheMgr, md.getModuleRevisionId().getOrganisation(),
                        md.getModuleRevisionId().getName());
            }
            if (xml) {
                FileUtil.copy(cacheMgr.getConfigurationResolveReportInCache(resolveId, "default"),
                    new File(getTodir(), outputname + ".xml"), null);
            }
            if (xsl) {
                genreport(cacheMgr, md.getModuleRevisionId().getOrganisation(),
                        md.getModuleRevisionId().getName());
            }
        } catch (Exception e) {
            throw new BuildException("impossible to generate graph for " + moduleRevisionId + ": " + e, e);
        }
    }

    private void genreport(ResolutionCacheManager cache, String organisation, String module) {
        // first process the report with xslt
        XSLTProcess xslt = new XSLTProcess();
        xslt.setTaskName(getTaskName());
        xslt.setProject(getProject());
        xslt.init();

        String resolveId = ResolveOptions.getDefaultResolveId(new ModuleId(organisation, module));
        xslt.setIn(cache.getConfigurationResolveReportInCache(resolveId, "default"));
        xslt.setOut(new File(getTodir(), outputname + "." + xslext));

        xslt.setStyle(xslFile);

        XSLTProcess.Param xslExt = xslt.createParam();
        xslExt.setName("extension");
        xslExt.setExpression(xslext);

        // add the provided XSLT parameters
        for (XSLTProcess.Param param : params) {
            XSLTProcess.Param realParam = xslt.createParam();
            realParam.setName(param.getName());
            realParam.setExpression(param.getExpression());
        }

        xslt.execute();
    }

    private void gengraph(ResolutionCacheManager cache, String organisation, String module)
            throws IOException {
        gen(cache, organisation, module, getGraphStylePath(cache.getResolutionCacheRoot()),
            "graphml");
    }

    private String getGraphStylePath(File cache) throws IOException {
        // style should be a file (and not an url)
        // so we have to copy it from classpath to cache
        File style = new File(cache, "ivy-report-graph-all.xsl");
        FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report-graph-all.xsl"),
            style, null);
        return style.getAbsolutePath();
    }

    private void gendot(ResolutionCacheManager cache, String organisation, String module)
            throws IOException {
        gen(cache, organisation, module, getDotStylePath(cache.getResolutionCacheRoot()), "dot");
    }

    private String getDotStylePath(File cache) throws IOException {
        // style should be a file (and not an url)
        // so we have to copy it from classpath to cache
        File style = new File(cache, "ivy-report-dot-all.xsl");
        FileUtil.copy(XmlReportOutputter.class.getResourceAsStream("ivy-report-dot-all.xsl"),
            style, null);
        return style.getAbsolutePath();
    }

    private void gen(ResolutionCacheManager cache, String organisation, String module,
            String style, String ext) {
        XSLTProcess xslt = new XSLTProcess();
        xslt.setTaskName(getTaskName());
        xslt.setProject(getProject());
        xslt.init();

        String resolveId = ResolveOptions.getDefaultResolveId(new ModuleId(organisation, module));
        xslt.setIn(cache.getConfigurationResolveReportInCache(resolveId, "default"));
        xslt.setOut(new File(getTodir(), outputname + "." + ext));
        xslt.setBasedir(cache.getResolutionCacheRoot());
        xslt.setStyle(style);
        xslt.execute();
    }

    public File getTodir() {
        if (todir == null && getProject() != null) {
            return getProject().getBaseDir();
        }
        return todir;
    }

    public void setTodir(File todir) {
        this.todir = todir;
    }

    public boolean isGraph() {
        return graph;
    }

    public void setGraph(boolean graph) {
        this.graph = graph;
    }

    public String getXslfile() {
        return xslFile;
    }

    public void setXslfile(String xslFile) {
        this.xslFile = xslFile;
    }

    public boolean isXml() {
        return xml;
    }

    public void setXml(boolean xml) {
        this.xml = xml;
    }

    public boolean isXsl() {
        return xsl;
    }

    public void setXsl(boolean xsl) {
        this.xsl = xsl;
    }

    public String getXslext() {
        return xslext;
    }

    public void setXslext(String xslext) {
        this.xslext = xslext;
    }

    public XSLTProcess.Param createParam() {
        XSLTProcess.Param result = new XSLTProcess.Param();
        params.add(result);
        return result;
    }

    public String getOutputname() {
        return outputname;
    }

    public void setOutputname(String outputpattern) {
        outputname = outputpattern;
    }

    public void setCache(File cache) {
        cacheAttributeNotSupported();
    }

    public String getMatcher() {
        return matcher;
    }

    public void setMatcher(String matcher) {
        this.matcher = matcher;
    }

    public String getModule() {
        return module;
    }

    public void setModule(String module) {
        this.module = module;
    }

    public String getOrganisation() {
        return organisation;
    }

    public void setOrganisation(String organisation) {
        this.organisation = organisation;
    }

    public String getRevision() {
        return revision;
    }

    public void setRevision(String revision) {
        this.revision = revision;
    }

    public String getBranch() {
        return branch;
    }

    public void setBranch(String branch) {
        this.branch = branch;
    }

    public boolean isDot() {
        return dot;
    }

    public void setDot(boolean dot) {
        this.dot = dot;
    }
}
