package de.renew.ant;

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

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.taskdefs.Delete;
import org.apache.tools.ant.taskdefs.Execute;
import org.apache.tools.ant.taskdefs.LogStreamHandler;
import org.apache.tools.ant.types.FileSet;


/**
   A class that runs LaTeX and BIBTeX.<p>

   <b>Implementation plan:</b><br>
   <ol>

     <li> Execution of LaTeX and assorted programs by instantiating of
     <code>Execute</code>. An example could be <code>Javac</code>.

     <li> Deletion of intermediate files such as aux, toc, etc, via
     pattern sets, this is shown in <code>MatchingTask</code>. This
     easily allows addition of other pattern in the ant-file depending
     on the document.

     <li> Parsing the output of LaTeX should give hints, whether we
     need an additional run. Use a counter to abort from endless loops
     which may result from using the package <code>varioref</code>.

     <li> Test via junit at least the construction of the list of
     deletable files. Perhaps go further and test the command line or
     even the results of latex commands.

   </ol>
   @author Klaus Alfert <a href="alfert@shire.prima.de">alfert@shire.prima.de</a>
*/
public class LaTeX extends Task {
    // Attributes
    private String _latexfile = null;
    private String _bibtexfile = null;
    private File _latexdir = null;
    private boolean _bibtex = false;
    private boolean _pdftex = false;
    private boolean _psgztex = false;
    private boolean _clean = false;
    private Delete _delete = null;
    private final String[] _deletePatterns =
        { "*.aux", "*.log", "*.toc", "*.lof", "*.lot", "*.bbl", "*.blg", "*.out" };

    /** Constructor. Simply calls <code>super()</code>. */
    public LaTeX() {
        super();
    }

    /** Initialiazes some default-values which need a running environment. */
    @Override
    public void init() {
        this._latexdir = getProject().getBaseDir();
    }

    /** The LaTeX main file#
     * @param filename the desired LaTeX main file */
    public void setLatexfile(String filename) {
        _latexfile = filename;
        _bibtexfile = _latexfile.substring(0, _latexfile.lastIndexOf(".tex"));
    }

    /** The directory in which the main-file resides.
     * @param dir The directory of the main file. */
    public void setDirectory(File dir) {
        _latexdir = dir;
    }

    /** Set the BIBTeX flag.
     * @param bibtex True if the bibtex command should be run, false else. */
    public void setBibtex(boolean bibtex) {
        this._bibtex = bibtex;
    }

    /** Set the PDFLaTeX flag.
     * @param  pdftex True if a PDF file should be created from the from ps file, false else. */
    public void setPdftex(boolean pdftex) {
        this._pdftex = pdftex;
    }

    /** Set the psgztex flag.
     * @param psgztex True if ps file should be created, false else. */
    public void setPsgztex(boolean psgztex) {
        this._psgztex = psgztex;
    }

    /** Set the clean flag.
     * @param clean True if _clean should be true, false else. */
    public void setClean(boolean clean) {
        this._clean = clean;
    }

    @Override
    public void execute() throws BuildException {
        this.log("Running LaTeX now!", Project.MSG_VERBOSE);
        this.dump();
        log("Calling latex on " + _latexfile);
        this.runLaTeX();
        if (_bibtex) {
            log("bibtex...");
            this.runBIBTeX();
        }
        log("latex");
        this.runLaTeX();

        log("latex");
        this.runLaTeX();

        if (_psgztex) {
            log("Creating ps from dvi file...");
            this.runDvips();
            if (_pdftex) {
                log("Creating pdf from ps file");
                this.runPs2pdf();
            }
            log("Running gzip on ps file");
            this.runGzip();
        }
        if (_delete == null) {
            _delete = this.createDefaultDelete();
        }
        this.log("run Delete", Project.MSG_VERBOSE);
        _delete.execute();
        log("Done.");
    }

    /** Creates the nested delete element.
     * @return The created delete element. */
    public Object createDelete() {
        log("Delete is created");
        _delete = (Delete) getProject().createTask("delete");
        return _delete;
    }

    /** Creates the default delete element. */
    private Delete createDefaultDelete() {
        Delete delete = null;
        delete = (Delete) getProject().createTask("delete");
        FileSet fs = new FileSet();
        fs.setDir(this._latexdir);
        for (int i = 0; i < this._deletePatterns.length; i++) {
            fs.createInclude().setName(_deletePatterns[i]);
        }
        delete.addFileset(fs);
        delete.setVerbose(true);
        return delete;
    }

    /** Constructs and runs LaTeX-command. */
    private final int runLaTeX() throws BuildException {
        String latex = "pdflatex";
        String[] command = { latex, "\\nonstopmode\\input{" + _latexfile + "}" };
        return this.runCommand(command, false);
    }

    /** Constructs and runs BibTeX-command. */
    private final int runBIBTeX() throws BuildException {
        String[] command = { "bibtex", _bibtexfile };
        return this.runCommand(command, false);
    }

    /** Constructs and runs dvips-command. */
    private final int runDvips() throws BuildException {
        String[] command = { "dvips", "-D600", "-o", _bibtexfile + ".ps", _bibtexfile };
        return this.runCommand(command, true);
    }

    private final int runGzip() throws BuildException {
        String[] command = { "gzip", "-f", _bibtexfile + ".ps" };
        return this.runCommand(command, false);
    }

    private final int runPs2pdf() throws BuildException {
        String[] command = { "ps2pdf", _bibtexfile + ".ps" };
        return this.runCommand(command, false);
    }

    /** Runs the command.
        The output of the command is logged at the verbose level.
        @param command The command.
        @param wordy   Whether the command tends to scroll on the
                       standard error channel.
     */
    private final int runCommand(String[] command, boolean wordy) throws BuildException {
        int exitCode = 0;
        StringBuffer com = new StringBuffer();
        for (int i = 0; i < command.length; i++) {
            if (i > 0) {
                com.append(' ');
            }
            com.append(command[i]);
        }
        log("running command: " + com.toString(), Project.MSG_VERBOSE);
        try {
            Execute exec = null;
            if (wordy) {
                exec = new Execute(
                    new LogStreamHandler(this, Project.MSG_DEBUG, Project.MSG_DEBUG), null);
            } else {
                exec = new Execute(
                    new LogStreamHandler(this, Project.MSG_DEBUG, Project.MSG_WARN), null);
            }


            exec.setWorkingDirectory(_latexdir);
            exec.setCommandline(command);


            // allow Ant to find the antRun script if necessary
            exec.setAntRun(getProject());
            exitCode = exec.execute();
        } catch (IOException exp) {
            throw new BuildException(exp);
        }
        log("command exitcode = " + exitCode, Project.MSG_VERBOSE);
        if (exitCode != 0) {
            throw new BuildException(
                "command " + com.toString() + " exited with exit code " + exitCode + ".");
        }
        return exitCode;
    }

    /** Dumps the local attributes. */
    private void dump() {
        this.log("latexfile  = " + _latexfile, Project.MSG_VERBOSE);
        this.log("bibtexfile = " + _latexfile, Project.MSG_VERBOSE);
        this.log("latexdir   = " + _latexdir, Project.MSG_VERBOSE);
        this.log("bibtex     = " + _bibtex, Project.MSG_VERBOSE);
        this.log("bibtex     = " + _bibtex, Project.MSG_VERBOSE);
        this.log("clean      = " + _clean, Project.MSG_VERBOSE);
        this.log("delete     = " + _delete, Project.MSG_VERBOSE);
    }
}