/*
 *  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.plugins.resolver.packager;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Map;

import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.repository.Resource;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.Message;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.BuildLogger;
import org.apache.tools.ant.DefaultLogger;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectHelper;

/**
 * Represents one entry in the cache of a {@link PackagerResolver}.
 */
public class PackagerCacheEntry {

    private final ModuleRevisionId mr;

    private final File dir;

    private final File resourceCache;

    private final String resourceURL;

    private final boolean validate;

    private final boolean preserve;

    private final boolean restricted;

    private final boolean verbose;

    private final boolean quiet;

    private boolean built;

    // CheckStyle:ParameterNumber OFF
    public PackagerCacheEntry(ModuleRevisionId mr, File rootDir, File resourceCache,
            String resourceURL, boolean validate, boolean preserve, boolean restricted,
            boolean verbose, boolean quiet) {
        this.mr = mr;
        this.dir = getSubdir(rootDir, this.mr);
        this.resourceCache = resourceCache;
        this.resourceURL = resourceURL;
        this.validate = validate;
        this.preserve = preserve;
        this.restricted = restricted;
        this.verbose = verbose;
        this.quiet = quiet;
    }

    // CheckStyle:ParameterNumber ON

    /**
     * Attempt to build this entry.
     *
     * @param packagerResource
     *            packager metadata resource
     * @param properties
     *            a map of properties to pass to the child Ant build responsible for dependency
     *            packaging
     *
     * @throws IOException
     *             if this entry has already been built
     */
    public synchronized void build(Resource packagerResource, Map<String, String> properties) throws IOException {
        // Sanity check
        if (this.built) {
            throw new IllegalStateException("build in directory `" + this.dir
                    + "' already completed");
        }

        // Remove work directory if it exists (e.g. left over from last time)
        if (this.dir.exists()) {
            if (!cleanup()) {
                throw new IOException("can't remove directory `" + this.dir + "'");
            }
        }

        // Create work directory
        if (!this.dir.mkdirs()) {
            throw new IOException("can't create directory `" + this.dir + "'");
        }

        // Write out packager XML
        InputStream packagerXML = packagerResource.openStream();
        saveFile("packager.xml", packagerXML);

        // Write packager XSLT
        saveFile("packager.xsl");

        // Write packager XSD
        saveFile("packager-1.0.xsd");

        // Write master Ant build file
        saveFile("build.xml");

        // Execute the Ant build file
        Project project = new Project();
        project.init();
        project.setUserProperty("ant.file", new File(dir, "build.xml").getAbsolutePath());
        ProjectHelper.configureProject(project, new File(dir, "build.xml"));
        project.setBaseDir(dir);

        // Configure logging verbosity
        BuildLogger logger = new DefaultLogger();
        logger.setMessageOutputLevel(this.verbose ? Project.MSG_VERBOSE
                : this.quiet ? Project.MSG_WARN : Project.MSG_INFO);
        logger.setOutputPrintStream(System.out);
        logger.setErrorPrintStream(System.err);
        project.addBuildListener(logger);

        // Set properties
        project.setUserProperty("ivy.packager.organisation", ""
                + this.mr.getModuleId().getOrganisation());
        project.setUserProperty("ivy.packager.module", "" + this.mr.getModuleId().getName());
        project.setUserProperty("ivy.packager.revision", "" + this.mr.getRevision());
        project.setUserProperty("ivy.packager.branch", "" + this.mr.getBranch());
        if (this.resourceCache != null) {
            project.setUserProperty("ivy.packager.resourceCache",
                "" + this.resourceCache.getCanonicalPath());
        }
        if (this.resourceURL != null) {
            project.setUserProperty("ivy.packager.resourceURL", "" + getResourceURL());
        }
        if (this.validate) {
            project.setUserProperty("ivy.packager.validate", "true");
        }
        project.setUserProperty("ivy.packager.restricted", "" + this.restricted);
        project.setUserProperty("ivy.packager.quiet", String.valueOf(quiet));
        if (properties != null) {
            for (Map.Entry<String, String> entry : properties.entrySet()) {
                project.setUserProperty(entry.getKey(), entry.getValue());
            }
        }

        // Execute task
        Message.verbose("performing packager resolver build in " + this.dir);
        try {
            project.executeTarget("build");
            this.built = true;
        } catch (BuildException e) {
            Message.verbose("packager resolver build failed: " + e);
            throw e;
        }
    }

    /**
     * Has this entry been successfully built?
     *
     * @return boolean
     */
    public synchronized boolean isBuilt() {
        return this.built;
    }

    /**
     * Get a built artifact.
     *
     * @param artifact ditto
     * @return ResolvedResource
     * @throws IllegalStateException
     *             if this entry's built has not (yet) completed successfully
     */
    public ResolvedResource getBuiltArtifact(Artifact artifact) {
        if (!this.built) {
            throw new IllegalStateException("build in directory `" + this.dir
                    + "' has not yet successfully completed");
        }
        return new ResolvedResource(new BuiltFileResource(this.dir, artifact),
                this.mr.getRevision());
    }

    public synchronized boolean cleanup() {
        this.built = false;
        return FileUtil.forceDelete(this.dir);
    }

    protected void saveFile(String name, InputStream input) throws IOException {
        FileUtil.copy(input, new File(this.dir, name), null);
    }

    protected void saveFile(String name) throws IOException {
        InputStream input = getClass().getResourceAsStream(name);
        if (input == null) {
            throw new IOException("can't find resource `" + name + "'");
        }
        saveFile(name, input);
    }

    // @Override
    protected void finalize() throws Throwable {
        try {
            if (!this.preserve) {
                cleanup();
            }
        } finally {
            super.finalize();
        }
    }

    private String getResourceURL() {
        String baseURL = IvyPatternHelper.substitute(resourceURL, mr.getOrganisation(),
            mr.getName(), mr.getRevision(), null, null, null, null,
            mr.getQualifiedExtraAttributes(), null);
        int slash = baseURL.lastIndexOf('/');
        if (slash != -1) {
            baseURL = baseURL.substring(0, slash + 1);
        }
        return baseURL;
    }

    private static File getSubdir(File rootDir, ModuleRevisionId mr) {
        return new File(rootDir, mr.getOrganisation() + File.separatorChar + mr.getName()
                + File.separatorChar + mr.getRevision());
    }
}
