/**************************************************************************
 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-2010 Alex Buloichik
               2013 Alex Buloichik, Thomas Cordonnier
               2015 Didier Briel
               2019 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.gui.glossary;

import java.io.File;
import java.io.FileInputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;

import org.omegat.core.Core;
import org.omegat.core.glossaries.IGlossary;
import org.omegat.core.glossaries.GlossaryEntryStore;
import org.omegat.filters2.master.PluginUtils;
import org.omegat.util.DirectoryMonitor;
import org.omegat.util.Language;
import org.omegat.util.Log;
import org.omegat.util.OConsts;

/**
 * Class that loads glossary files and adds glossary entries to strings of the source files.
 * 
 * This class don't need any threads synchronization code, since it only set and clear 'glossaryEntries' var.
 * 
 * @author Keith Godfrey
 * @author Maxym Mykhalchuk
 * @author Alex Buloichik <alex73mail@gmail.com>
 * @author Didier Briel
 * @author Thomas Cordonnier
 */
public class GlossaryManager implements DirectoryMonitor.Callback {

    protected DirectoryMonitor monitor;

    private final GlossaryTextArea pane;
    protected final Map<String, IGlossary> glossaries = new TreeMap<String, IGlossary>();

    protected File priorityGlossary;

    public GlossaryManager(final GlossaryTextArea pane) {
        this.pane = pane;

        for (Class<?> glc : PluginUtils.getGlossaryClasses()) {
            try {
                IGlossary instance = (IGlossary) glc.newInstance();
                glossaries.put(instance.getLongName(), instance);
            } catch (Exception ex) {
                Log.log(ex);
            }
        }
    }

    public void addGlossaryProvider(IGlossary provider) {
        glossaries.put(provider.getLongName(),provider);
    }
	
	public IGlossary getPriorityGlossary() {
		return glossaries.get(priorityGlossary.getPath());
	}

    public void start() {
        File dir = new File(Core.getProject().getProjectProperties().getGlossaryRoot());
        priorityGlossary = new File(Core.getProject().getProjectProperties().getWriteableGlossary());
        monitor = new DirectoryMonitor(dir, this);
        monitor.start();
    }

    public void stop() {
        monitor.fin();
        synchronized (this) {
            glossaries.clear();
        }
    }

    @Override
    public void fileChanged(File file) {
        synchronized (this) {
            glossaries.remove(file.getPath());
        }
        if (file.exists()) {
            try {
                IGlossary glos = loadGlossaryFile(file);
                if ((glos != null) && glos.hasContents()) {
                    synchronized (this) {
                        Log.logRB("CT_LOADING_GLOSSARY_DETAILS", "", file.getName());
                        glossaries.put(file.getPath(), glos);
                    }
                }
            } catch (Exception ex) {
                Log.logRB("CT_ERROR_ACCESS_GLOSSARY_DIR");
                Log.log(ex);
            }
        }
        pane.refresh();
    }

    public void forceReloadTBX() {
        Set<File> files = monitor.getExistFiles();
        for (File f : files) {
            if (f.getName().toLowerCase().endsWith(OConsts.EXT_TBX)) {
                fileChanged(f);
            }
        }
    }
    
    public void forceUpdateGlossary() {
        pane.refresh();
    }

    /**
     * Loads one glossary file. It choose and calls required required reader.
     */
    public static IGlossary loadGlossaryFile(final File file) throws Exception {
        String fname_lower = file.getName().toLowerCase();
        if (fname_lower.endsWith(OConsts.EXT_TSV_DEF)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return new GlossaryReaderTSV(file);
        } else if (fname_lower.endsWith(OConsts.EXT_TSV_UTF8) || fname_lower.endsWith(OConsts.EXT_TSV_TXT)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return new GlossaryReaderTSV(file);
        } else if (fname_lower.endsWith(OConsts.EXT_CSV_UTF8)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return new GlossaryReaderCSV(file);
        } else if (fname_lower.endsWith(OConsts.EXT_TBX)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return new GlossaryReaderTBX(file);
        } else if (fname_lower.endsWith(OConsts.PROPERTIES_EXTENSION)) {
            Properties config = new Properties();
            config.load (new FileInputStream (file));
            if (config.getProperty("name") == null) config.setProperty("name", file.toString());
            Class<? extends IGlossary> glosClass = (Class<? extends IGlossary>) PluginUtils.getPluginsClassLoader().loadClass(config.getProperty("class"));
            Constructor<? extends IGlossary> cons = glosClass.getConstructor (Properties.class);
            return (IGlossary) cons.newInstance (config);
        } else {
            return null;
        }
    }
    
    /**
     * Get glossary entries.
     * 
     * @return all entries
     * @param src
     */
    public List<GlossaryEntryStore> getGlossaryEntries(String src) {
        Language source = Core.getProject().getProjectProperties().getSourceLanguage();
        Language target = Core.getProject().getProjectProperties().getTargetLanguage();

        List<GlossaryEntryStore> result = new ArrayList<GlossaryEntryStore>();
        synchronized (this) {
            for (IGlossary en : glossaries.values())
                try { for (GlossaryEntryStore ge: en.search(source, target, src)) result.add(ge); }
                catch (Exception ex) { Log.log(ex); }
        }

        return result;
    }

    /**
     * Get glossary entries for search operation. Almost the same as getGlossaryEntries(), except search
     * usually executed for every segment in project, i.e. should work enough fast. Then, search should be
     * produced by local files only.
     * 
     * @return all entries
     * @param src
     */
    public List<GlossaryEntryStore> search(String src) {
        Language source = Core.getProject().getProjectProperties().getSourceLanguage();
        Language target = Core.getProject().getProjectProperties().getTargetLanguage();

        List<GlossaryEntryStore> result = new ArrayList<GlossaryEntryStore>();
        synchronized (this) {
            for (IGlossary en : glossaries.values()) 
                if (! en.isExternal())
                    try { for (GlossaryEntryStore ge: en.search(source, target, src)) result.add(ge); }
                    catch (Exception ex) { Log.log(ex); }
        }

        return result;
    }
}
