/**************************************************************************
 OmegaT - Computer Assisted Translation (CAT) tool 
          with fuzzy matching, translation memory, keyword search, 
          glossaries, and translation leveraging into updated projects.

 Copyright (C) 2000-2006 Keith Godfrey and Maxym Mykhalchuk
               Home page: http://www.omegat.org/
               Support center: http://groups.yahoo.com/group/OmegaT/

 This file is part of OmegaT.

 OmegaT 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 3 of the License, or
 (at your option) any later version.

 OmegaT 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, see <http://www.gnu.org/licenses/>.
 **************************************************************************/

package org.omegat.filters3.xml.opendoc;

import java.awt.Dialog;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.omegat.filters2.AbstractFilter;
import org.omegat.filters2.FilterContext;
import org.omegat.filters2.Instance;
import org.omegat.filters2.TranslationException;
import org.omegat.util.Log;
import org.omegat.util.OStrings;

/**
 * Filter for Open Document file format.
 * 
 * @author Maxym Mykhalchuk
 */
public class OpenDocFilter extends AbstractFilter {
    private static final Set<String> TRANSLATABLE = new HashSet<String>(Arrays.asList("content.xml",
            "styles.xml", "meta.xml"));

    protected boolean requirePrevNextFields() {
        return true;
    }

    /** Returns true if it's OpenDocument file. */
    public boolean isFileSupported(File inFile, Map<String, String> config, FilterContext fc) {
        try {
            ZipFile file = new ZipFile(inFile);
            Enumeration<? extends ZipEntry> entries = file.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                if (TRANSLATABLE.contains(entry.getName())) {
                    file.close();
                    return true;
                }
            }
            file.close();
        } catch (IOException e) {
        }
        return false;
    }

    OpenDocXMLFilter xmlfilter = null;

    private OpenDocXMLFilter createXMLFilter(Map<String, String> options) {
        xmlfilter = new OpenDocXMLFilter();
        xmlfilter.setCallbacks(entryParseCallback, entryTranslateCallback);
        // Defining the actual dialect, because at this step
        // we have the options
        OpenDocDialect dialect = (OpenDocDialect) xmlfilter.getDialect();
        dialect.defineDialect(new OpenDocOptions(options));

        return xmlfilter;
    }

    /**
     * Returns a temporary file for OpenOffice XML. A nasty hack, to say polite way.
     */
    private File tmp() throws IOException {
        return File.createTempFile("ot-oo-", ".xml");
    }

    /**
     * Processes a single OpenDocument file, which is actually a ZIP file consisting of many XML files, some
     * of which should be translated.
     */
    @Override
    public void processFile(File inFile, File outFile, FilterContext fc) throws IOException,
            TranslationException {
        ZipFile zipfile = new ZipFile(inFile);
        ZipOutputStream zipout = null;
        if (outFile != null)
            zipout = new ZipOutputStream(new FileOutputStream(outFile));
        Enumeration<? extends ZipEntry> zipcontents = zipfile.entries();
        while (zipcontents.hasMoreElements()) {
            ZipEntry zipentry = zipcontents.nextElement();
            String shortname = zipentry.getName();
            if (shortname.lastIndexOf('/') >= 0)
                shortname = shortname.substring(shortname.lastIndexOf('/') + 1);
            if (TRANSLATABLE.contains(shortname)) {
                File tmpin = tmp();
                FileUtils.copyInputStreamToFile(zipfile.getInputStream(zipentry), tmpin);
                File tmpout = null;
                if (zipout != null)
                    tmpout = tmp();

                try {
                    createXMLFilter(processOptions).processFile(tmpin, tmpout, fc);
                } catch (Exception e) {
                    zipfile.close();
                    throw new TranslationException(e.getLocalizedMessage() + "\n"
                            + OStrings.getString("OpenDoc_ERROR_IN_FILE") + inFile);
                }

                if (zipout != null) {
                    ZipEntry outentry = new ZipEntry(zipentry.getName());
                    outentry.setMethod(ZipEntry.DEFLATED);
                    zipout.putNextEntry(outentry);
                    FileUtils.copyFile(tmpout, zipout);
                    zipout.closeEntry();
                }
                if (!tmpin.delete())
                    tmpin.deleteOnExit();
                if (tmpout != null) {
                    if (!tmpout.delete())
                        tmpout.deleteOnExit();
                }
            } else {
                if (zipout != null) {
                    ZipEntry outentry = new ZipEntry(zipentry.getName());
                    zipout.putNextEntry(outentry);
                    IOUtils.copy(zipfile.getInputStream(zipentry), zipout);
                    zipout.closeEntry();
                }
            }
        }
        if (zipout != null)
            zipout.close();
        zipfile.close();
    }

    /** Human-readable OpenDocument filter name. */
    public String getFileFormatName() {
        return OStrings.getString("OpenDoc_FILTER_NAME");
    }

    /** Extensions... */
    public Instance[] getDefaultInstances() {
        return new Instance[] { new Instance("*.sx?"), new Instance("*.st?"), new Instance("*.od?"),
                new Instance("*.ot?"), };
    }

    /** Source encoding can not be varied by the user. */
    public boolean isSourceEncodingVariable() {
        return false;
    }

    /** Target encoding can not be varied by the user. */
    public boolean isTargetEncodingVariable() {
        return false;
    }

    /** Not implemented. */
    protected void processFile(BufferedReader inFile, BufferedWriter outFile, FilterContext fc) throws IOException,
            TranslationException {
        throw new IOException("Not Implemented!");
    }

    /**
     * Returns true to indicate that the OpenDoc filter has options.
     * 
     * @return True, because the OpenDoc filter has options.
     */
    public boolean hasOptions() {
        return true;
    }

    /**
     * OpenDoc Filter shows a <b>modal</b> dialog to edit its own options.
     * 
     * @param currentOptions
     *            Current options to edit.
     * @return Updated filter options if user confirmed the changes, and current options otherwise.
     */
    public Map<String, String> changeOptions(Dialog parent, Map<String, String> currentOptions) {
        try {
            EditOpenDocOptionsDialog dialog = new EditOpenDocOptionsDialog(parent, currentOptions);
            dialog.setVisible(true);
            if (EditOpenDocOptionsDialog.RET_OK == dialog.getReturnStatus())
                return dialog.getOptions().getOptionsMap();
            else
                return null;
        } catch (Exception e) {
            Log.logErrorRB("HTML_EXC_EDIT_OPTIONS");
            Log.log(e);
            return null;
        }
    }

    @Override
    public String getInEncodingLastParsedFile() {
        //Encoding is 'binary', it is zipped. Inside there may be many files. It makes no sense to display the encoding of some xml file inside.
        return "OpenDoc";
    }
}
