package com.versata.ant.task.jaxb;

import java.io.*;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.Properties;
import org.apache.tools.ant.*;
import org.apache.tools.ant.types.Path;
import org.apache.tools.ant.taskdefs.*;
import org.apache.tools.ant.types.Reference;
import org.apache.tools.ant.util.FileUtils;


/**
 * A Task to process set of JAXB Schema binding documents.
 * <ul>
 * <li>basedir
 * <li>destdir
 * <li>includes
 * <li>excludes
 * </ul>
 * Of these arguments, the <b>sourcedir</b> and <b>destdir</b> are required.
 * <p>
 * This task will recursively scan the sourcedir and destdir
 * looking for JAXB schema binding documents to compile them using the specified compiler or Sun's XJC by default.
 *
 * @author <a href="mailto:jumper18@bayarea.net">J. Matthew Pryor</a>
 * @version $Revision$
 */
public class XJCTask extends MatchingTask implements XJCProcessor
{

    private File destDir = null;

    private File baseDir = null;

    private String processor = "xjc";

    private File bindingFile = null;

    private File dtdFile = null;

    private Path classpath = null;

    private XJCProcessor xjcprocessor_ = null;

    private boolean force = false;

    private FileUtils fileUtils;

    /**
     * Creates a new XSLTProcess Task.
     **/
    public XJCTask()
    {
        fileUtils = FileUtils.newFileUtils();
    } //-- XSLTProcess

    /**
     * Executes the task.
     */

    public void execute() throws BuildException
    {
        DirectoryScanner scanner;
        String[]         list;
        String[]         dirs;

        if( baseDir == null )
        {
            baseDir = project.resolveFile(".");
        }

        xjcprocessor_ = getProcessor();
        
        log("Using "+xjcprocessor_.getClass().toString(), Project.MSG_VERBOSE);

        // if we have an in file and out then process them
        if( bindingFile != null && dtdFile != null )
        {
            process(bindingFile, dtdFile);
            return;
        }

        /*
         * if we get here, in and out have not been specified, we are
         * in batch processing mode.
         */

        //-- make sure Source directory exists...
        if( destDir == null )
        {
            String msg = "destdir attributes must be set!";
            throw new BuildException(msg);
        }
        scanner = getDirectoryScanner(baseDir);
        
        // Process all the files marked for styling
        list = scanner.getIncludedFiles();
        for( int i = 0;i < list.length; ++i )
        {
            process( baseDir, list[i], destDir );
        }

        // Process all the directoried marked for styling
        dirs = scanner.getIncludedDirectories();
        for( int j = 0;j < dirs.length;++j )
        {
            list=new File(baseDir,dirs[j]).list();
            for( int i = 0;i < list.length;++i )
                process( baseDir, list[i], destDir );
        }
    } //-- execute

    /**
     * Set whether to check dependencies, or always generate.
     **/
    public void setForce(boolean force)
    {
        this.force = force;
    } //-- setForce

    /**
     * Set the base directory.
     **/
    public void setBasedir(File dir)
    {
        baseDir = dir;
    } //-- setSourceDir

    /**
     * Set the destination directory into which the XSL result
     * files should be copied to
     * @param dirName the name of the destination directory
     **/
    public void setDestdir(File dir)
    {
        destDir = dir;
    } //-- setDestDir

    /**
     * Set the classpath to load the Processor through (attribute).
     */
    public void setClasspath(Path classpath)
    {
        createClasspath().append(classpath);
    }

    /**
     * Set the classpath to load the Processor through (nested element).
     */
    public Path createClasspath()
    {
        if( classpath == null )
        {
            classpath = new Path(project);
        }
        return(classpath.createPath());
    }

    /**
     * Set the classpath to load the Processor through via reference
     * (attribute).
     */
    public void setClasspathRef(Reference r)
    {
        createClasspath().setRefid(r);
    }


    public void setProcessor(String processor)
    {
        this.processor = processor;
    }

    /**
     * Load processor here instead of in setProcessor - this will be
     * called from within execute, so we have access to the latest
     * classpath.
     */
    private void resolveProcessor(String proc) throws Exception
    {
        if( proc.equals("xjc") )
        {
            xjcprocessor_ = (XJCProcessor)this;
        }
        else
        {
            xjcprocessor_ = (XJCProcessor) loadClass(proc).newInstance();
        }
    }

    /**
     * Load named class either via the system classloader or a given
     * custom classloader.
     */
    private Class loadClass(String classname) throws Exception
    {
        if( classpath == null )
        {
            return(Class.forName(classname));
        }
        else
        {
            AntClassLoader al = new AntClassLoader(project, classpath);
            Class c = al.loadClass(classname);
            AntClassLoader.initializeClass(c);
            return(c);
        }
    }

    /**
     * Sets an DTD file
     */
    public void setDtdFile(File dtdFile)
    {
        this.dtdFile = dtdFile;
    }

