/**************************************************************************
 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
               2009 Martin Fleurke, Alex Buloichik, Didier Briel
               2012 Aaron Madlon-Kay, Thomas Cordonnier
               2013 Kyle Katarn, Aaron Madlon-Kay
               2014 Alex Buloichik
               2015-2021 Thomas Cordonnier
               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;

import java.awt.Toolkit;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.reflect.Field;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.PropertyResourceBundle;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;

import org.omegat.CLIParameters.TAG_VALIDATION_MODE;
import org.omegat.convert.ConvertConfigs;
import org.omegat.core.Core;
import org.omegat.core.CoreEvents;
import org.omegat.core.data.NotLoadedProject;
import org.omegat.core.data.ProjectProperties;
import org.omegat.core.data.RealProject;
import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.ExternalTMX;
import org.omegat.core.events.IProjectEventListener;
import org.omegat.core.matching.external.lucene.LuceneWriter;
import org.omegat.core.matching.external.lucene.LuceneGlossaryWriter;
import org.omegat.core.tagvalidation.ErrorReport;
import org.omegat.filehooks.XliffFileHook;
import org.omegat.filters2.master.PluginUtils;
import org.omegat.gui.main.ProjectUICommands;
import org.omegat.gui.stat.StatisticsWindow.STAT_TYPE;
import org.omegat.core.machinetranslators.BaseTranslate;
import org.omegat.filters2.master.PluginUtils;
import org.omegat.gui.tagvalidation.ITagValidation;
import org.omegat.gui.exttrans.IMachineTranslation;
import org.omegat.util.Language;
import org.omegat.gui.scripting.ScriptItem;
import org.omegat.gui.scripting.ScriptRunner;
import org.omegat.util.FileUtil;
import org.omegat.util.Language;
import org.omegat.util.Log;
import org.omegat.util.OConsts;
import org.omegat.util.OStrings;
import org.omegat.util.Platform;
import org.omegat.util.Preferences;
import org.omegat.util.ProjectFileStorage;
import org.omegat.util.RuntimePreferences;
import org.omegat.util.StaticUtils;
import org.omegat.util.StringUtil;
import org.omegat.util.TMXWriter2;
import org.omegat.util.gui.OSXIntegration;

import com.vlsolutions.swing.docking.DockingDesktop;

/**	
 * The main OmegaT class, used to launch the program.
 * 
 * @author Keith Godfrey
 * @author Martin Fleurke
 * @author Alex Buloichik
 * @author Didier Briel
 * @author Aaron Madlon-Kay
 * @author Kyle Katarn
 * @author Thomas Cordonnier
 */
public class Main {

    /** Regexp for parse parameters. */
    protected static final Pattern PARAM = Pattern.compile("\\-\\-([A-Za-z\\-]+)(=(.+))?");

    /** Project location for load on startup. */
    protected static File projectLocation = null;

    /** Execution command line parameters. */
    protected static final Map<String, String> params = new TreeMap<String, String>();

    /** Execution mode. */
    protected static CLIParameters.RUN_MODE runMode = CLIParameters.RUN_MODE.GUI;

