/************************************************************************
 *
 *  Application.java
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License version 2.1, as published by the Free Software Foundation.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston,
 *  MA  02111-1307  USA
 *
 *  Copyright: 2002-2007 by Henrik Just
 *
 *  All Rights Reserved.
 * 
 *  Version 0.5 (2007-09-12) 
 *
 */
 
 // Warning: Cleaing of this code is in progress...

package writer2latex;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URL;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Locale;
import java.text.Collator;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import org.w3c.dom.Element;

import writer2latex.api.Converter;
import writer2latex.api.ConverterFactory;
import writer2latex.api.ConverterResult;
import writer2latex.api.OutputFile;

import writer2latex.xmerge.OfficeDocument;

import writer2latex.util.Misc;
import writer2latex.util.Config;
import writer2latex.util.L10n;
import writer2latex.office.*;
import writer2latex.xhtml.XhtmlDocument;
import writer2latex.latex.*;

/**
 * <p>Commandline utility to convert an OpenOffice.org Writer XML file into XHTML/LaTeX/BibTeX</p>
 * <p>The utility is invoked with the following command line:</p>
 * <pre>java -jar writer2latex.jar [options] source [target]</pre>
 * <p>Where the available options are
 * <ul>
 * <li><code>-latex</code>, <code>-bibtex</code>, <code>-xhtml</code>,
       <code>-xhtml+mathml</code>, <code>-xhtml+mathml+xsl</code>
 * <li><code>-recurse</code>
 * <li><code>-config[=]filename</code>
 * <li><code>-template[=]filename</code>
 * <li><code>-option[=]value</code>
 * </ul>
 * <p>where <code>option</code> can be any simple option known to Writer2LaTeX
 * (see documentation for the configuration file).</p>
 */
public final class Application {
	
    /* Based on command-line parameters. */
    private String sTargetMIME = MIMETypes.LATEX;
    private boolean bRecurse = false;
    private String sConfigFileName = null;
    private String sTemplateFileName = null;
    private Hashtable options = new Hashtable();
    private String sSource = null;
    private String sTarget = null;

    /* Other global variables */
    private String sOutputFormat = null;
    private String sOutPathName = null;
    private String sOutFileName = null;
    private String sOutFileExtension = null;
	
    private byte[] templateBytes = null;
    private XhtmlDocument template = null;

    private Config config;
    L10n l10n;
    boolean bBatch = false;
    String sDefaultLang = null;
    String sDefaultCountry = null;

    /**
     *  Main method
     *
     *  @param  args  The argument passed on the command line.
     */

    public static final void main (String[] args){

        try {
            Application app = new Application();
            app.parseCommandLine(args);
            app.doConversion();
        } catch (IllegalArgumentException ex) {
            String msg = ex.getMessage();
            showUsage(msg);
        } catch (Exception ex) {
            String msg = ex.getMessage();
            if (msg != null) System.out.println("\n" + msg);
            ex.printStackTrace();
        }

    }
	
    /**
     *  Converts the document
     *
     *  @throws  IllegalArgumentException  If an argument is invalid.
     */
    private void doConversion() {
        // Say hello...
        String sOutputFormat;
        if (MIMETypes.LATEX.equals(sTargetMIME)) { sOutputFormat = "LaTeX"; }
        else if (MIMETypes.BIBTEX.equals(sTargetMIME)) { sOutputFormat = "BibTeX"; }
        else { sOutputFormat = "xhtml"; }
        System.out.println();
        System.out.println("This is Writer2" + sOutputFormat + 
                           ", Version " + ConverterFactory.getVersion() + 
                           " (" + ConverterFactory.getDate() + ")");
        System.out.println();
        System.out.println("Starting conversion...");

        // Read configuration from file
        config = new Config();
        if (sConfigFileName != null) {
            File f = new File(sConfigFileName);
            if (!f.exists()) {
                System.err.println("The configuration file '"+sConfigFileName+"' does not exist.");
            }
            else {
                try {
                    config.read(new FileInputStream(sConfigFileName));
                } catch (Throwable t) {
                    System.err.println("I had trouble reading the configuration file "+sConfigFileName);
                    t.printStackTrace();
                }
            }
       }
	   
       // Create file name data
       String sFullOutFileName = sTarget!=null ? sTarget : Misc.removeExtension(sSource);
       if (sFullOutFileName.endsWith(File.separator)) { // directory only
           sOutPathName=sFullOutFileName;
           sOutFileName = (new File(sSource)).getName();
       }
       else { // directory and filename
           File f = new File(sFullOutFileName);
           sOutPathName = f.getParent();
           if (sOutPathName==null) { sOutPathName=""; } else { sOutPathName+=File.separator; }
           sOutFileName = f.getName();
       }
       sOutFileName = Misc.removeExtension(sOutFileName);
	
       // Set options from command line
       Enumeration keys = options.keys();
       while (keys.hasMoreElements()) {
           String sKey = (String) keys.nextElement();
           String sValue = (String) options.get(sKey);
           config.setOption(sKey,sValue);
       }
		
       // Set locale for directory conversion
       l10n = new L10n();
       sDefaultLang = System.getProperty("user.language");
       sDefaultCountry = System.getProperty("user.country");
       l10n.setLocale(sDefaultLang, sDefaultCountry);

       
       File f = new File(sSource);

        // Make sure the input file/directory  actually exists and can be read
        // before using it
        if (!f.exists()) {
            System.out.println("I'm sorry, I can't find "+sSource);
            System.exit(1);
        }
        if (!f.canRead()) {
            System.out.println("I'm sorry, I can't read "+sSource);
            System.exit(1);
        }
	
        // Read the template
        if (sTemplateFileName!=null) {
            try {
                templateBytes = Misc.inputStreamToByteArray(new FileInputStream(sTemplateFileName));
                template = new XhtmlDocument("Template",XhtmlDocument.XHTML10);
                template.read(new ByteArrayInputStream(templateBytes));
            }
            catch (Throwable t) {
                t.printStackTrace();
                template = null;
            }
        }

        // Convert the file/directory
        if (f.isDirectory()) { convertDirectory(f,sOutPathName); }
        else if (f.isFile()) { convertFile(f,sOutPathName); }
		
        // Say goodbye...
        System.out.println("Done!");

    }

