/* *******************************************************************
 * Copyright (c) 1999-2001 Xerox Corporation, 
 *               2002 Palo Alto Research Center, Incorporated (PARC),
 *               2005-2006 Contributors.
 * All rights reserved. 
 * This program and the accompanying materials are made available 
 * under the terms of the Eclipse Public License v1.0 
 * which accompanies this distribution and is available at 
 * http://www.eclipse.org/legal/epl-v10.html 
 *  
 * Contributors: 
 *     Xerox/PARC     initial implementation 
 * ******************************************************************/

package org.aspectj.internal.build;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import junit.framework.TestCase;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.taskdefs.Java;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Commandline.Argument;
import org.aspectj.internal.tools.ant.taskdefs.BuildModule;
import org.aspectj.internal.tools.ant.taskdefs.Checklics;
import org.aspectj.internal.tools.build.Util;

/**
 * Test our integrated taskdef build.
 * This responds to two environment variables:
 * (1) run.build.tests must be defined before
 *   tests that build the tree (and hence take minutes)
 *   will run;
 * (2) build.config takes the same form as it does for the
 *   builder task, e.g., "useEclipseCompiles" will avoid
 *   recompiling with Javac and adopt classes in the 
 *   {module}/bin directories.
 */
public class BuildModuleTest extends TestCase {

    private static boolean printInfoMessages = false;
    private static boolean printedMessage;
    private static final boolean REMOVE_JARS_AFTER_DEBUGGING = true;
    // to just build one module verbosely
    private static final String[] DEBUGS  
        = {};

    // skip those requiring ajdoc, which requires tools.jar
    // also skip those requiring java5 unless manually set up
    // also skip big ones to avoid slowing the build too much
    private static final String[] SKIPS 
        //= {};
       = {"aspectjtools", "ajdoc", "aspectj5rt", "run-all-junit-tests",
        "ajbrowser", "testing", "testing-drivers", "org.aspectj.ajdt.core", "weaver"};

    private static final String SKIP_MESSAGE = 
        "BuildModuleTest: Define \"run.build.tests\" as a system "
        + "property to run tests to build ";
    private static final String BUILD_CONFIG;
    static {
        String config = null;
        try {
            config = System.getProperty("build.config");
        } catch (Throwable t) {
            // ignore
        }
        BUILD_CONFIG = config;
        if (printInfoMessages) {
            System.out.println("BuildModuleTest build.config: " + config);
        }
    }
        
    ArrayList<File> tempFiles = new ArrayList<File>();
    private File jarDir;
    private boolean deleteJars;
    boolean building;  // must be enabled for tests to run
    
	public BuildModuleTest(String name) {
		super(name);
        building = Boolean.getBoolean("run.build.tests");
	}
    protected void setUp() {
        // change to view whether prior output is used
        deleteJars = true; // todo
    }

    protected void tearDown() throws Exception {
		super.tearDown();
        if (debugging() && !REMOVE_JARS_AFTER_DEBUGGING) {
            if (0 < tempFiles.size()) {
                System.err.println("debugging files left: " + tempFiles);
            }
            return;
        }
        deleteTempFiles();
	}
    
    protected void deleteTempFiles() {
        for (Iterator iter = tempFiles.iterator(); iter.hasNext();) {
            File file = (File) iter.next();
            if (!Util.delete(file)) {
                File[] list = file.listFiles();
                if (!Util.isEmpty(list)) {                
                    StringBuffer sb = new StringBuffer();
                    sb.append("warning: BuildModuleTest unable to delete ");
                    sb.append(file.toString());
                    sb.append("\n"); // XXX platform
                    for (int i = 0; i < list.length; i++) {
                        sb.append("  ");
                        sb.append(list[i].toString());
                        sb.append("\n"); // XXX platform
                    }
                    System.err.println(sb.toString());
                }
            }
        }
    }

    public void testAllJunitTests() {
      checkBuild("run-all-junit-tests");
    }

	public void testBuild() {
        checkBuild("build", 
            Checklics.class.getName(), 
            new String[0], // help message
            true); // ant needed
    }
    
    public void testUtil() {
        checkBuild("util");
    }

    public void testAsm() {
        checkBuild("asm");
    }
    
    public void testRuntime() {
        checkBuild("runtime");
    }