    /**
     * Sets an binding schema file to be compiled
     */
    public void setBindingFile(File bindingFile)
    {
        this.bindingFile = bindingFile;
    }

    /**
     * Processes the given input XML file and stores the result
     * in the given resultFile.
     **/
    private void process(File baseDir, String xmlFile, File destDir)
    throws BuildException
    {

        String fileExt=DTD_FILE_EXT;
        File   dtdFile=null;
        File   bindingFile=null;

        try
        {
            bindingFile = new File(baseDir,xmlFile);
            int dotPos = xmlFile.lastIndexOf('.');
            if( dotPos>0 )
            {
                dtdFile = new File(destDir,xmlFile.substring(0,xmlFile.lastIndexOf('.'))+fileExt);
            }
            else
            {
                dtdFile = new File(destDir,xmlFile+fileExt);
            }

            ensureDirectoryFor( dtdFile );

            log("Compiling binding file "+bindingFile+" time: " + bindingFile.lastModified() + " and DTD file "+dtdFile+" time: " + dtdFile.lastModified() );
            xjcprocessor_.processBinding(bindingFile, dtdFile);
        }
        catch( Exception ex )
        {
            // If failed to process document,
            // or it will not attempt to process it the second time
            log("Failed to process " + bindingFile, Project.MSG_INFO);

            throw new BuildException(ex);
        }

    } //-- processXML

    private void process(File bindingFile, File dtdFile) throws BuildException {
        try
        {
            log("Compiling binding file "+bindingFile+" time: " + bindingFile.lastModified() + " and DTD file "+dtdFile+" time: " + dtdFile.lastModified() );
            ensureDirectoryFor( dtdFile );
            xjcprocessor_.processBinding(bindingFile, dtdFile);
        }
        catch( Exception ex )
        {
            log("Failed to process " + bindingFile, Project.MSG_INFO);
            throw new BuildException(ex);
        }
    }

    private void ensureDirectoryFor( File targetFile ) throws BuildException {
        File directory = new File( targetFile.getParent() );
        if( !directory.exists() )
        {
            if( !directory.mkdirs() )
            {
                throw new BuildException("Unable to create directory: "
                                         + directory.getAbsolutePath() );
            }
        }
    }

    protected XJCProcessor getProcessor()
    {
        // if processor wasn't specified, see if TraX is available.  If not,
        // default it to xslp or xalan, depending on which is in the classpath
        if( xjcprocessor_ == null )
        {
            if( processor != null )
            {
                try
                {
                    resolveProcessor(processor);
                }
                catch( Exception e )
                {
                    throw new BuildException(e);
                }
            }
            else
            {
                try
                {
                    resolveProcessor("xjc");
                }
                catch( Throwable e1 )
                {
                    e1.printStackTrace();
                    throw new BuildException(e1);
                }
            }
        }
        return(xjcprocessor_);
    }

    /**
     * return the files extension or empty string if it doesn't have one
     * 
     * @param file   the file name
     * @return the files extension or empty string if it doesn't have one
     */
    protected static String getFileExtension(String file)
    {
        int dotPos = file.lastIndexOf('.');
        if( dotPos>0 && dotPos < file.length() )
        {
            return(file.substring(dotPos+1));
        }
        else
        {
            return("");
        }
    }


    /**
     * return the files extension or empty string if it doesn't have one
     * 
     * @param file   the file name
     * @return the files extension or empty string if it doesn't have one
     */
    protected String getFileMinusExtension(String file)
    {
        int dotPos = file.lastIndexOf('.');
        if( dotPos>0 )
        {
            return(file.substring(0, dotPos));
        }
        else
        {
            return(file);
        }
    }

    public void processBinding(File bindingFile, File dtdFile) throws BuildException
    {
        File    dtd_file = null;

        try
        {
            if( getFileExtension(bindingFile.getAbsolutePath()).equalsIgnoreCase(BINDING_FILE_EXT) )
            {
                dtd_file = new File(getFileMinusExtension(bindingFile.getAbsolutePath()) + "." + DTD_FILE_EXT);

                if( !dtd_file.exists() )
                {
                    throw new BuildException("DTD file for binding file " + dtdFile.getAbsolutePath() + " cannot be found at " + dtd_file.getAbsolutePath());
                }

                String[]    args = new String[4];

                args[0] = "-d";
                args[1] = this.destDir.getAbsolutePath();
                args[2] = dtd_file.getAbsolutePath();
                args[3] = bindingFile.getAbsolutePath();

                com.sun.tools.xjc.Main.main(args);

            }
        }
        catch( Exception e )
        {
            e.printStackTrace();
            throw new BuildException(e);
        }

    }

    public static final String DTD_FILE_EXT = "dtd";
    public static final String BINDING_FILE_EXT = "xjs";
} //-- XJCTask

