/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */

package net.sf.jaxodraw.gui.panel;

import java.awt.Component;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

import net.sf.jaxodraw.gui.JaxoDialogs;
import net.sf.jaxodraw.io.JaxoPreview;
import net.sf.jaxodraw.util.JaxoInfo;
import net.sf.jaxodraw.util.JaxoDictionary;
import net.sf.jaxodraw.util.JaxoLog;
import net.sf.jaxodraw.util.JaxoXsltTransformer;

// the following is only needed for pdf transform, see commented methods below and deps in pom
/*
import java.io.BufferedOutputStream;

import javax.xml.transform.Result;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.sax.SAXResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.fop.apps.FOPException;
import org.apache.fop.apps.FOUserAgent;
import org.apache.fop.apps.Fop;
import org.apache.fop.apps.FopFactory;

import org.apache.xmlgraphics.util.MimeConstants;
*/

/** Responsible for creating and locating the User Guide. */
public class JaxoUserGuide {
    private static final JaxoDictionary LANGUAGE = new JaxoDictionary(JaxoUserGuide.class);

    private static final String USER_GUIDE_DIR = JaxoInfo.SETTINGS_DIR + "usrGuide/";
    private static final String WEB_URL = JaxoInfo.WEB_SITE + "userguide.html";
    private static final String USER_GUIDE_SOURCES_DIR = "resources/usrGuide/";

    private final File lockFile;
    private final File indexFile;
    private final File userGuideDir;

    private final Component parent;

    private boolean inJar;

    /**
     * Constructor that takes a parent component.
     * @param parentc The parent component for this UserGuide.
     */
    public JaxoUserGuide(final Component parentc) {
        this(USER_GUIDE_DIR, parentc);
    }

    /**
     * Constructor that takes a parent component and an alternative target location.
     *
     * @param targetDir the directory where the user guide will be created.
     * @param parentc The parent component for this UserGuide.
     */
    public JaxoUserGuide(final String targetDir, final Component parentc) {
        this.parent = parentc;
        this.userGuideDir = new File(targetDir);
        this.lockFile = new File(targetDir, ".lock");
        this.indexFile = new File(targetDir, "index.html");
    }

    /**
     * Create the User Guide from the command line.
     *
     * @param args optional: args[0] may contain the target directory
     *      where the User Guide will be created. If not given,
     *      the User Guide will be created in {@link JaxoInfo#USER_HOME}.
     */
    public static void main(String[] args) {
        final String targetDir = (args.length > 0) ? args[0] : JaxoInfo.USER_HOME + "/usrGuide/";
        JaxoLog.info("Creating user guide in: " + targetDir);
        final File target = new File(targetDir);

        final JaxoUserGuide guide = new JaxoUserGuide(targetDir, null);

        if (!guide.prepareTargetDir(target)) {
            throw new RuntimeException("Failed to create target directory!");
        }

        if (!guide.createHtml(target)) {
            throw new RuntimeException(LANGUAGE.message("installErrorVisit%0", WEB_URL));
        }
/*
        if (!guide.createPdf(target)) {
            throw new RuntimeException("Failed to create pdf!");
        }
*/
    }

    /**
     * Shows the User Guide in the given Preview.
     * @param p The preview to use.
     */
    public void show(final JaxoPreview p) {
        final URL indexPage = localIndexPage();

        if (indexPage == null) {
            final String msg = LANGUAGE.message("installErrorVisit%0", WEB_URL);
            JaxoDialogs.showErrorDialog(parent, msg);
        } else {
            p.setParentComponent(parent);
            p.browseURL(indexPage);
        }
    }

