/*
 *  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
 *
 *      http://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.core.deliver;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.apache.ivy.core.IvyPatternHelper;
import org.apache.ivy.core.cache.ResolutionCacheManager;
import org.apache.ivy.core.module.descriptor.DependencyDescriptor;
import org.apache.ivy.core.module.descriptor.ModuleDescriptor;
import org.apache.ivy.core.module.id.ModuleRevisionId;
import org.apache.ivy.plugins.parser.xml.UpdateOptions;
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorParser;
import org.apache.ivy.plugins.parser.xml.XmlModuleDescriptorUpdater;
import org.apache.ivy.plugins.report.XmlReportParser;
import org.apache.ivy.util.ConfigurationUtils;
import org.apache.ivy.util.Message;
import org.xml.sax.SAXException;

public class DeliverEngine {
    private DeliverEngineSettings settings;

    public DeliverEngine(DeliverEngineSettings settings) {
        this.settings = settings;
    }

    /**
     * Delivers a resolved ivy file based upon last resolve call status. If resolve report file
     * cannot be found in cache, then it throws an IllegalStateException (maybe resolve has not been
     * called before ?).
     * 
     * @param revision
     *            the revision to which the module should be delivered
     * @param destIvyPattern
     *            the pattern to which the delivered ivy file should be written
     * @param options
     *            the options with which deliver should be done
     */
    public void deliver(String revision, String destIvyPattern, DeliverOptions options)
            throws IOException, ParseException {
        String resolveId = options.getResolveId();
        if (resolveId == null) {
            throw new IllegalArgumentException("A resolveId must be specified for delivering.");
        }
        File[] files = getCache().getConfigurationResolveReportsInCache(resolveId);
        if (files.length == 0) {
            throw new IllegalStateException("No previous resolve found for id '" + resolveId
                    + "' Please resolve dependencies before delivering.");
        }
        XmlReportParser parser = new XmlReportParser();
        parser.parse(files[0]);
        ModuleRevisionId mrid = parser.getResolvedModule();
        deliver(mrid, revision, destIvyPattern, options);
    }

    private ResolutionCacheManager getCache() {
        return settings.getResolutionCacheManager();
    }

    /**
     * Delivers a resolved ivy file based upon last resolve call status. If resolve report file
     * cannot be found in cache, then it throws an IllegalStateException (maybe resolve has not been
     * called before ?).
     * 
     * @param mrid
     *            the module revision id of the module to deliver
     * @param revision
     *            the revision to which the module should be delivered
     * @param destIvyPattern
     *            the pattern to which the delivered ivy file should be written
     * @param options
     *            the options with which deliver should be done
     */
    public void deliver(ModuleRevisionId mrid, String revision, String destIvyPattern,
            DeliverOptions options) throws IOException, ParseException {
        Message.info(":: delivering :: " + mrid + " :: " + revision + " :: " + options.getStatus()
                + " :: " + options.getPubdate());
        Message.verbose("\toptions = " + options);
        long start = System.currentTimeMillis();
        destIvyPattern = settings.substitute(destIvyPattern);

        // 1) find the resolved module descriptor in cache
        File ivyFile = getCache().getResolvedIvyFileInCache(mrid);
        if (!ivyFile.exists()) {
            throw new IllegalStateException("ivy file not found in cache for " + mrid
                    + ": please resolve dependencies before delivering (" + ivyFile + ")");
        }
        ModuleDescriptor md = null;
        URL ivyFileURL = null;
        try {
            ivyFileURL = ivyFile.toURI().toURL();
            md = XmlModuleDescriptorParser.getInstance().parseDescriptor(settings, ivyFileURL,
                options.isValidate());
            md.setResolvedModuleRevisionId(ModuleRevisionId.newInstance(md.getModuleRevisionId(), 
                options.getPubBranch() == null ? mrid.getBranch() : options.getPubBranch(), 
                revision));
            md.setResolvedPublicationDate(options.getPubdate());
        } catch (MalformedURLException e) {
            throw new RuntimeException("malformed url obtained for file " + ivyFile, e);
        } catch (ParseException e) {
            throw new RuntimeException("bad ivy file in cache for " + mrid
                    + ": please clean '" + ivyFile + "' and resolve again", e);
        }

        // 2) parse resolvedRevisions From properties file
        Map resolvedRevisions = new HashMap(); // Map (ModuleId -> String revision)
        Map resolvedBranches = new HashMap(); // Map (ModuleId -> String branch)
        Map dependenciesStatus = new HashMap(); // Map (ModuleId -> String status)
        File ivyProperties = getCache().getResolvedIvyPropertiesInCache(mrid);
        if (!ivyProperties.exists()) {
            throw new IllegalStateException("ivy properties not found in cache for " + mrid
                    + ": please resolve dependencies before delivering (" + ivyFile + ")");
        }
        Properties props = new Properties();
        FileInputStream in = new FileInputStream(ivyProperties);
        props.load(in);
        in.close();

        for (Iterator iter = props.keySet().iterator(); iter.hasNext();) {
            String depMridStr = (String) iter.next();
            String[] parts = props.getProperty(depMridStr).split(" ");
            ModuleRevisionId decodedMrid = ModuleRevisionId.decode(depMridStr);
            if (options.isResolveDynamicRevisions()) {
                resolvedRevisions.put(decodedMrid, parts[0]);
                if (parts.length >= 4) {
                    if (parts[3] != null && !"null".equals(parts[3])) {
                        resolvedBranches.put(decodedMrid, parts[3]);
                    }
                }
            }
            dependenciesStatus.put(decodedMrid, parts[1]);
            
            if (options.isReplaceForcedRevisions()) {
                if (parts.length <= 2) {
                    // maybe the properties file was generated by an older Ivy version
                    // so it is possible that this part doesn't exist.
                    throw new IllegalStateException("ivy properties file generated by an older" +
                    		" version of Ivy which doesn't support replacing forced revisions!");
                }
            
                resolvedRevisions.put(decodedMrid, parts[2]);
            }
        }

        // 3) use pdrResolver to resolve dependencies info
        Map resolvedDependencies = new HashMap(); // Map (ModuleRevisionId -> String revision)
        DependencyDescriptor[] dependencies = md.getDependencies();
        for (int i = 0; i < dependencies.length; i++) {
            String rev = (String) resolvedRevisions.get(dependencies[i].getDependencyRevisionId());
            if (rev == null) {
                rev = dependencies[i].getDependencyRevisionId().getRevision();
            }
            String bra = (String) resolvedBranches.get(dependencies[i].getDependencyRevisionId());
            if (bra == null || "null".equals(bra)) {
                bra = dependencies[i].getDependencyRevisionId().getBranch();
            }
            String depStatus = (String) dependenciesStatus.get(dependencies[i]
                    .getDependencyRevisionId());
            ModuleRevisionId mrid2 = null;
            if (bra == null) {
                mrid2 = ModuleRevisionId.newInstance(dependencies[i].getDependencyRevisionId(), rev);
            }
            else {
                mrid2 = ModuleRevisionId.newInstance(dependencies[i].getDependencyRevisionId(), bra, rev);
            }
            resolvedDependencies.put(dependencies[i].getDependencyRevisionId(), options
                    .getPdrResolver().resolve(
                        md,
                        options.getStatus(),
                        mrid2,
                        depStatus));
        }

        // 4) copy the source resolved ivy to the destination specified,
        // updating status, revision and dependency revisions obtained by
        // PublishingDependencyRevisionResolver
        File publishedIvy = settings.resolveFile(
                    IvyPatternHelper.substitute(destIvyPattern, md.getResolvedModuleRevisionId()));
        Message.info("\tdelivering ivy file to " + publishedIvy);

        String[] confs = ConfigurationUtils.replaceWildcards(options.getConfs(), md);
        Set confsToRemove = new HashSet(Arrays.asList(md.getConfigurationsNames()));
        confsToRemove.removeAll(Arrays.asList(confs));

        try {
            UpdateOptions opts = new UpdateOptions()
            .setSettings(settings)
            .setResolvedRevisions(resolvedDependencies)
            .setStatus(options.getStatus())
            .setRevision(revision)
            .setBranch(options.getPubBranch())
            .setPubdate(options.getPubdate())
            .setGenerateRevConstraint(options.isGenerateRevConstraint())
            .setMerge(options.isMerge())
            .setMergedDescriptor(md)
            .setConfsToExclude((String[]) confsToRemove
                .toArray(new String[confsToRemove.size()]));
            if (!resolvedBranches.isEmpty()) {
                opts = opts.setResolvedBranches(resolvedBranches);
            }
            XmlModuleDescriptorUpdater.update(ivyFileURL, publishedIvy, opts);
        } catch (SAXException ex) {
            throw new RuntimeException("bad ivy file in cache for " + mrid
                    + ": please clean '" + ivyFile + "' and resolve again", ex);
        }

        Message.verbose("\tdeliver done (" + (System.currentTimeMillis() - start) + "ms)");
    }
}
