/*
 * 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.netbeans.nbbuild;

import java.io.File;
import java.util.*;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;

/** Pseudo-task to unpack a set of modules.
 * Causes the containing target to both depend on the building of the modules in
 * the first place; and then to unpack them all to a certain location.
 *
 * @author various people
 *
 * 2002-07-31: Rudolf Balada Added build success granularity (Issue 9701),
 *             fixed modules can't fail, "modules" can fail
 */
public class NbMerge extends Task {
    
    private File dest;
    private Vector<String> modules = new Vector<> (); // list of modules defined by build.xml
    private Vector<String> buildmodules = new Vector<> (); // list of modules which will be built
    private Vector<String> fixedmodules = new Vector<> (); // list of fixed modules defined in build.xml
    private Vector<String> buildfixedmodules = new Vector<> (); // List of fixed modules which will be built
    private Vector<String> failedmodules = new Vector<> (); // List of failed modules
    private Vector<String> builtmodules = new Vector<> (); // list of successfully built modules
    private Vector<String> mergemodules = new Vector<> (); // list of successfully built modules
    private Vector<String> builttargets = new Vector<> (); // list of successfully built targets
    private String targetprefix = "all-";    
    private List<File> topdirs = new ArrayList<> ();
    private List<Suppress> suppress = new LinkedList<> ();
    private boolean failonerror = true; // false = enable build success granularity
    private boolean mergedependentmodules = false; // merge also dependent modules
    private String dummyName;
    private Target dummy;
    private Hashtable<String,Target> targets;
    private String builtmodulesproperty = ""; // if set, update property of the name
                                         // to list of successfuly built modules
    
    /** Target directory to unpack to (top of IDE installation). */
    public void setDest (File f) {
        dest = f;
    }

    /** Enable/disable build failing */
    public void setFailOnError (boolean b) {
        failonerror = b;
    }

    /** At the end of task, set system property to the list of successfuly
     *  built modules
     */
    public void setBuiltModulesProperty (String s) {
        builtmodulesproperty = s;
    }
    
    /** Enable/Disable merging also dependencies */
    public void setMergeDependentModules (boolean b) {
        mergedependentmodules = b;
    }
    
    /** Comma-separated list of fixed modules to include. */
    public void setFixedModules (String s) {
        StringTokenizer tok = new StringTokenizer (s, ", ");
        fixedmodules = new Vector<> ();
        while (tok.hasMoreTokens ())
            fixedmodules.addElement (tok.nextToken ());
    }
    
    /** Comma-separated list of modules to include. */
    public void setModules (String s) {
        StringTokenizer tok = new StringTokenizer (s, ", ");
        modules = new Vector<> ();
        while (tok.hasMoreTokens ())
            modules.addElement (tok.nextToken ());
    }
    
    /** String which will have a module name appended to it.
     * This will form a target in the same project which should
     * create the <samp>netbeans/</samp> subdirectory.
     */
    public void setTargetprefix (String s) {
        targetprefix = s;
    }
    
    /** Set the top directory.
     * There should be subdirectories under this for each named module.
     */
    public void setTopdir (File t) {
        topdirs.add (t);
    }

    /** Nested topdir addition. */
    public class Topdir {
        /** Path to an extra topdir. */
        public void setPath (File t) {
            topdirs.add (t);
        }
    }
    /** Add a nested topdir.
     * If there is more than one topdir total, build products
     * may be taken from any of them, including from multiple places
     * for the same module. (Later topdirs may override build
     * products in earlier topdirs.)
     */
    public Topdir createTopdir () {
        return new Topdir ();
    }

    /** Locale to suppress. */
    public class Suppress {
        // [PENDING] also support branding here
        String locale;
        String iftest;
        String unlesstest;
        /** Name of the locale, e.g. <samp>ja</samp>. */
        public void setLocale (String l) {
            locale = l;
        }
        /** Property which if set will enable the suppression. */
        public void setIf (String p) {
            iftest = p;
        }
        /** Property which if set will disable the suppression. */
        public void setUnless (String p) {
            unlesstest = p;
        }
    }
    /** Add a locale to suppress.
     * Files matching this locale suffix will not be merged in.
     * E.g. for the locale <samp>ja</samp>, this will exclude
     * all files and directories ending in <samp>_ja</samp> as well
     * as files ending in <samp>_ja.</samp> plus some extension.
     */
    public Suppress createSuppress () {
        Suppress s = new Suppress ();
        suppress.add (s);
        return s;
    }

    
    /** Execute targets which cannot fail and though throw BuildException */
    private void fixedModulesBuild() throws BuildException {
        // Somewhat convoluted code because Project.executeTargets does not
        // eliminate duplicates when analyzing dependencies! Ecch.
        // build fixed modules first
        dummy = new Target ();
        dummyName = "nbmerge-" + getOwningTarget().getName();
        targets = getProject().getTargets();
        while (targets.contains (dummyName))
            dummyName += "-x";
        dummy.setName (dummyName);
        for (String fixedmodule : buildfixedmodules) {
            dummy.addDependency (targetprefix + fixedmodule);
        }
        getProject().addTarget(dummy);

        getProject().setProperty("fixedmodules-built",  "1" );
        @SuppressWarnings("unchecked")
        Vector<Target> fullList = getProject().topoSort(dummyName, targets);
        // Now remove earlier ones: already done.
        Vector<Target> doneList = getProject().topoSort(getOwningTarget().getName(), targets);
        List<Target> todo = new ArrayList<>(fullList.subList(0, fullList.indexOf(dummy)));
        todo.removeAll(doneList.subList(0, doneList.indexOf(getOwningTarget())));
        log("Going to execute targets " + todo);
        for (Target nexttargit: todo) {
            String targetname = nexttargit.getName();
            if ( builttargets.indexOf(targetname) < 0 ) {
                // XXX poor replacement for Project.fireTargetStarted etc.
                System.out.println(""); System.out.println(targetname + ":");
                try {
                    nexttargit.execute();
                } catch (BuildException ex) {
                    log("Failed to build target: " + targetname, Project.MSG_ERR);
                    throw ex;
                }
                builttargets.addElement(targetname);
            }
        }

        builtmodules.addAll(buildfixedmodules); // add already built fixed modules to the list
        log("fixedmodules=" + buildfixedmodules, Project.MSG_DEBUG);
        log("builtmodules=" + builtmodules, Project.MSG_VERBOSE);
    }
    