    public static void main(String[] args) {
        if (args.length > 0 && (CLIParameters.HELP_SHORT.equals(args[0])
                || CLIParameters.HELP.equals(args[0]))) {
            if (args.length > 1) System.out.println(CLIParameters.help(args[1])); else System.out.println(CLIParameters.help(null));
            System.exit(0);
        }
        
        /*
         * Parse command line arguments info map.
         */
        for (String arg : args) {
            // Normalize Unicode here because e.g. OS X filesystem is NFD while
            // in Java land things are NFC
            arg = StringUtil.normalizeUnicode(arg);
            Matcher m = PARAM.matcher(arg);
            if (m.matches()) {
                params.put(m.group(1), m.group(3));
            } else {
                if (arg.startsWith(CLIParameters.RESOURCE_BUNDLE + "=")) {
                    // backward compatibility
                    params.put(CLIParameters.RESOURCE_BUNDLE,
                            arg.substring(CLIParameters.RESOURCE_BUNDLE.length()));
                } else {
                    File f = new File(arg);
                    if (f.getName().equals(OConsts.FILE_PROJECT)) {
                        f = f.getParentFile();
                    }
                    if (StaticUtils.isProjectDir(f)) {
                        projectLocation = f;
                    }
                }
            }
        }

        applyConfigFile(params.get(CLIParameters.CONFIG_FILE));

        runMode = CLIParameters.RUN_MODE.parse(params.get(CLIParameters.MODE));

        String resourceBundle = params.get(CLIParameters.RESOURCE_BUNDLE);
        if (resourceBundle != null) {
            OStrings.loadBundle(resourceBundle);
        }

        String configDir = params.get(CLIParameters.CONFIG_DIR);
        if (configDir != null) {
            if (configDir.contains("{")) {
                if (! configDir.contains("$")) configDir = configDir.replace("{","${");
                configDir = org.omegat.util.FileUtil.interpolated (configDir);
                Log.log ("Config directory set to " + configDir);
            }
            RuntimePreferences.setConfigDir(configDir);
        }

        if (params.containsKey(CLIParameters.QUIET)) {
            RuntimePreferences.setQuietMode(true);
        }

        if (params.containsKey(CLIParameters.DISABLE_PROJECT_LOCKING)) {
            RuntimePreferences.setProjectLockingEnabled(false);
        }
        
        if (params.containsKey(CLIParameters.DISABLE_LOCATION_SAVE)) {
            RuntimePreferences.setLocationSaveEnabled(false);
        }

        Log.log("\n" + "===================================================================" + "\n"
                + OStrings.getNameAndVersion() + " " + getBuildDate()
                + "\n\tLocale " + Locale.getDefault() + " Date " + new Date());

        Log.logRB("LOG_STARTUP_INFO", System.getProperty("java.vendor"), System.getProperty("java.version"),
                System.getProperty("java.home"));

        System.setProperty("http.user", OStrings.getDisplayNameAndVersion());

        ConvertConfigs.convert();
        PluginUtils.loadPlugins(params);
        
        int result;
        try {
            switch (runMode) {
            case GUI:
                result = runGUI();
                // GUI has own shutdown code
                break;
            case CONSOLE_TRANSLATE:
                result = runConsoleTranslate();
                PluginUtils.unloadPlugins();
                break;
            case CONSOLE_CREATEPSEUDOTRANSLATETMX:
                result = runCreatePseudoTranslateTMX();
                PluginUtils.unloadPlugins();
                break;
            case CONSOLE_ALIGN:
                result = runConsoleAlign();
                PluginUtils.unloadPlugins();
                break;
            case CONSOLE_INDEX_MEMORY:
                result = runConsoleIndexMemory();
                PluginUtils.unloadPlugins();
                break;
            case CONSOLE_INDEX_GLOSSARY:
                result = runConsoleIndexGlossary();
                PluginUtils.unloadPlugins();
                break;
            case CONSOLE_EXTRACT_XLIFF:
                result = runConsoleExtractXliff();
                PluginUtils.unloadPlugins();
                break;
            case CONSOLE_CALC_STATS:
                result = runConsoleCalcStats();
                PluginUtils.unloadPlugins();
                break;
             default:
                result = 1;
            }
        } catch (Throwable ex) {
            Log.log(ex);
            showError(ex);
            result = 1;
        }
        
        if (params.containsKey("server-object")) 
            try {
                org.omegat.core.matching.external.rmi.Server.open(params.get("server-port"), params.get("server-object"));
            } catch (Exception rmiEx) {
                rmiEx.printStackTrace();
            }

        if (result != 0) {
            System.exit(result);
        }
    }
    
    /** When was the JAR built **/
    private static String getBuildDate() {
        try {
            java.net.JarURLConnection j = (java.net.JarURLConnection) ClassLoader.getSystemResource("org/omegat/Version.properties").openConnection();
            return " (" + new Date(j.getJarFile().getEntry("META-INF/MANIFEST.MF").getTime()) + ")";
        } catch (Exception e) {
            return "";
        }
    }

    /**
     * Load System properties from a specified .properties file. In order to
     * allow this to reliably change the display language, it must called before
     * any use of {@link Log#log}, thus it logs to {@link System#out}.
     * 
     * @param path
     *            to config file
     */
    private static void applyConfigFile(String path) {
        if (path == null) {
            return;
        }
        File configFile = new File(path);
        if (!configFile.exists()) {
            return;
        }
        System.out.println("Reading config from " + path);
        try {
            PropertyResourceBundle config = new PropertyResourceBundle(new FileInputStream(configFile));
            // Put config properties into System properties and into OmegaT params.
            for (String key : config.keySet()) {
                String value = config.getString(key);
                System.setProperty(key, value);
                params.put(key, value);
                System.out.println("Read from config: " + key + "=" + value);
            }
            // Apply language preferences, if present.
            // This must be done with Locale.setDefault(). Merely doing
            // System.setProperty() will not work.
            if (config.containsKey("user.language")) {
                String userLanguage = config.getString("user.language");
                Locale userLocale = config.containsKey("user.country")
                        ? new Locale(userLanguage, config.getString("user.country"))
                        : new Locale(userLanguage);
                Locale.setDefault(userLocale);
            }
        } catch (FileNotFoundException exception) {
            System.err.println("Config file not found: " + path);
        } catch (IOException exception) {
            System.err.println("Error while reading config file: " + path);
        }
    }
    
