/*
 *  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.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.ivy.core.module.descriptor.Artifact;
import org.apache.ivy.core.module.descriptor.DefaultArtifact;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.resolver.URLResolver;
import org.apache.ivy.plugins.resolver.util.ResolvedResource;
import org.apache.ivy.util.FileUtil;
import org.apache.ivy.util.Message;

/**
 * Resolver that performs a "build" operation to resolve artifacts.
 *
 * <p>
 * The resolver is configured with a base URL, from which the "ivy.xml" and "packager.xml" files are
 * resolved. The latter file contains instructions describing how to build the actual artifacts.
 * </p>
 */
public class PackagerResolver extends URLResolver {

    private static final String PACKAGER_ARTIFACT_NAME = "packager";

    private static final String PACKAGER_ARTIFACT_TYPE = "packager";

    private static final String PACKAGER_ARTIFACT_EXT = "xml";

    private final Map<ModuleRevisionId, PackagerCacheEntry> packagerCache = new HashMap<>();

    private File buildRoot;

    private File resourceCache;

    private String resourceURL;

    private final Map<String, String> properties = new LinkedHashMap<>();

    private boolean validate = true;

    private boolean preserve;

    private boolean restricted = true;

    private boolean verbose;

    private boolean quiet;

    public PackagerResolver() {
        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                clearCache();
            }
        });
    }

    protected synchronized void clearCache() {
        if (this.preserve) {
            return;
        }
        for (PackagerCacheEntry entry : packagerCache.values()) {
            entry.cleanup();
        }
        packagerCache.clear();
        if (this.buildRoot != null) {
            FileUtil.forceDelete(this.buildRoot);
        }
    }

    /**
     * Set root directory under which builds take place.
     *
     * @param buildRoot File
     */
    public void setBuildRoot(File buildRoot) {
        this.buildRoot = buildRoot;
    }

    /**
     * Returns root directory under which builds take place.
     *
     * @return File
     */
    public File getBuildRoot() {
        return buildRoot;
    }

    /**
     * Set resource cache directory.
     *
     * @param resourceCache File
     */
    public void setResourceCache(File resourceCache) {
        this.resourceCache = resourceCache;
    }

    /**
     * Get resource cache directory.
     *
     * @return File
     */
    public File getResourceCache() {
        return resourceCache;
    }

    /**
     * Set base resource override URL pattern.
     *
     * @param resourceURL String
     */
    public void setResourceURL(String resourceURL) {
        this.resourceURL = resourceURL;
    }

    /**
     * Set pattern for locating "packager.xml" files.
     *
     * @param pattern String
     */
    public void setPackagerPattern(String pattern) {
        List<String> list = new ArrayList<>();
        list.add(pattern);
        setArtifactPatterns(list);
    }

    /**
     * Set whether to preserve build directories. Default is false.
     *
     * @param preserve boolean
     */
    public void setPreserveBuildDirectories(boolean preserve) {
        this.preserve = preserve;
    }

    /**
     * Set whether to enable restricted mode. Default is true.
     *
     * @param restricted boolean
     */
    public void setRestricted(boolean restricted) {
        this.restricted = restricted;
    }

    /**
     * Set whether to run ant with the -verbose flag. Default is false.
     *
     * @param verbose boolean
     */
    public void setVerbose(boolean verbose) {
        this.verbose = verbose;
    }

    /**
     * Set whether to run ant with the -quiet flag. Default is false.
     *
     * @param quiet boolean
     */
    public void setQuiet(boolean quiet) {
        this.quiet = quiet;
    }

    /**
     * Set whether to validate downloaded packager.xml files. Default is true.
     *
     * @param validate boolean
     */
    public void setValidate(boolean validate) {
        this.validate = validate;
    }

    public void setAllownomd(boolean b) {
        Message.error("allownomd not supported by resolver " + this);
    }

    public void setDescriptor(String rule) {
        if (DESCRIPTOR_OPTIONAL.equals(rule)) {
            Message.error("descriptor=\"" + DESCRIPTOR_OPTIONAL + "\" not supported by resolver "
                    + this);
            return;
        }
        super.setDescriptor(rule);
    }

    /**
     * Sets a property to be passed to the child Ant build responsible for packaging the dependency.
     *
     * @param propertyKey
     *            the property to pass
     * @param propertyValue
     *            the value of the property to pass
     */
    public void setProperty(String propertyKey, String propertyValue) {
        properties.put(propertyKey, propertyValue);
    }

    // @Override
    public void validate() {
        super.validate();
        if (this.buildRoot == null) {
            throw new IllegalStateException("no buildRoot specified");
        }
        if (getArtifactPatterns().size() == 0) {
            throw new IllegalStateException("no packager pattern specified");
        }
    }

    // @Override
    public synchronized ResolvedResource findArtifactRef(Artifact artifact, Date date) {

        // For our special packager.xml file, defer to superclass
        if (PACKAGER_ARTIFACT_NAME.equals(artifact.getName())
                && PACKAGER_ARTIFACT_TYPE.equals(artifact.getType())
                && PACKAGER_ARTIFACT_EXT.equals(artifact.getExt())) {
            return super.findArtifactRef(artifact, date);
        }

        // Check the cache
        ModuleRevisionId mr = artifact.getModuleRevisionId();
        PackagerCacheEntry entry = packagerCache.get(mr);

        // Ignore invalid entries
        if (entry != null && !entry.isBuilt()) {
            packagerCache.remove(mr);
            entry.cleanup();
            entry = null;
        }

        // Build the artifacts (if not done already)
        if (entry == null) {
            ResolvedResource packager = findArtifactRef(new DefaultArtifact(mr, null,
                    PACKAGER_ARTIFACT_NAME, PACKAGER_ARTIFACT_TYPE, PACKAGER_ARTIFACT_EXT), date);
            if (packager == null) {
                return null;
            }
            entry = new PackagerCacheEntry(mr, this.buildRoot, this.resourceCache,
                    this.resourceURL, this.validate, this.preserve, this.restricted, this.verbose,
                    this.quiet);
            try {
                entry.build(packager.getResource(), properties);
            } catch (IOException e) {
                throw new RuntimeException("can't build artifact " + artifact, e);
            }
            packagerCache.put(mr, entry);
        }

        // Return reference to desired artifact
        return entry.getBuiltArtifact(artifact);
    }

    public String getTypeName() {
        return "packager";
    }
}