    /**
     * Checks if the User Guide exists locally and if not, tries to re-create it.
     * @return True if the local version of the User Guide was created
     * without errors, and is therefore browsable.
     */
    public boolean isLocallyBrowsable() {
        boolean ready = false;

        if (userGuideDir.isDirectory()) {
            // this implies that it exists
            if (lockFile.exists()) {
                // there was a problem last time, try to re-create
                JaxoLog.info("Lock file detected, re-creating User Guide!");
                deleteDir(userGuideDir);
                ready = createHtml(userGuideDir);
            } else {
                if (indexFile.exists()) {
                    ready = true;
                } else {
                    // directory is there but no index file, re-create everything
                    JaxoLog.info("No index file found, re-creating User Guide!");
                    deleteDir(userGuideDir);
                    ready = createHtml(userGuideDir);
                }
            }
        } else {
            // create
            ready = createHtml(userGuideDir);
        }

        return ready;
    }

    /**
     * Returns a URL to the index page of the local copy of the User Guide.
     * @return Null if isLocallyBrowsable() returns false, or if another
     * error occurred; the local URL otherwise.
     */
    public URL localIndexPage() {
        URL indexPage = null;

        try {
            if (isLocallyBrowsable()) {
                indexPage = indexFile.toURI().toURL();
            }
        } catch (MalformedURLException e) {
            JaxoLog.debug(e);
        }

        return indexPage;
    }

      //
     // private
    //

    private boolean createHtml(final File targetDir) {
        final JaxoProgressDialog progress =
                new JaxoProgressDialog(parent, 0, 100, LANGUAGE.value("creatingUserGuide"));
        progress.setValue(0);
        progress.pack();
        progress.setVisible(parent != null);

        int value = 10;
        progress.setValue(value);

        File sourceDir = null;

        try {
            sourceDir = getSourcesDir(targetDir);
        } catch (IOException e) {
            JaxoLog.warn("Failed to copy usrGuide sources: " + e.getMessage());
            JaxoLog.debug(e);
            return false;
        }

        final File xslFile = new File(sourceDir, "xslt/style.xsl");
        final File xdocDir = new File(sourceDir, "xdocs/");

        final File[] xmlFiles = xdocDir.listFiles();

        final int nof = xmlFiles.length;
        final int step = Math.round(90.f / nof);

        try {
            for (int j = 0; j < nof; j++) {
                final File xmlFile = xmlFiles[j];
                final String filename = xmlFile.getName();

                File outFile = new File(targetDir, filename.replace(".xml", ".html"));
                JaxoXsltTransformer.transform(xmlFile, outFile, xslFile,
                    getHtmlParameters(), getHtmlOutputProperties());

                value += step;
                progress.setValue(value);
            }

            copyDirectory(new File(sourceDir, "resources/"), targetDir);
        } catch (IOException e) {
            JaxoLog.warn("Failed to transform usrGuide sources: " + e.getMessage());
            JaxoLog.debug(e);
            return false;
        }

        if (inJar && !deleteDir(sourceDir)) {
            // cannot delete temporary file but UG was created: ignore
            JaxoLog.warn("Could not delete temporary file: "
                + sourceDir.getAbsolutePath());
        }

        progress.setValue(100);

        if (!lockFile.delete()) {
            // cannot delete lock file file but UG was created: ignore
            JaxoLog.warn("Could not delete lock file: " + lockFile.getAbsolutePath());
        }

        progress.dispose();

        return true;
    }

    private boolean prepareTargetDir(final File targetDir) {
        boolean succeed = false;
        try {
            if (targetDir.exists()) {
                JaxoLog.info("Directory " + targetDir + " will be recreated!");
                succeed = deleteDir(targetDir);
                if (!succeed) {
                    JaxoLog.warn(targetDir.getAbsolutePath() + " could not be deleted!");
                    return false;
                }
            }

            succeed = targetDir.mkdirs();
            if (!succeed) {
                JaxoLog.warn(targetDir.getAbsolutePath() + " could not be created!");
                return false;
            }

            succeed = lockFile.createNewFile();
            if (!succeed) {
                JaxoLog.warn("Lock file could not be created!");
                return false;
            }
        } catch (SecurityException e) {
            JaxoLog.warn(targetDir.getAbsolutePath() + " could not be created!");
            JaxoLog.debug(e);
            return false;
        } catch (IOException e) {
            JaxoLog.warn(targetDir.getAbsolutePath() + " could not be created!");
            JaxoLog.debug(e);
            return false;
        }

        return true;
    }