    public void testAspectj5rt() {
        checkBuild("aspectj5rt"); 
    }
   
//    public void testLocalOutOfDate() { 
//        Messager handler = new Messager();
//        File jarDir = new File("c:/home/ws/head/aj-build/jars");
//        File baseDir = new File("c:/home/ws/head");
//        Modules mods = new Modules(baseDir, jarDir, handler);
//        Module module = mods.getModule("ajbrowser");
//        Result r = module.getResult(Result.kind(true, true));
//        r.outOfDate();
//    }
//    public void testAspectj5rtRequired() {
//        File baseDir = new File("..");        
//        Modules modules = new Modules(baseDir, getJarDir(), new Messager());
//        Module module = modules.getModule("aspectj5rt");
//        Result result = module.getResult(Result.kind(true, true));
//        Result[] results = result.getRequired();
//        System.out.println(result.toLongString());
//        System.out.println("results: " + Arrays.asList(results));
//        deleteTempFiles();
//    }

    public void xtestNoDuplicates() {
        File weaverAllJar = null;
        try {
            weaverAllJar = doTask("weaver",true, true, true);
        } catch (Throwable t) {
            System.err.println(getClass() + ".testNoDuplicates() incomplete");
            t.printStackTrace(System.err);
            return;
        }
        String dupError = duplicateEntryError(weaverAllJar);
        weaverAllJar.delete();
        if (null != dupError) {
            fail(dupError);
        }
    }
    public void testAjbrowser() {
        checkBuild("ajbrowser", 
            "org.aspectj.tools.ajbrowser.Main",
            new String[] {"-noExit", "-version"}); // compiler version
    }
    public void testTestingUtils() {
        checkBuild("testing-util"); 
    }

    public void testAjdt() {
        checkBuild("org.aspectj.ajdt.core", 
           "org.aspectj.tools.ajc.Main",
            new String[] { "-noExit", "-version" });
    }//
    public void testTesting() {
        checkBuild("testing", 
            "org.aspectj.testing.util.LangUtilTest", 
            new String[] {"ignored"});
    }
    public void testTestingDrivers() {
        checkBuild("testing-drivers", 
            "org.aspectj.testing.drivers.Harness", 
            new String[] {"-help"});
    }
    public void testWeaver() {
        checkBuild("weaver"); 
    }
    
    // ajdoc relies on tools.jar
    public void testAspectjtools() {
        if (!shouldBuild("aspectjtools")) {
            return;
        }
        File baseDir = new File("..");
        File tempBuildDir = new File(baseDir, "aj-build");
        File distDir = new File(tempBuildDir, "dist");
        File jarDir = new File(tempBuildDir, "jars");
        assertTrue(distDir.canWrite() || distDir.mkdirs());
        File productDir = new File(baseDir.getPath() + "/build/products/tools");
        assertTrue(""+productDir, productDir.canRead());
        checkBuildProduct(productDir, baseDir, distDir, jarDir);
    }

    void checkBuildProduct(File productDir, File baseDir, File distDir, File jarDir) {
        if (!shouldBuild(productDir.getPath())) {
            return;
        }
        assertTrue(null != productDir);
        assertTrue(productDir.canRead());

        checkJavac();

        BuildModule task = new BuildModule();
        Project project = new Project();
        task.setProject(project);
        assertTrue(jarDir.canWrite() || jarDir.mkdirs());
        tempFiles.add(jarDir);
        task.setJardir(new Path(project, jarDir.getAbsolutePath()));
        task.setProductdir(new Path(project, productDir.getAbsolutePath()));
        task.setBasedir(new Path(project, baseDir.getAbsolutePath()));
        task.setDistdir(new Path(project, distDir.getAbsolutePath()));
        task.setFailonerror(true);
        if (null != BUILD_CONFIG) {
            task.setBuildConfig(BUILD_CONFIG);
        }
        //task.setVerbose(true);
        task.setCreateinstaller(true);
        task.execute();
        // now run installer and do product tests?
    }

    File getAntJar() {
        return new File("../lib/ant/lib/ant.jar");
    }
    File getJUnitJar() {
        return new File("../lib/junit/junit.jar");
    }
    
    File getJarDir() {
        if (null == jarDir) {
            File baseDir = new File("../aj-build/");
            if (!baseDir.canWrite()) {
                baseDir = new File(".");
            }
            jarDir = new File(baseDir, "BuildModuleTest-jars");
            tempFiles.add(jarDir);
        }
        if (!jarDir.exists()) {
            assertTrue(jarDir.mkdirs());
        }
        return jarDir;
    }
    
    BuildModule getTask(String module) {
        BuildModule task = new BuildModule();
        Project project = new Project();
        task.setProject(project);
        File jarDir = getJarDir();
        assertTrue(jarDir.canWrite() || jarDir.mkdirs());
        tempFiles.add(jarDir);
        File moduleDir = new File(Util.path("..", module));
        assertTrue(moduleDir.canRead());
        task.setModuledir(new Path(project, moduleDir.getAbsolutePath()));
        task.setJardir(new Path(project, jarDir.getAbsolutePath()));
        task.setFailonerror(true);
        if (null != BUILD_CONFIG) {
            task.setBuildConfig(BUILD_CONFIG);
        }
        return task;
    }
    