    private String convertFile(File file,String sOutPathName) {
        String sFileName = file.getName();
        String sLocalOutFileName = bBatch ? Misc.removeExtension(sFileName) : sOutFileName;
        System.out.println("Converting "+sFileName);

        ConverterResult dataOut = null;
        ConverterFactory factory = new ConverterFactory();
        factory.setConfig(config);
        Converter converter = factory.createConverter(sTargetMIME);
		
        if (converter!=null) {
            try {
                if (templateBytes!=null) {
                    converter.readTemplate(new ByteArrayInputStream(templateBytes));
                }
                dataOut = converter.convert(new FileInputStream(file),sLocalOutFileName);
            }
            catch (Exception e) {
                System.out.println(" --> Conversion failed");
                e.printStackTrace();
            }
        }

        // TODO: Should do some further checking on the feasability of writing
        // the directory and the files.
        File dir = new File(sOutPathName);
        if (!dir.exists()) { dir.mkdirs(); }
        
        Enumeration docEnum = dataOut.getDocumentEnumeration();
        while (docEnum.hasMoreElements()) {
            OutputFile docOut      = (OutputFile)docEnum.nextElement();
            String fileName      = sOutPathName+docOut.getFileName();
            try {
                FileOutputStream fos = new FileOutputStream(fileName);
                docOut.write(fos);
                fos.flush();
                fos.close();
            } catch (Exception writeExcept) {
                System.out.println("\nThere was an error writing out file <" +
                    fileName + ">");
                writeExcept.printStackTrace();
            }
        }

        return sLocalOutFileName + sOutFileExtension;
    }