    // return the directory where the userGuide sources are located
    // if we are inside a jar, the sources are copied to targetDir and that location is returned
    private File getSourcesDir(final File targetDir) throws IOException {
        File sourceDir = new File(Thread.currentThread().
                getContextClassLoader().getResource(USER_GUIDE_SOURCES_DIR).getFile());
        String sourceDirPath = sourceDir.getPath();

        // handle whitespace on windows
        // FIXME: just use sourceDir.toURI().toURL().toString())?
        try {
            sourceDirPath = URLDecoder.decode(sourceDirPath, "utf-8");
        } catch (UnsupportedEncodingException e) {
            // cannot happen
            JaxoLog.debug("utf-8 not supported?", e);
        }

        final int bang = sourceDirPath.indexOf(".jar!");
        this.inJar = (bang != -1);

        if (inJar) {
            // we're inside a jar, copy source to the file system
            // path is of the form "file:path_to_jarfile.jar!path_do_file"
            final String jarName = sourceDirPath.substring(5, bang + 4);
            sourceDir = new File(targetDir, "src/");

            copyFolderFromJar(new JarFile(jarName), USER_GUIDE_SOURCES_DIR, sourceDir);
        }

        return sourceDir;
    }

    private static void copyDirectory(final File srcDir, final File dstDir)
        throws IOException {
        if (srcDir.isDirectory()) {
            if (!dstDir.exists() && !dstDir.mkdir()) {
                throw new IOException("Failed to create target directory: "
                        + dstDir.getAbsolutePath());
            }

            final String[] children = srcDir.list();
            for (int i = 0; i < children.length; i++) {
                copyDirectory(new File(srcDir, children[i]),
                    new File(dstDir, children[i]));
            }
        } else {
            copyFile(srcDir, dstDir);
        }
    }

    private static void copyFile(final File src, final File dst) throws IOException {
        InputStream in = null;
        OutputStream out = null;
        try {
            in = new FileInputStream(src);
            out = new FileOutputStream(dst);
            final byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } finally {
                if (out != null) {
                    out.close();
                }
            }
        }
    }

    private static boolean deleteDir(final File dir) {
        if (dir.isDirectory()) {
            final String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                final boolean success = deleteDir(new File(dir, children[i]));
                if (!success) {
                    return false;
                }
            }
        }
        return dir.delete();
    }

    private static void copyFolderFromJar(final JarFile jar, final String folderName, final File fDest)
        throws IOException {

        final Enumeration<JarEntry> entries = jar.entries();

        while (entries.hasMoreElements()) {
            final JarEntry entry = entries.nextElement();
            final String entryName = entry.getName();

            if (entryName.startsWith(folderName)) {
                final File f = new File(fDest,
                    entryName.substring(folderName.length(), entryName.length()));

                if (entry.isDirectory()) {
                    if (!f.exists() && !f.mkdirs()) {
                        throw new IOException("could not create directory: "
                            + f.getAbsolutePath() + " for jar entry " + entryName);
                    }
                } else if (!f.exists() && !copyFileFromJar(entryName, f)) {
                    throw new IOException("copyFileFromJar failed: "
                            + f.getAbsolutePath() + " for jar entry " + entryName);
                }
            }
        }
    }

    private static boolean copyFileFromJar(final String sResource, final File fDest)
        throws IOException {

        if (sResource == null || fDest == null) {
            return false;
        }

        InputStream sIn = null;
        OutputStream sOut = null;

        try {
            int nLen = 0;
            sIn = Thread.currentThread().getContextClassLoader().
                    getResourceAsStream(sResource);

            if (sIn == null) {
                throw new IOException("Error copying from jar"
                + "(" + sResource + " to " + fDest.getPath() + ")");
            }

            sOut = new FileOutputStream(fDest);
            final byte[] bBuffer = new byte[1024];

            while ((nLen = sIn.read(bBuffer)) > 0) {
                sOut.write(bBuffer, 0, nLen);
            }

            sOut.flush();
        } finally {
            try {
                if (sIn != null) {
                    sIn.close();
                }
            } finally {
                if (sOut != null) {
                    sOut.close();
                }
            }
        }

        return fDest.exists();
    }

    private static Map<String, String> getHtmlParameters() {
        final Map<String, String> params = new HashMap<String, String>(3);

        params.put("JDtitle", "JaxoDraw");
        params.put("JDversion", JaxoInfo.VERSION_NUMBER);

        return params;
    }

    private static Map<String, String> getHtmlOutputProperties() {
        final Map<String, String> params = new HashMap<String, String>(3);

        params.put("doctype-public", "-//W3C//DTD XHTML 1.0 Transitional//EN");
        params.put("doctype-system", "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd");

        return params;
    }