    /** Execute targets which can fail _without_ throwing BuildException */
    private void modulesBuild() throws BuildException {
        if ( ! failonerror ) {
            // build the rest of modules
            for (String module : buildmodules) {
                dummy = new Target ();
                dummyName = "nbmerge-" + getOwningTarget().getName() + "-" + module;
                while (targets.contains (dummyName))
                    dummyName += "-x";
                dummy.setName (dummyName);
                dummy.addDependency (targetprefix + module);
                getProject().addTarget(dummy);
                @SuppressWarnings("unchecked")
                Vector<Target> fullList = getProject().topoSort(dummyName, targets);
                // Now remove earlier ones: already done.
                @SuppressWarnings("unchecked")
                Vector<Target> doneList = getProject().topoSort(getOwningTarget().getName(), targets);
                List<Target> todo = new ArrayList<>(fullList.subList(0, fullList.indexOf(dummy)));
                todo.removeAll(doneList.subList(0, doneList.indexOf(getOwningTarget())));
                
                Iterator<Target> targit = todo.iterator();
                try {
                    while (targit.hasNext()) {
                        Target nexttargit = targit.next();
                        String targetname = nexttargit.getName();
                        if ( builttargets.indexOf(targetname) < 0 ) {
                            System.out.println(); System.out.println(targetname + ":");
                            nexttargit.execute();
                            builttargets.addElement(targetname);
                        }
                        
                    }
                    builtmodules.addElement(module);
                } catch (BuildException BE) {
                        log(BE.toString(), Project.MSG_WARN);
                        BE.printStackTrace();
                        failedmodules.addElement(module);
                }
            }
            log("builtmodules=" + builtmodules, Project.MSG_VERBOSE);
            log("failedmodules=" + failedmodules, Project.MSG_VERBOSE);
        }
    }
    
    public void execute () throws BuildException {
        if (topdirs.isEmpty ()) {
            throw new BuildException("You must set at least one topdir attribute", getLocation());
        }

        buildfixedmodules.addAll(fixedmodules);
        buildmodules.addAll(modules);
        
        if (( modules.size() > 0 ) && ( fixedmodules.size() == 0 ) && (! failonerror)) {
            log("Unable to build without fixedmodules set", Project.MSG_WARN);
            log("Swapping modules list with fixedmodules list", Project.MSG_WARN);
            buildfixedmodules.addAll(modules);
            buildmodules.removeAllElements();
        }

        if (( failonerror ) && ( modules.size() > 0 )) {
            // failonerror is enabled => build success granularity is disabled
            // though move all modules to fixedmodules
            buildfixedmodules.addAll(modules);
            buildmodules.removeAllElements();
        } 

        // build of fixed modules
        fixedModulesBuild();
        
        // build of the rest of modules
        modulesBuild();
        
        // final data merging
        dataMerge();

        // display build success status
        if (builtmodules.size() > 0 ) {
            log("builtmodules=" + builtmodules);
            log("builttargets=" + builttargets);
            if (failedmodules.size() > 0 ) {
                log("SOME MODULES FAILED TO BUILD, BUT THEIR BuildException WAS CAUGHT.", Project.MSG_WARN);
                log("failedmodules=" + failedmodules, Project.MSG_WARN);
            }
            
            if ( mergemodules.size() > 0 ) {
                if ( builtmodulesproperty.length() > 0 ) {
                    Vector<String> setmodules = new Vector<>();
                    // add all successfuly built modules
                    setmodules.addAll(mergemodules);
                    // remove all fixed modules (don't put fixed modules to modules list)
                    setmodules.removeAll(fixedmodules);
                    // check if the modules list is equal to mergemodules without fixedmodules
                    if (( ! modules.containsAll(setmodules)) || ( ! setmodules.containsAll(modules))) {
                        String bm = setmodules.toString();
                        bm = bm.substring( 1, bm.length() - 1);
                        if (bm.length() > 0 ) {
                            log("Setting property \"" + builtmodulesproperty + "\" to new value " + bm); //, Project.MSG_VERBOSE);
                            getProject().setUserProperty(builtmodulesproperty, bm);
                        }
                    }
                }
            }
            
        } else {
            throw new BuildException("No modules were built", getLocation());
        }
        
    }

    /** Do final data merge */
    private void dataMerge() throws BuildException {
        List<String> suppressedlocales = new LinkedList<> ();
        for (Suppress s: suppress) {
            if (s.iftest != null && getProject().getProperty(s.iftest) == null) {
                continue;
            } else if (s.unlesstest != null && getProject().getProperty(s.unlesstest) != null) {
                continue;
            }
            log ("Suppressing locale: " + s.locale);
            suppressedlocales.add (s.locale);
        }

        UpdateTracking tr = new UpdateTracking( dest.getAbsolutePath() );
        log ( dest.getAbsolutePath() );
        for(String locale: suppressedlocales) {
          tr.removeLocalized(locale);
        }
    }
}