    private void convertDirectory(File dir,String sOutPathName) {
        bBatch = true;
        // Convert files and directories in the directory dir

        // Create index page (with header/footer); currently always use xhtml 1.0 strict
        XhtmlDocument htmlDoc = new XhtmlDocument("index",XhtmlDocument.XHTML10);
        htmlDoc.setEncoding(config.xhtmlEncoding());
        htmlDoc.setNoDoctype(config.xhtmlNoDoctype());
        if (templateBytes!=null) { htmlDoc.readFromTemplate(template); }
        else { htmlDoc.createHeaderFooter(); }

        org.w3c.dom.Document htmlDOM = htmlDoc.getContentDOM();

        // Use directory name as title
        htmlDoc.getTitleNode().appendChild( htmlDOM.createTextNode(dir.getName()) );

        // Declare charset (we need this for xhtml because we have no <?xml ... ?>)
        // if (nType==XhtmlDocument.XHTML10) {
            Element meta = htmlDOM.createElement("meta");
            meta.setAttribute("http-equiv","Content-Type");
            meta.setAttribute("content","text/html; charset="+htmlDoc.getEncoding().toLowerCase());
            htmlDoc.getHeadNode().appendChild(meta);
        // }
		
        // Add link to stylesheet
        if (config.xhtmlCustomStylesheet().length()>0) {
            Element htmlStyle = htmlDOM.createElement("link");
            htmlStyle.setAttribute("rel","stylesheet");
            htmlStyle.setAttribute("type","text/css");
            htmlStyle.setAttribute("media","all");
            htmlStyle.setAttribute("href",config.xhtmlCustomStylesheet());
            htmlDoc.getHeadNode().appendChild(htmlStyle);
        }

        // Add uplink to header and footer.
        Element header = htmlDoc.getHeaderNode();
        if (config.getXhtmlUplink().length()>0) {
            Element a = htmlDOM.createElement("a");
            a.setAttribute("href",config.getXhtmlUplink());
            a.appendChild(htmlDOM.createTextNode(l10n.get(L10n.UP)));
            header.appendChild(htmlDOM.createTextNode("["));
            header.appendChild(a);
            header.appendChild(htmlDOM.createTextNode("] "));
        }
        else {
            header.appendChild(htmlDOM.createTextNode("["+l10n.get(L10n.UP)+"]"));
        }
        header.appendChild(htmlDOM.createElement("hr"));
		
        Element footer = htmlDoc.getFooterNode();
        footer.appendChild(htmlDOM.createElement("hr"));
        if (config.getXhtmlUplink().length()>0) {
            Element a = htmlDOM.createElement("a");
            a.setAttribute("href",config.getXhtmlUplink());
            a.appendChild(htmlDOM.createTextNode(l10n.get(L10n.UP)));
            footer.appendChild(htmlDOM.createTextNode("["));
            footer.appendChild(a);
            footer.appendChild(htmlDOM.createTextNode("] "));
        }
        else {
            footer.appendChild(htmlDOM.createTextNode("["+l10n.get(L10n.UP)+"]"));
        }
		
        // Add heading
        if (sOutPathName.length()>0) {
            Element h1 = htmlDOM.createElement("h1");
            htmlDoc.getContentNode().appendChild(h1);
            String sSeparator = File.separator;
            if (sSeparator.equals("\\")) { sSeparator="\\\\"; }
            String sHeading = sOutPathName.substring(0,sOutPathName.length()-1)
                                          .replaceAll(sSeparator," - ");
            h1.appendChild(htmlDOM.createTextNode(sHeading));
        }
		
        // Get and sort the directory
        File[] contents = dir.listFiles();
        int nLen = contents.length;
        Collator collator = Collator.getInstance(new Locale(sDefaultLang,sDefaultCountry));
        for (int i = 0; i<nLen; i++) {
            for (int j = i+1; j<nLen ; j++) {
                File entryi = contents[i];
                File entryj = contents[j];
                if (collator.compare(entryi.getName(), entryj.getName()) > 0) {
                    contents[i] = entryj;
                    contents[j] = entryi;
                }
            }
        }


        // Traverse subdirectories, if allowed
        if (bRecurse) {
            boolean bUseIcon = config.getXhtmlDirectoryIcon().length()>0;
            for (int i=0; i<nLen; i++) {
                if (contents[i].isDirectory()) {
                    config.setOption("xhtml_uplink","../index.html");
                    convertDirectory(contents[i],sOutPathName+contents[i].getName()+File.separator);
                    Element p = htmlDOM.createElement("p");
                    htmlDoc.getContentNode().appendChild(p);
                    if (bUseIcon) {
                        Element img = htmlDOM.createElement("img");
                        p.appendChild(img);
                        img.setAttribute("src",config.getXhtmlDirectoryIcon());
                        img.setAttribute("alt","Directory icon"); // todo: localize
                        p.appendChild(htmlDOM.createTextNode(" "));
                    }
                    Element a = htmlDOM.createElement("a");
                    p.appendChild(a);
                    a.setAttribute("href",Misc.makeHref(contents[i].getName()+"/index.html"));
                    a.appendChild(htmlDOM.createTextNode(contents[i].getName()));
                }
            }
        }

        // Traverse documents
        boolean bUseIcon = config.getXhtmlDocumentIcon().length()>0;
        for (int i=0; i<nLen; i++) {
            if (contents[i].isFile()) {
                config.setOption("xhtml_uplink","index.html");
                String sLinkFile = convertFile(contents[i],sOutPathName);
                if (sLinkFile!=null) {
                    Element p = htmlDOM.createElement("p");
                    htmlDoc.getContentNode().appendChild(p);
                    if (bUseIcon) {
                        Element img = htmlDOM.createElement("img");
                        p.appendChild(img);
                        img.setAttribute("src",config.getXhtmlDocumentIcon());
                        img.setAttribute("alt","Document icon"); // todo: localize
                        p.appendChild(htmlDOM.createTextNode(" "));
                    }
                    Element a = htmlDOM.createElement("a");
                    p.appendChild(a);
                    a.setAttribute("href",Misc.makeHref(sLinkFile));
                    a.appendChild(htmlDOM.createTextNode(Misc.removeExtension(sLinkFile)));
                }
            }
        }

        // Write out the index file
        File outdir = new File(sOutPathName);
        if (!outdir.exists()) { outdir.mkdirs(); }
        
        String sFileName = sOutPathName+htmlDoc.getFileName();
        try {
            FileOutputStream fos = new FileOutputStream(sFileName);
            htmlDoc.write(fos);
            fos.flush();
            fos.close();
        } catch (Exception writeExcept) {
            System.out.println("\nThere was an error writing out file <" +
                sFileName + ">");
            writeExcept.printStackTrace();
        }

    }

