/*
 * 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.io.IOException;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Target;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Ant;
import org.apache.tools.ant.taskdefs.CallTarget;
import org.apache.tools.ant.taskdefs.Echo;
import org.apache.tools.ant.taskdefs.Property;

/**
 * Insert into the live project some all-* targets.
 * This means they do not need to manually written into nbbuild/build.xml.
 * Targets are only added for projectized modules which do not already
 * have all-* entries in the build script.
 * Generally, an all-* target for a projectized module in a named cluster depends on:
 * 1. 'init'
 * 2. The all-* targets for any module which it lists as <build-prerequisite/>s
 *    in project.xml, but which are not included in the same cluster as this module.
 * Therefore cluster dependencies still have to be manually set.
 * An all-* target for a projectized module without a specific cluster depends on:
 * 1. 'init'
 * 2. The all-* targets for any module which it lists as <build-prerequisite/>s
 *    in project.xml.
 * cluster.properties must have already been read for this task to work.
 * @author Jesse Glick
 */
public final class InsertModuleAllTargets extends Task {
    
    public InsertModuleAllTargets() {}
    
    boolean checkModules = false;
    public void setCheckModules( boolean check ) {
        checkModules = check;
    }

    boolean useClusters = true;
    public void setUseClusters(boolean b) {
        useClusters = b;
    }
    
    public @Override void execute() throws BuildException {
        try {
            Project prj = getProject();
            @SuppressWarnings("unchecked")
            Set<String> existingTargets = prj.getTargets().keySet();
            if (existingTargets.contains("all-openide.util")) {
                log("Already seem to have inserted targets into this project; will not do it twice", Project.MSG_VERBOSE);
                return;
            }
            Hashtable<String,Object> props = prj.getProperties();

            if (checkModules) {
                boolean missingModules = false;
                String[] clusters = ((String) props.get("nb.clusters.list")).split(", *");
                String nb_all = (String) props.get("nb_all");
                if (nb_all == null)
                    throw new BuildException("Can't file 'nb_all' property, probably not in the NetBeans build system");
                File nbRoot = new File(nb_all);
                for( String cluster: clusters) {
                    if (props.get(cluster) == null) 
                        throw new BuildException("Cluster '"+cluster+"' has got empty list of modules. Check configuration of that cluster.",getLocation());
                    String[] clusterModules = ((String) props.get(cluster)).split(", *");
                    for( String module: clusterModules) {
                        File moduleBuild = new File(nbRoot, module + File.separator + "build.xml");
                        if (!moduleBuild.exists() || !moduleBuild.isFile()) {
                            String clusterDir = (String) props.get(cluster + ".dir");
                            File subModuleBuild = new File(new File(nbRoot, clusterDir), module + File.separator + "build.xml");
                            if (!subModuleBuild.exists() || !subModuleBuild.isFile()) {
                                missingModules = true;
                                log("This module is missing from checkout: " + module + " - at least can't find: " + moduleBuild.getAbsolutePath());
                            }
                        }
                    }
                }
                if (missingModules) {
                    String clusterConfig = (String) props.get("cluster.config");
                    throw new BuildException("Some modules according your cluster config '" + clusterConfig + "' are missing from checkout, see messages above.",getLocation());
                }
            }
            
            Map<String,String> clustersOfModules = new HashMap<>();
            if (useClusters) {
                for (Map.Entry<String,Object> pair : props.entrySet()) {
                    String cluster = pair.getKey();
                    if (!cluster.startsWith("nb.cluster.") || cluster.endsWith(".depends") || cluster.endsWith(".dir")) {
                        continue;
                    }
                    for (String module : ((String) pair.getValue()).split(", *")) {
                        clustersOfModules.put(module, cluster);
                    }
                }
            }
            ModuleListParser mlp = new ModuleListParser(props, ModuleType.NB_ORG, prj);
            SortedMap<String,ModuleListParser.Entry> entries = new TreeMap<>();
            for (ModuleListParser.Entry entry : mlp.findAll()) {
                String path = entry.getNetbeansOrgPath();
                if (path == null) continue; // It is taken from binary
                entries.put(path, entry);
            }
           
            for (ModuleListParser.Entry entry : entries.values()) {
                String path = entry.getNetbeansOrgPath();
                assert path != null : entry;
                String trg = "all-" + entry.getNetbeansOrgId();
                if (existingTargets.contains(trg)) {
                    log("Not adding target " + trg + " because one already exists", Project.MSG_INFO);
                    continue;
                }
                String[] prereqsAsCnb = entry.getBuildPrerequisites();
                StringBuffer namedDeps = new StringBuffer("init");
                String myCluster = clustersOfModules.get(path);
                for (String cnb : prereqsAsCnb ) {
                    ModuleListParser.Entry other = mlp.findByCodeNameBase(cnb);
                    if (other == null) {
                        log("Cannot find build prerequisite " + cnb + " of " + entry, Project.MSG_WARN);
                        continue;
                    }
                    String otherId = other.getNetbeansOrgId();
                    if (otherId == null) continue; // Do not add the all-module dependency for module which is in the binaries
                    String otherCluster = clustersOfModules.get(otherId);
                    if (myCluster == null || otherCluster == null || myCluster.equals(otherCluster)) {
                        namedDeps.append(",all-");
                        namedDeps.append(otherId);
                    }
                }
                String namedDepsS = namedDeps.toString();
                log("Adding target " + trg + " with depends=\"" + namedDepsS + "\"", Project.MSG_VERBOSE);
                Target t = new Target();
                t.setName(trg);
                t.setLocation(getLocation());
                t.setDepends(namedDepsS);
                prj.addTarget(t);
                if (myCluster != null) {
                    CallTarget call = (CallTarget) prj.createTask("antcall");
                    call.setTarget("build-one-cluster-dependencies");
                    call.setInheritAll(false);
                    Property param = call.createParam();
                    param.setName("one.cluster.dependencies");
                    param.setValue(props.get(myCluster + ".depends"));
                    param = call.createParam();
                    param.setName("one.cluster.name");
                    param.setValue("this-cluster");
                    t.addTask(call);
                }
                Echo echo = (Echo) prj.createTask("echo");
                echo.setMessage("Building " + path + "...");
                t.addTask(echo);
                Ant ant = (Ant) prj.createTask("ant");
                ant.setDir(prj.resolveFile("../" + path));
                ant.setTarget("netbeans");
                Property property = ant.createProperty();
                property.setName(ParseProjectXml.DO_NOT_RECURSE);
                property.setValue("true");
                t.addTask(ant);
            }
        } catch (IOException e) {
            throw new BuildException(e.toString(), e, getLocation());
        }
    }
}