/*
    private boolean createPdf(final File targetDir) {
        final File sourceDir;

        try {
            sourceDir = getSourcesDir(targetDir);
        } catch (IOException e) {
            JaxoLog.warn("Failed to copy usrGuide sources: " + e.getMessage());
            JaxoLog.debug(e);
            return false;
        }

        final File xmlfile = new File(sourceDir, "pdf-navigation.xml");
        final File xsltfile = new File(sourceDir, "xslt/project2fo.xslt");
        final File resourceDir = new File(sourceDir, "resources");
        final File fofile = new File(sourceDir, "usrGuide.fo");
        final File pdffile = new File(targetDir, "usrGuide.pdf");

        try {
            JaxoXsltTransformer.transform(xmlfile, fofile, xsltfile, getPdfParameters(), null);
            convertFO2PDF(fofile, pdffile, resourceDir);
        } catch (IOException e) {
            JaxoLog.warn("Failed to transform usrGuide sources: " + e.getMessage());
            JaxoLog.debug(e);
            return false;
        }

        if (inJar && !deleteDir(sourceDir)) {
            // cannot delete temporary file but pdf was created: ignore
            JaxoLog.warn("Could not delete temporary working directory: " + sourceDir.getAbsolutePath());
        }

        return true;
    }

    private static Map<String, String> getPdfParameters() {
        final Map<String, String> params = new HashMap<String, String>(3);

        params.put("JDtitle", "JaxoDraw");
        params.put("basePath", "../");
        params.put("paperType", "A4");
        params.put("companyIncName", "The JaxoDraw Team");
        params.put("confidential", "false");
        params.put("publicationYear", "2003-2011");
        params.put("imageDpi", "150");
        params.put("companyLogo", "images/top.gif");
        //params.put("projectLogo", "images/ZHfermions.gif");
        params.put("coverProjectCompany", "The JaxoDraw Team");
        params.put("coverProjectName", "JaxoDraw");
        params.put("coverType", "User Guide");
        params.put("coverVersion", JaxoInfo.VERSION_NUMBER);
        params.put("coverDate", "2011");

        return params;
    }

    private static void convertFO2PDF(final File fo, final File pdf, final File resourceDir)
        throws IOException {
        final FopFactory factory = FopFactory.newInstance();

        FOUserAgent userAgent = factory.newFOUserAgent();
        userAgent.setBaseURL(resourceDir.toURI().toURL().toString());

        OutputStream out = null;
        try {
            out = new BufferedOutputStream(new FileOutputStream(pdf));

            Result res = null;
            try {
                Fop fop = factory.newFop(MimeConstants.MIME_PDF, userAgent, out);
                res = new SAXResult(fop.getDefaultHandler());
            } catch (FOPException e) {
                throw new IOException(e);
            }

            Transformer transformer = null;
            try {
                // identity transformer
                transformer = TransformerFactory.newInstance().newTransformer();
            } catch (TransformerConfigurationException e) {
                throw new IOException(e);
            }

            try {
                transformer.transform(new StreamSource(fo), res);
            } catch (TransformerException e) {
                throw new IOException(e);
            }
        } finally {
            if(out != null) {
                out.close();
            }
        }
    }
*/
}