    /**
     *  Display usage.
     */
    private static void showUsage(String msg) {
        System.out.println();
        System.out.println("This is Writer2LaTeX, Version " + ConverterFactory.getVersion() 
                           + " (" + ConverterFactory.getDate() + ")");
        System.out.println();
        if (msg != null) System.out.println(msg);
        System.out.println();
        System.out.println("Usage:");
        System.out.println("   java -jar <path>/writer2latex.jar <options> <source file/directory> [<target file/directory>]");
        System.out.println("where the available options are:");
        System.out.println("   -latex");
        System.out.println("   -bibtex");
        System.out.println("   -xhtml");
        System.out.println("   -xhtml+mathml");
        System.out.println("   -xhtml+mathml+xsl");
        System.out.println("   -recurse");
        System.out.println("   -config[=]<configuration file>");
        System.out.println("   -template[=]<template file>");
        System.out.println("   -<configuration option>[=]<value>");
        System.out.println("See the documentation for the full list of options");
    }


    /**
     *  Parse command-line arguments.
     *
     *  @param  args  Array of command line arguments.
     *
     *  @throws  IllegalArgumentException  If an argument is invalid.
     */
    private void parseCommandLine(String sArgs[])
        throws IllegalArgumentException {

        int i = 0;
		
        while (i<sArgs.length) {
            String sArg = getArg(i++,sArgs);
            if (sArg.startsWith("-")) { // found an option
                if ("-latex".equals(sArg)) { sTargetMIME = MIMETypes.LATEX; }
                else if ("-bibtex".equals(sArg)) { sTargetMIME = MIMETypes.BIBTEX; }
                else if ("-xhtml".equals(sArg)) { sTargetMIME = MIMETypes.XHTML; }
                else if ("-xhtml+mathml".equals(sArg)) { sTargetMIME = MIMETypes.XHTML_MATHML; }
                else if ("-xhtml+mathml+xsl".equals(sArg)) { sTargetMIME = MIMETypes.XHTML_MATHML_XSL; }
                else if ("-recurse".equals(sArg)) { bRecurse = true; }
                else { // option with argument
                    int j=sArg.indexOf("=");
                    String sArg2;
                    if (j>-1) { // argument is separated by =
                        sArg2 = sArg.substring(j+1);
                        sArg = sArg.substring(0,j);
                    }
                    else { // argument is separated by space
                        sArg2 = getArg(i++,sArgs);
                    }
                    if ("-config".equals(sArg)) { sConfigFileName = sArg2; }
                    else if ("-template".equals(sArg)) { sTemplateFileName = sArg2; }
                    else { // configuration option
                        options.put(sArg.substring(1),sArg2);
                    }
                }
            }
            else { // not an option, so this must be the source
                sSource = sArg;
                // Possibly followed by the target
                if (i<sArgs.length) {
                    String sArgument = getArg(i++,sArgs); 
                    if (sArgument.length()>0) { sTarget = sArgument; }
                }
                // Skip any trailing empty arguments and signal an error if there's more
                while (i<sArgs.length) {
                    String sArgument = getArg(i++,sArgs);
                    if (sArgument.length()>0) {
                        throw new IllegalArgumentException("I didn't expect "+sArgument+"?");
                    }
                }
            }
        }
        if (sSource==null) {
            throw new IllegalArgumentException("Please specify a source document/directory!");
        }
        // Parsing of command line ended successfully!
    }


    /**
     *  Extract the next argument from the array, while checking to see
     *  that the array size is not exceeded.  Throw a friendly error
     *  message in case the arg is missing.
     *
     *  @param  i     Argument index.
     *  @param  args  Array of command line arguments.
     *
     *  @return  The argument with the specified index.
     *
     *  @throws  IllegalArgumentException  If an argument is invalid.
     */
    private String getArg(int i, String args[])
        throws IllegalArgumentException {

        if (i < args.length) {
            return args[i];
        }
        else throw new
            IllegalArgumentException("I'm sorry, the commandline ended abnormally");
    }
	

}
