/*
 * JSPTem - use jsp's for all your templating!
 * Copyright (c) 2006, Paul Tuckey
 * All rights reserved.
 *
 * JSPTem is based on GNUJSP (Copyright (C) 1999, Yaroslav Faybishenko <yaroslav@cs.berkeley.edu>)
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package org.tuckey.jsptem;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.DirectoryScanner;
import org.apache.tools.ant.taskdefs.MatchingTask;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.GlobPatternMapper;
import org.apache.tools.ant.util.SourceFileScanner;

import java.io.File;
import java.io.IOException;


public class JspTemAntTask extends MatchingTask {

    private Path src;
    private File destDir;
    private Path compileClasspath;
    private Path compileSourcepath;
    private Path bootclasspath;

    protected boolean listFiles = false;
    protected File[] compileList = new File[0];

    private String source;
    private String debugLevel;
    private File tmpDir;
    private File srcDirFile;
    private String emitter;

    /**
     * Javac task for compilation of Java files.
     */
    public JspTemAntTask() {

    }

    /**
     * Get the value of debugLevel.
     *
     * @return value of debugLevel.
     */
    public String getDebugLevel() {
        return debugLevel;
    }

    /**
     * Keyword list to be appended to the -g command-line switch.
     * <p/>
     * This will be ignored by all implementations except modern
     * and classic(ver >= 1.2). Legal values are none or a
     * comma-separated list of the following keywords: lines, vars,
     * and source. If debuglevel is not specified, by default, :none
     * will be appended to -g. If debug is not turned on, this attribute
     * will be ignored.
     *
     * @param v Value to assign to debugLevel.
     */
    public void setDebugLevel(String v) {
        this.debugLevel = v;
    }

    /**
     * Get the value of source.
     *
     * @return value of source.
     */
    public String getSource() {
        return source;
    }

    /**
     * Value of the -source command-line switch; will be ignored
     * by all implementations except modern and jikes.
     * <p/>
     * If you use this attribute together with jikes, you must
     * make sure that your version of jikes supports the -source switch.
     * Legal values are 1.3, 1.4 and 1.5 - by default, no -source argument
     * will be used at all.
     *
     * @param v Value to assign to source.
     */
    public void setSource(String v) {
        this.source = v;
    }

    /**
     * Adds a path for source compilation.
     *
     * @return a nested src element.
     */
    public Path createSrc() {
        if (src == null) {
            src = new Path(getProject());
        }
        return src.createPath();
    }

    /**
     * Recreate src.
     *
     * @return a nested src element.
     */
    protected Path recreateSrc() {
        src = null;
        return createSrc();
    }

    /**
     * Set the source directories to find the source Java files.
     *
     * @param srcDir the source directories as a path
     */
    public void setSrcdir(Path srcDir) {
        // todo: is this right?
        //log("dir " + srcDir.toString());
        srcDirFile = new File(srcDir.toString());

        if (src == null) {
            src = srcDir;
        } else {
            src.append(srcDir);
        }
    }

    /**
     * Gets the source dirs to find the source java files.
     *
     * @return the source directories as a path
     */
    public Path getSrcdir() {
        return src;
    }

    /**
     * Set the destination directory into which the Java source
     * files should be compiled.
     *
     * @param destDir the destination director
     */
    public void setDestdir(File destDir) {
        this.destDir = destDir;
    }

    /**
     * Gets the destination directory into which the java source files
     * should be compiled.
     *
     * @return the destination directory
     */
    public File getDestdir() {
        return destDir;
    }

    /**
     * Set the sourcepath to be used for this compilation.
     *
     * @param sourcepath the source path
     */
    public void setSourcepath(Path sourcepath) {
        if (compileSourcepath == null) {
            compileSourcepath = sourcepath;
        } else {
            compileSourcepath.append(sourcepath);
        }
    }

    /**
     * Gets the sourcepath to be used for this compilation.
     *
     * @return the source path
     */
    public Path getSourcepath() {
        return compileSourcepath;
    }

    /**
     * Adds a path to sourcepath.
     *
     * @return a sourcepath to be configured
     */
    public Path createSourcepath() {
        if (compileSourcepath == null) {
            compileSourcepath = new Path(getProject());
        }
        return compileSourcepath.createPath();
    }

    /**
     * Adds a reference to a source path defined elsewhere.
     *
     * @param r a reference to a source path
     */
    public void setSourcepathRef(Reference r) {
        createSourcepath().setRefid(r);
    }

    /**
     * Set the classpath to be used for this compilation.
     *
     * @param classpath an Ant Path object containing the compilation classpath.
     */
    public void setClasspath(Path classpath) {
        if (compileClasspath == null) {
            compileClasspath = classpath;
        } else {
            compileClasspath.append(classpath);
        }
    }

    /**
     * Gets the classpath to be used for this compilation.
     *
     * @return the class path
     */
    public Path getClasspath() {
        return compileClasspath;
    }

    /**
     * Adds a path to the classpath.
     *
     * @return a class path to be configured
     */
    public Path createClasspath() {
        if (compileClasspath == null) {
            compileClasspath = new Path(getProject());
        }
        return compileClasspath.createPath();
    }

    /**
     * Adds a reference to a classpath defined elsewhere.
     *
     * @param r a reference to a classpath
     */
    public void setClasspathRef(Reference r) {
        createClasspath().setRefid(r);
    }

    /**
     * Sets the bootclasspath that will be used to compile the classes
     * against.
     *
     * @param bootclasspath a path to use as a boot class path (may be more
     *                      than one)
     */
    public void setBootclasspath(Path bootclasspath) {
        if (this.bootclasspath == null) {
            this.bootclasspath = bootclasspath;
        } else {
            this.bootclasspath.append(bootclasspath);
        }
    }

    /**
     * Gets the bootclasspath that will be used to compile the classes
     * against.
     *
     * @return the boot path
     */
    public Path getBootclasspath() {
        return bootclasspath;
    }

    /**
     * Adds a path to the bootclasspath.
     *
     * @return a path to be configured
     */
    public Path createBootclasspath() {
        if (bootclasspath == null) {
            bootclasspath = new Path(getProject());
        }
        return bootclasspath.createPath();
    }

    /**
     * Adds a reference to a classpath defined elsewhere.
     *
     * @param r a reference to a classpath
     */
    public void setBootClasspathRef(Reference r) {
        createBootclasspath().setRefid(r);
    }

    /**
     * If true, list the source files being handed off to the compiler.
     *
     * @param list if true list the source files
     */
    public void setListfiles(boolean list) {
        listFiles = list;
    }

    /**
     * Get the listfiles flag.
     *
     * @return the listfiles flag
     */
    public boolean getListfiles() {
        return listFiles;
    }

    /**
     * Where Ant should place temporary files.
     *
     * @param tmpDir the temporary directory
     * @since Ant 1.6
     */
    public void setTempdir(File tmpDir) {
        this.tmpDir = tmpDir;
    }

    /**
     * Where Ant should place temporary files.
     *
     * @return the temporary directory
     * @since Ant 1.6
     */
    public File getTempdir() {
        return tmpDir;
    }

    /**
     * Executes the task.
     *
     * @throws BuildException if an error occurs
     */
    public void execute() throws BuildException {
        log("in exec");
        checkParameters();
        resetFileLists();

        // scan source directories and dest directory to build up
        // compile lists
        String[] list = src.list();
        for (int i = 0; i < list.length; i++) {
            String aList = list[i];
            File srcDir = getProject().resolveFile(aList);
            log(srcDir.toString());
            if (!srcDir.exists()) {
                throw new BuildException("srcdir \""
                        + srcDir.getPath()
                        + "\" does not exist!", getLocation());
            }

            DirectoryScanner ds = this.getDirectoryScanner(srcDir);
            String[] files = ds.getIncludedFiles();

            scanDir(srcDir, destDir != null ? destDir : srcDir, files);
        }

        compile();
    }

    /**
     * Clear the list of files to be compiled and copied..
     */
    protected void resetFileLists() {
        compileList = new File[0];
    }

    /**
     * Scans the directory looking for source files to be compiled.
     * The results are returned in the class variable compileList
     *
     * @param srcDir  The source directory
     * @param destDir The destination directory
     * @param files   An array of filenames
     */
    protected void scanDir(File srcDir, File destDir, String[] files) {
        GlobPatternMapper m = new GlobPatternMapper();
        m.setFrom("*.jsp");
        m.setTo("*.jsp.java");
        SourceFileScanner sfs = new SourceFileScanner(this);
        File[] newFiles = sfs.restrictAsFiles(files, srcDir, destDir, m);

        if (newFiles.length > 0) {
            File[] newCompileList
                    = new File[compileList.length + newFiles.length];
            System.arraycopy(compileList, 0, newCompileList, 0,
                    compileList.length);
            System.arraycopy(newFiles, 0, newCompileList,
                    compileList.length, newFiles.length);
            compileList = newCompileList;
        }
    }

    /**
     * Gets the list of files to be compiled.
     *
     * @return the list of files as an array
     */
    public File[] getFileList() {
        return compileList;
    }

    /**
     * Check that all required attributes have been set and nothing
     * silly has been entered.
     *
     * @throws BuildException if an error occurs
     * @since Ant 1.5
     */
    protected void checkParameters() throws BuildException {
        if (src == null) {
            throw new BuildException("srcdir attribute must be set!",
                    getLocation());
        }
        if (src.size() == 0) {
            throw new BuildException("srcdir attribute must be set!",
                    getLocation());
        }
        if (destDir != null && !destDir.exists()) {
            destDir.mkdir();
        }
        if (destDir != null && !destDir.isDirectory()) {
            throw new BuildException("destination directory \""
                    + destDir
                    + "\" does not exist "
                    + "or is not a directory", getLocation());
        }
    }

    /**
     * Perform the compilation.
     *
     * @since Ant 1.5
     */
    protected void compile() {
        // String compilerImpl = getCompiler();

        if (compileList.length > 0) {
            log("Compiling " + compileList.length + " source file"
                    + (compileList.length == 1 ? "" : "s")
                    + (destDir != null ? " to " + destDir : ""));

            JspToJavaCompiler jspToJavaCompiler = new JspToJavaCompiler(destDir, srcDirFile, emitter);

            if (listFiles) {
                for (int i = 0; i < compileList.length; i++) {
                    File aCompileList = compileList[i];
                    String filename = aCompileList.getAbsolutePath();
                    log(filename);
                    try {
                        jspToJavaCompiler.compileFileAnt(aCompileList);
                    } catch (IOException e) {
                        throw new BuildException(e);
                    }
                }
            }

        }
    }

    /**
     * Adds an "compiler" attribute to Commandline$Attribute used to
     * filter command line attributes based on the current
     * implementation.
     */
    public class ImplementationSpecificArgument extends
            org.apache.tools.ant.util.facade.ImplementationSpecificArgument {

        /**
         * @param impl the name of the compiler
         */
        public void setCompiler(String impl) {
            super.setImplementation(impl);
        }
    }

    public String getEmitter() {
        return emitter;
    }

    public void setEmitter(String emitter) {
        this.emitter = emitter;
    }
}