    /**
     * Execute standard GUI.
     */
    protected static int runGUI() {
        // MacOSX-specific - they must be setted BEFORE any GUI calls
        if (Platform.isMacOSX()) {
            OSXIntegration.init();
        }

        Log.log("Docking Framework version: " + DockingDesktop.getDockingFrameworkVersion());
        Log.log("");

        // Set X11 application class name to make some desktop user interfaces
        // (like Gnome Shell) recognize OmegaT
        Toolkit toolkit = Toolkit.getDefaultToolkit();
        Class<?> cls = toolkit.getClass();
        try
        {
            if (cls.getName().equals("sun.awt.X11.XToolkit"))
            {
                Field field = cls.getDeclaredField("awtAppClassName");
                field.setAccessible(true);
                field.set(toolkit, "OmegaT");
            }
        }
        catch (Exception e)
        {
            // do nothing
        }

        try {
            // Workaround for JDK bug 6389282 (OmegaT bug bug 1555809)
            // it should be called before setLookAndFeel() for GTK LookandFeel
            // Contributed by Masaki Katakai (SF: katakai)
            UIManager.getInstalledLookAndFeels();

            UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            
            System.setProperty("swing.aatext", "true");

        } catch (Exception e) {
            // do nothing
            Log.logErrorRB("MAIN_ERROR_CANT_INIT_OSLF");
        }

        try {
            Core.initializeGUI(params);
        } catch (Throwable ex) {
            Log.log(ex);
            showError(ex);
            return 1;
        }

        if (!Core.getPluginsLoadingErrors().isEmpty()) {
            String err = "";
            for (int i = 0; i < Core.getPluginsLoadingErrors().size(); i++) {
                err += "\n" + Core.getPluginsLoadingErrors().get(i);
            }
            err = err.substring(1);
            JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), err,
                    OStrings.getString("STARTUP_ERRORBOX_TITLE"), JOptionPane.ERROR_MESSAGE);
        }

        CoreEvents.fireApplicationStartup();

        SwingUtilities.invokeLater(new Runnable() {
            public void run() {
                // setVisible can't be executed directly, because we need to
                // call all application startup listeners for initialize UI
                Core.getMainWindow().getApplicationFrame().setVisible(true);

                if (projectLocation != null) {
                    ProjectUICommands.projectOpen(projectLocation);
                }
            }
        });
        return 0;
    }

    /**
     * Execute in console mode for translate.
     */
    protected static int runConsoleTranslate() throws Exception {
        Log.log("Console translation mode");
        Log.log("");

        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);

        RealProject p = selectProjectConsoleMode(true);

        validateTagsConsoleMode();

        System.out.println(OStrings.getString("CONSOLE_TRANSLATING"));

        String sourceMask = params.get(CLIParameters.SOURCE_PATTERN);
        if (sourceMask != null)
            p.compileProject(sourceMask, false);
        else
            p.compileProject(".*", false);

        // Called *after* executing post processing command (unlike the 
        // regular PROJECT_CHANGE_TYPE.COMPILE)
        executeConsoleScript(IProjectEventListener.PROJECT_CHANGE_TYPE.COMPILE);

        p.closeProject();
        executeConsoleScript(IProjectEventListener.PROJECT_CHANGE_TYPE.CLOSE);
        System.out.println(OStrings.getString("CONSOLE_FINISHED"));
        
        return 0;
    }
    
    /**
     * Validates tags according to command line specs:
     * --tag-validation=[abort|warn]
     * 
     * On abort, the program is aborted when tag validation finds errors. 
     * On warn the errors are printed but the program continues.
     * In all other cases no tag validation is done.
     */
    private static void validateTagsConsoleMode() {
        TAG_VALIDATION_MODE mode = TAG_VALIDATION_MODE.parse(params.get(CLIParameters.TAG_VALIDATION));

        List<ErrorReport> stes;

        switch (mode) {
        case ABORT:
            System.out.println(OStrings.getString("CONSOLE_VALIDATING_TAGS"));
            stes = Core.getTagValidation().listInvalidTags();
            if (stes != null) {
                Core.getTagValidation().displayTagValidationErrors(stes, null);
                System.out.println(OStrings.getString("CONSOLE_TAGVALIDATION_FAIL"));
                System.out.println(OStrings.getString("CONSOLE_TAGVALIDATION_ABORT"));
                System.exit(1);
            }
            break;
        case WARN:
            System.out.println(OStrings.getString("CONSOLE_VALIDATING_TAGS"));
            stes = Core.getTagValidation().listInvalidTags();
            if (stes != null) {
                Core.getTagValidation().displayTagValidationErrors(stes, null);
                System.out.println(OStrings.getString("CONSOLE_TAGVALIDATION_FAIL"));
            }
            break;
        default:
            //do not validate tags = default
        }
    }

    /**
     * Execute in console mode for translate.
     */
    protected static int runCreatePseudoTranslateTMX() throws Exception {
        Log.log("Console pseudo-translate mode");
        Log.log("");

        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);

        RealProject p = selectProjectConsoleMode(true);

        validateTagsConsoleMode();

        System.out.println(OStrings.getString("CONSOLE_CREATE_PSEUDOTMX"));

        ProjectProperties m_config = p.getProjectProperties();
        List<SourceTextEntry> entries = p.getAllEntries();
        String pseudoTranslateTMXFilename = params.get(CLIParameters.PSEUDOTRANSLATETMX);

        String fname;
        if (!StringUtil.isEmpty(pseudoTranslateTMXFilename)) {
            if (!pseudoTranslateTMXFilename.endsWith(OConsts.TMX_EXTENSION)) {
                fname = pseudoTranslateTMXFilename + "." + OConsts.TMX_EXTENSION;
            } else {
                fname = pseudoTranslateTMXFilename;
            }
        } else {
            fname = "";
        }

        IMachineTranslation translator = null;
        String pseudoTranslateType = params.get("pseudotranslatetype");
        pseudoTranslateType = Character.toTitleCase(pseudoTranslateType.charAt(0)) + pseudoTranslateType.substring(1).toLowerCase();
        try {
            Class<IMachineTranslation> cls = (Class<IMachineTranslation>) Class.forName("org.omegat.core.machinetranslators." + pseudoTranslateType + "Translate");
            translator = cls.newInstance();
        } catch (ClassNotFoundException cnf1) {
            try {
                Class<IMachineTranslation> cls = (Class<IMachineTranslation>) Class.forName("org.omegat.core.machinetranslators.dummy." + pseudoTranslateType + "Translate");
                translator = cls.newInstance();
            } catch (ClassNotFoundException cnf2) {
                try {
                    Class<IMachineTranslation> cls = (Class<IMachineTranslation>) Class.forName("org.omegat.core.machinetranslators.net." + pseudoTranslateType + "Translate");
                    translator = cls.newInstance();
                } catch (ClassNotFoundException cnf3) {
                    Log.log ("No such translation class");
                    System.exit(1);
                }
            }
        }
        try {
            BaseTranslate t = (BaseTranslate) translator;
            t.enable();
        } catch (ClassCastException cceBase) {
            // Not an error: other engines may be always enabled
        }

        TMXWriter2 wr = new TMXWriter2(new File(fname), m_config.getSourceLanguage(), m_config.getTargetLanguage(),
                    m_config.isSentenceSegmentingEnabled(), false, false);
        try {
            // Write OmegaT-project-compatible TMX:
            for (SourceTextEntry ste : entries) 
                wr.writeEntry(ste.getSrcText(), 
                    translator.getTranslation (m_config.getSourceLanguage(), m_config.getTargetLanguage(), ste.getSrcText()),
                    null, null, 0, null, 0, null);                
        } catch (IOException e) {
            Log.logErrorRB("CT_ERROR_CREATING_TMX");
            Log.log(e);
            throw new IOException(OStrings.getString("CT_ERROR_CREATING_TMX") + "\n" + e.getMessage());
        } finally {
            wr.close();
        }
        p.closeProject();
        System.out.println(OStrings.getString("CONSOLE_FINISHED"));
        return 0;
    }

    public static int runConsoleAlign() throws Exception {
        Log.log("Console alignment mode");
        Log.log("");

        if (projectLocation == null) {
            System.out.println(OStrings.getString("PP_ERROR_UNABLE_TO_READ_PROJECT_FILE"));
            return 1;
        }

        String dir = params.get(CLIParameters.ALIGNDIR);
        if (dir == null) {
            System.out.println(OStrings.getString("CONSOLE_TRANSLATED_FILES_LOC_UNDEFINED"));
            return 1;
        }

        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);
        RealProject p = selectProjectConsoleMode(true);

        validateTagsConsoleMode();

        System.out.println(StringUtil.format(OStrings.getString("CONSOLE_ALIGN_AGAINST"), dir));

        String tmxFile = p.getProjectProperties().getProjectInternal() + "align.tmx";
        ProjectProperties config = p.getProjectProperties();

        try(TMXWriter2 wr = new TMXWriter2(new File(tmxFile), config.getSourceLanguage(), config.getTargetLanguage(),
                config.isSentenceSegmentingEnabled(), false, false)) {
            wr.writeEntries(p.align(p.getProjectProperties(), new File(dir)));
        }
        p.closeProject();
        System.out.println(OStrings.getString("CONSOLE_FINISHED"));
        return 0;
    }

    /**
     * Moves tmx files to Lucene indexes
     */
    protected static int runConsoleIndexMemory() throws Exception {
        Log.log("Console index memory");
        Log.log("");

        
        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);
        
        String tmxPath = params.get("source");
        List<File> sourceFiles, targetDirs, saveDirs;
        boolean isSentenceSegmentingEnabled;
        Language sourceLanguage, targetLanguage;
        if (tmxPath != null) { 		// index only one single file
            String targetPath = params.get("target"), sourceLang = params.get("source-lang"), targetLang = params.get("target-lang");
            File tmxFile = new File (tmxPath), targetDir = new File (targetPath);
            if (! tmxFile.exists()) {
                Log.log ("File " + tmxPath + " not found. Exit");
                return 1;
            } else if ( targetDir.exists()) {
                if (! targetDir.isDirectory()) {
                    Log.log ("Target path " + targetPath + " is not a directory. Exit.");
                    return 1;
                }
            } else if (sourceLang == null) {
                Log.log ("Missing parameter source-lang");
                return 1;
            } else if (targetLang == null) {
                Log.log ("Missing parameter target-lang");
                return 1;
            }
            sourceFiles = new ArrayList<File>(1); sourceFiles.add(tmxFile);
            targetDirs = new ArrayList<File>(1); targetDirs.add(targetDir);
            saveDirs = null;
            sourceLanguage = new Language (sourceLang);
            targetLanguage = new Language (targetLang);
            isSentenceSegmentingEnabled = params.get("source-segmenting-enabled") != null;
        } else { 		// index a project
            if (projectLocation == null) {
                System.out.println(OStrings.getString("PP_ERROR_UNABLE_TO_READ_PROJECT_FILE"));
                return 1;
            }
            RealProject p = selectProjectConsoleMode(false);
            sourceFiles = FileUtil.findFiles (new File(p.getProjectProperties().getTMRoot()), f -> f.getName().endsWith(".tmx"));
            targetDirs = new java.util.ArrayList(sourceFiles.size());
            saveDirs = new java.util.ArrayList(sourceFiles.size());
            String projRoot = p.getProjectProperties().getProjectRoot(), tmRoot = p.getProjectProperties().getTMRoot();
            if (! tmRoot.endsWith(File.separator)) tmRoot += File.separator;
            String idxRoot = projRoot; if (! idxRoot.endsWith(File.separator)) idxRoot += File.separator;
            idxRoot += "tm-indexes"; new File(idxRoot).mkdirs();
            for (File sourceFile: sourceFiles) {
                targetDirs.add (new File (idxRoot + File.separator + sourceFile.getPath().substring(tmRoot.length()) + ".ottm"));
                if (params.get("save-to") != null)
					saveDirs.add(new File(params.get("save-to") + sourceFile.getPath().substring(tmRoot.length())));
                else if (params.containsKey("save-files"))
                    saveDirs.add(new File(projRoot + File.separator + "tm-saved" + File.separator + sourceFile.getPath().substring(tmRoot.length())));
            }
            sourceLanguage = p.getProjectProperties().getSourceLanguage();
            targetLanguage = p.getProjectProperties().getTargetLanguage();
            isSentenceSegmentingEnabled = p.getProjectProperties().isSentenceSegmentingEnabled();
        }
        
        for (int i = 0; i < targetDirs.size(); i++)
            try {
                try (FileOutputStream propFile = new FileOutputStream (sourceFiles.get(i) + ".properties")) {
                    try (PrintWriter pOut = new PrintWriter (propFile)) {
                        pOut.println("class=org.omegat.core.matching.external.lucene.LuceneReader");
                        StringBuffer targetBuf = new StringBuffer(); char[] targetChars = targetDirs.get(i).toString().toCharArray();
                        for (int j = 0; j < targetChars.length; j++)
                            if (targetChars[j] == '\\') targetBuf.append("\\\\");
                            else if (targetChars[j] > 0x0080) targetBuf.append("\\u").append(String.format("%04x", (int) targetChars[j]));
                            else targetBuf.append(targetChars[j]);
                        pOut.println("dir=" + targetBuf);						
                    }
                }
                targetDirs.get(i).mkdir();
                LuceneWriter writer = new LuceneWriter (targetDirs.get(i),true);
                ExternalTMX tmx = new ExternalTMX (isSentenceSegmentingEnabled,  
                    sourceLanguage, targetLanguage, sourceFiles.get(i),
                    Preferences.isPreference(Preferences.EXT_TMX_SHOW_LEVEL2),
                    Preferences.isPreference(Preferences.EXT_TMX_USE_SLASH));
                writer.calcstopNgrams (tmx.browseAllEntries());
                writer.indexAll (tmx.browseAllEntries());
                writer.close();
                if (tmxPath == null)
                    if ((saveDirs.size() > i) && (saveDirs.get(i) != null)) {
                        saveDirs.get(i).getParentFile().mkdirs();
                        sourceFiles.get(i).renameTo(saveDirs.get(i));
                    } else {
                        sourceFiles.get(i).delete();
                    }
            } catch (Exception e) {
                Log.log(e);
                return 1;
            }
        
        return 0;
    }
    
    /**
     * Moves tmx files to Lucene indexes
     */
    protected static int runConsoleIndexGlossary() throws Exception {
        Log.log("Console index glossary");
        Log.log("");
        
        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);
        
        String glosPath = params.get("source");
        List<File> sourceFiles, targetDirs, saveDirs;
        boolean isSentenceSegmentingEnabled;
        Language sourceLanguage, targetLanguage;
        if (glosPath != null) { 		// index only one single file
            String targetPath = params.get("target"), sourceLang = params.get("source-lang"), targetLang = params.get("target-lang");
            File glosFile = new File (glosPath), targetDir = new File (targetPath);
            if (! glosFile.exists()) {
                Log.log ("File " + glosFile + " not found. Exit");
                return 1;
            } else if ( targetDir.exists()) {
                if (! targetDir.isDirectory()) {
                    Log.log ("Target path " + targetPath + " is not a directory. Exit.");
                    return 1;
                }
            } else if (sourceLang == null) {
                Log.log ("Missing parameter source-lang");
                return 1;
            } else if (targetLang == null) {
                Log.log ("Missing parameter target-lang");
                return 1;
            }
            sourceFiles = new ArrayList<File>(1); sourceFiles.add(glosFile);
            targetDirs = new ArrayList<File>(1); targetDirs.add(targetDir);
            saveDirs = null;
            sourceLanguage = new Language (sourceLang);
            targetLanguage = new Language (targetLang);
            isSentenceSegmentingEnabled = params.get("source-segmenting-enabled") != null;
        } else { 		// index a project
            if (projectLocation == null) {
                System.out.println(OStrings.getString("PP_ERROR_UNABLE_TO_READ_PROJECT_FILE"));
                return 1;
            }
            RealProject p = selectProjectConsoleMode(false);
            String projRoot = p.getProjectProperties().getProjectRoot(), glosRoot = p.getProjectProperties().getGlossaryRoot();
            sourceFiles = FileUtil.findFiles (new File(glosRoot), f -> f.getName().endsWith(".tab") || f.getName().endsWith(".txt") || f.getName().endsWith(".utf8") || f.getName().endsWith(".tbx"));
            targetDirs = new java.util.ArrayList(sourceFiles.size());
            saveDirs = new java.util.ArrayList(sourceFiles.size());
            if (! glosRoot.endsWith(File.separator)) glosRoot += File.separator;
            String idxRoot = projRoot; if (! idxRoot.endsWith(File.separator)) idxRoot += File.separator;
            idxRoot += "glos-indexes"; new File(idxRoot).mkdirs();
            for (File sourceFile: sourceFiles) {
                targetDirs.add (new File (idxRoot + File.separator + sourceFile.getPath().substring(glosRoot.length()) + ".otgs"));
                if (params.get("save-to") != null)
					saveDirs.add(new File(params.get("save-to") + sourceFile.getPath().substring(glosRoot.length())));
                else if (params.containsKey("save-files"))
                    saveDirs.add(new File(projRoot + File.separator + "glos-saved" + File.separator + sourceFile.getPath().substring(glosRoot.length())));
            }
            sourceLanguage = p.getProjectProperties().getSourceLanguage();
            targetLanguage = p.getProjectProperties().getTargetLanguage();
            isSentenceSegmentingEnabled = p.getProjectProperties().isSentenceSegmentingEnabled();
        }
        
        for (int i = 0; i < targetDirs.size(); i++)
            try {
                try (FileOutputStream propFile = new FileOutputStream (sourceFiles.get(i) + ".properties")) {
                    try (PrintWriter pOut = new PrintWriter (propFile)) {
                        pOut.println("class=org.omegat.core.matching.external.lucene.LuceneGlossaryReader");
                        StringBuffer targetBuf = new StringBuffer(); char[] targetChars = targetDirs.get(i).toString().toCharArray();
                        for (int j = 0; j < targetChars.length; j++)
                            if (targetChars[j] == '\\') targetBuf.append("\\\\");
                            else if (targetChars[j] > 0x0080) targetBuf.append("\\u").append(String.format("%04x", (int) targetChars[j]));
                            else targetBuf.append(targetChars[j]);
                        pOut.println("dir=" + targetBuf);						
                    }
                }
                targetDirs.get(i).mkdir();
                
                org.omegat.tokenizer.BaseTokenizer srcTok = (org.omegat.tokenizer.BaseTokenizer) PluginUtils.getTokenizerClassForLanguage(sourceLanguage).newInstance();
                LuceneGlossaryWriter writer = new LuceneGlossaryWriter (targetDirs.get(i),srcTok.getGlossaryAnalyser(),true);
                org.omegat.core.glossaries.IGlossary loadGlos = org.omegat.gui.glossary.GlossaryManager.loadGlossaryFile(sourceFiles.get(i));
                writer.indexAll (loadGlos.search(sourceLanguage, targetLanguage, ""));
                writer.close();
                if (glosPath == null)
                    if ((saveDirs.size() > i) && (saveDirs.get(i) != null)) {
                        saveDirs.get(i).getParentFile().mkdirs();
                        sourceFiles.get(i).renameTo(saveDirs.get(i));
                    } else {
                        sourceFiles.get(i).delete();
                    }
            } catch (Exception e) {
                Log.log(e);
                return 1;
            }
        
        return 0;
    }
    
    
    /**
     * Extract the native file embedded inside an SDLXLIFF
     */
    protected static int runConsoleExtractXliff() throws Exception {
        Log.log("Console extract XLIFF");
        Log.log("");

        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);
        
        List<File> sourceFiles, extractedFiles;
        if (projectLocation != null) {	// extract for one project: from 'source' to 'source-native'
            RealProject p = selectProjectConsoleMode(false);
            sourceFiles = FileUtil.findFiles (new File(p.getProjectProperties().getSourceRoot()), f -> f.getName().endsWith(".sdlxliff"));
            extractedFiles = new ArrayList<>(sourceFiles.size());
            for (File f: sourceFiles) extractedFiles.add(new File(
                f.getParentFile().getParentFile(), 
                XliffFileHook.SOURCE_NATIVE_DIR + File.separator + f.getName().substring(0, f.getName().lastIndexOf("."))));
        } else {	// extract for individual files
            File source = new File(params.get("source"));
            if (source.getName().endsWith(".sdlxliff")) { 
                sourceFiles = java.util.Collections.singletonList(source);
                String target = params.get("target");
                if (target != null) extractedFiles = java.util.Collections.singletonList(new File(target, source.getName().substring(0, source.getName().lastIndexOf("."))));
                else extractedFiles = java.util.Collections.singletonList(new File(source.getCanonicalPath().substring(0, source.getCanonicalPath().lastIndexOf("."))));
            } else if (source.isDirectory()) {
                sourceFiles = FileUtil.findFiles (source, f -> f.getName().endsWith(".sdlxliff"));
                extractedFiles = new ArrayList<>(sourceFiles.size());
                for (File f: sourceFiles) extractedFiles.add(new File(f.getCanonicalPath().substring(0,f.getCanonicalPath().lastIndexOf("."))));				
            } else throw new Exception ("Wrong parameters: cannot see which files to convert");
        }
        
        for (int i = 0; i < sourceFiles.size(); i++)
            try {
                System.out.println("Extracting " + sourceFiles.get(i) + " to " + extractedFiles.get(i));
                if (! extractedFiles.get(i).getParentFile().exists()) extractedFiles.get(i).getParentFile().mkdirs(); 
                XliffFileHook.extractBase64(sourceFiles.get(i), null, extractedFiles.get(i).getCanonicalPath());
            } catch (Exception e) {
                e.printStackTrace();
            }
        
        return 0;
    }
    
    /**
     * Opens project, launch statistics, produces the file and exit
     */
    protected static int runConsoleCalcStats() throws Exception {
        Log.log("Console calculate statistics");
        Log.log("");
        
        class DummyPanel implements org.omegat.core.statistics.IStatisticsPanel.Standard, org.omegat.core.statistics.IStatisticsPanel.Matches {
            // Useless methods, only to implement the interfaces
            public void finishData() {} 
            public void setTextData(String data) {}
            public void setDataFile(String path) {}
            public void setProjectTableData(final String[] headers, final String[][] projectData) {}
            public void setFilesTableData(final String[] headers, final String[][] filesData) {}
            public void appendTextData(final String result) {}
            public void setTable(String[] headers, String[][] data) {}
            public void appendTable(String title, String[] headers, String[][] data) {}
            
            // Only this method can do something interesting, showing the log
            public void showProgress(int percent) {
                System.out.println("Progressing: " + percent + " %");
            }
        }

        System.out.println(OStrings.getString("CONSOLE_INITIALIZING"));
        Core.initializeConsole(params);
        org.omegat.core.threads.LongProcessThread thread = null;
        RealProject p = selectProjectConsoleMode(true);
        switch (STAT_TYPE.valueOf(params.get("stats-mode"))) {
          case STANDARD:   thread = new org.omegat.core.statistics.CalcStandardStatistics(new DummyPanel()); break;
          case MATCHES:  thread = new org.omegat.core.statistics.CalcMatchStatistics(new DummyPanel(), false); break;
          case MATCHES_PER_FILE:  thread = new org.omegat.core.statistics.CalcMatchStatistics(new DummyPanel(), true); break;
          case REVISION:  thread = new org.omegat.core.statistics.CalcRevisionStatistics(new DummyPanel()); break;
        }
        thread.run(); // in command line, no real need to create a thread
        p.closeProject();
        System.out.println(OStrings.getString("CONSOLE_FINISHED"));
        
        return 0;
    }
    
    
    /**
     * creates the project class and adds it to the Core. Loads the project if
     * specified. An exit occurs on error loading the project. This method is
     * for the different console modes, to prevent code duplication.
     * 
     * @param loadProject
     *            load the project or not
     * @return the project.
     */
    private static RealProject selectProjectConsoleMode(boolean loadProject) {
        System.out.println(OStrings.getString("CONSOLE_LOADING_PROJECT"));

        // check if project okay
        ProjectProperties projectProperties = null;
        try {
            projectProperties = ProjectFileStorage.loadProjectProperties(projectLocation);
            projectProperties.verifyProject();
        } catch (Exception ex) {
            Log.logErrorRB(ex, "PP_ERROR_UNABLE_TO_READ_PROJECT_FILE");
            System.out.println(OStrings.getString("PP_ERROR_UNABLE_TO_READ_PROJECT_FILE"));
            System.exit(1);
        }

        RealProject p = new RealProject(projectProperties);
        Core.setProject(p);
        if (loadProject) {
            p.loadProject(true);
            if (!p.isProjectLoaded()) {
                Core.setProject(new NotLoadedProject());
            } else {
                executeConsoleScript(IProjectEventListener.PROJECT_CHANGE_TYPE.LOAD);
            }

        }
        return p;
    }
    
    /** Execute script as PROJECT_CHANGE events. We can't use the regular project listener because 
     *  the SwingUtilities.invokeLater method used in CoreEvents doesn't stop the project processing
     *  in console mode. 
     */
    private static void executeConsoleScript(IProjectEventListener.PROJECT_CHANGE_TYPE eventType) {
        if (params.containsKey(CLIParameters.SCRIPT)) {
    		File script = new File(params.get("script").toString());

            if (script.isFile()) {
    			HashMap<String, Object> binding = new HashMap<String, Object>();
    			binding.put("eventType", eventType);
    			binding.put("console", new org.omegat.gui.scripting.IScriptLogger() {
                    public void print(Object o) { System.out.print(o); }
                    public void println(Object o) { System.out.println(o); }
                    public void clear() { } // Nothing to do 					
				});
                try {
                    String result = ScriptRunner.executeScript(new ScriptItem(script), binding);
                    Log.log(result);
                } catch (Exception ex) {
                    Log.log(ex);
                }
    		}
    	}
    }


    public static void showError(Throwable ex) {
        String msg;
        if (StringUtil.isEmpty(ex.getMessage())) {
            msg = ex.getClass().getName();
        } else {
            msg = ex.getMessage();
        }
        switch (runMode) {
        case GUI:
            JOptionPane.showMessageDialog(JOptionPane.getRootFrame(), msg,
                    OStrings.getString("STARTUP_ERRORBOX_TITLE"), JOptionPane.ERROR_MESSAGE);
            break;
        default:
            System.err.println(MessageFormat.format(OStrings.getString("CONSOLE_ERROR"), msg));
            break;
        }
    }
}