    void checkBuild(String module) { 
        checkBuild(module, null, null, false);
    }
    
    void checkBuild(String module, 
        String classname, 
        String[] args) {
        checkBuild(module, classname, args, true);
    }

    boolean shouldBuild(String target) {
        if (null == target) {
            return false;
        }
        if (!building && !printedMessage) {
            System.err.println(SKIP_MESSAGE + target + " (this is the only warning)");
            printedMessage = true;
        }
        if (debugging()) {
            for (int i = 0; i < DEBUGS.length; i++) {
                if (target.equals(DEBUGS[i])) {
                    return true;
                }
            }
            return false;
        } else {
            for (int i = 0; i < SKIPS.length; i++) {
                if (SKIPS[i].equals(target)) {
                    if (printInfoMessages) {
                        System.err.println(target + " skipped build test [" + getClass().getName() + ".shouldBuild(..)]");                
                    }
                    return false;
                }
            }
        }
        return building;
    }
    private static boolean debugging() {
        return ((null != DEBUGS) && (0 < DEBUGS.length));
    }
    private static String duplicateEntryError(File weaverAllJar) {
        ZipFile zipFile = null;
        try {
            zipFile = new ZipFile(weaverAllJar);
            Enumeration e = zipFile.entries();
            ArrayList<String> entryNames = new ArrayList<String>();
            while (e.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) e.nextElement();
                String name = entry.getName();
                if (entryNames.contains(name)) {
                    return "duplicate entry: " + name;
                }
                entryNames.add(name);
            }
        } catch (ZipException e) {
            return "ZipException " + e;
        } catch (IOException e) {
            return "IOException " + e;
        } finally {
            if (null != zipFile) {
                try {
                    zipFile.close();
                } catch (IOException e) {
                    return "IOException closing " + zipFile + ": " + e;
                }
            }
        }
        return null;
    }
    
    private static String name(String module, boolean trimTesting, boolean assemble) {
        return module + (trimTesting?"":"-test") + (assemble?"-all":"");
    }
    private void deleteJar(File jar) {
        if (!deleteJars) {
            return ;
        }
        if (jar.exists()) {
            jar.delete();
        }
        if (jar.exists()) {
            try {
                Thread.sleep(5000);
            } catch (Throwable t) {                
            }
        }
        if (jar.exists()) {
            assertTrue("cannot delete " + jar, jar.delete());
        }
    }
    void checkBuild(String module, 
        String classname, 
        String[] args,
        boolean addAnt) {
        if (!shouldBuild(module)) {
            return;
        }
        assertTrue(null != module);
        checkJavac();
        doTask(module, true, false);
        doTask(module, true, true);
        doTask(module, false, false);
        File jar = doTask(module, false, true, true);

        // verify if possible
        if (null != classname) {
            Java java = new Java();
            Project project = new Project();
            java.setProject(project);
            java.setFailonerror(true);
            Path cp = new Path(project);
            assertTrue(jar.canRead());
            cp.append(new Path(project, jar.getAbsolutePath()));
            if (addAnt) {
                cp.append(new Path(project, getAntJar().getAbsolutePath()));
                cp.append(new Path(project, getJUnitJar().getAbsolutePath()));
            }
            java.setClasspath(cp);
            java.setClassname(classname);
            if (null != args) {
                for (int i = 0; i < args.length; i++) {
                    Argument arg = java.createArg();
                    arg.setValue(args[i]);
                }
            }
            try {
                java.execute();
            } catch (BuildException e) {
                e.printStackTrace(System.err);
                assertTrue("BuildException running " + classname, false);
            }
        }
        deleteJar(jar);        
    }
    void doTask(String module, boolean trimTesting, boolean assembleAll) {
        doTask(module, trimTesting, assembleAll, false);
    }

    File doTask(String module, boolean trimTesting, boolean assembleAll, boolean keepJars) {
        BuildModule task = getTask(module);
        String name = name(module, trimTesting, assembleAll);
        File jar = new File(getJarDir(), name+ ".jar");
        task.setAssembleall(assembleAll);
        task.setTrimtesting(trimTesting);
        task.execute();
        if (!jar.canRead()) {
            File[] files = getJarDir().listFiles();
            fail("cannot read " + jar + " in " + Arrays.asList(files));
        }
        if (!keepJars && deleteJars) {
            deleteTempFiles();
        }
        return jar;
    }

    void checkJavac() {
        boolean result = false;
        try {
            result = (null != Class.forName("sun.tools.javac.Main"));
        } catch (Throwable t) {
            // ignore
        }
        if (! result) {
            assertTrue("add tools.jar to the classpath for Ant's use of javac", false);
        }
    }

}
