/**************************************************************************
 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
               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.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;
    private final Map<String, List<GlossaryEntry>> glossaries = new TreeMap<String, List<GlossaryEntry>>();

    protected File priorityGlossary;
    protected Map<String, IGlossary> externalGlossaries = new TreeMap<String, IGlossary>(); 

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

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

    public void addGlossaryProvider(IGlossary provider) {
        externalGlossaries.put(provider.getClass().getName(),provider);
    }

    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 {
                List<GlossaryEntry> entries = loadGlossaryFile(file);
                if (entries != null) {
                    synchronized (this) {
                        Log.logRB("CT_LOADING_GLOSSARY_DETAILS", entries.size(), file.getName());
                        glossaries.put(file.getPath(), entries);
                    }
                } else {
                    IGlossary externalGlossary = buildExternalGlossaryFromFile (file);
                    if (externalGlossary != null) {
                        Map<String, IGlossary> newExternalGlossaries = new TreeMap<String, IGlossary>(externalGlossaries); 
                        newExternalGlossaries.put(file.getPath(), externalGlossary);
                        externalGlossaries = newExternalGlossaries;
                    }
                }
            } 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.
     */
    private List<GlossaryEntry> loadGlossaryFile(final File file) throws Exception {
        boolean isPriority = priorityGlossary.equals(file);
        String fname_lower = file.getName().toLowerCase();
        if (fname_lower.endsWith(OConsts.EXT_TSV_DEF)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return GlossaryReaderTSV.read(file, isPriority);
        } else if (fname_lower.endsWith(OConsts.EXT_TSV_UTF8) || fname_lower.endsWith(OConsts.EXT_TSV_TXT)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return GlossaryReaderTSV.read(file, isPriority);
        } else if (fname_lower.endsWith(OConsts.EXT_CSV_UTF8)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return GlossaryReaderCSV.read(file, isPriority);
        } else if (fname_lower.endsWith(OConsts.EXT_TBX)) {
            Log.logRB("CT_LOADING_GLOSSARY", file.getName());
            return GlossaryReaderTBX.read(file, isPriority);
        } else {
            return null;
        }
    }

    private IGlossary buildExternalGlossaryFromFile (final File file) throws Exception {
        boolean isPriority = priorityGlossary.equals(file);
        String fname_lower = file.getName().toLowerCase();
        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());
            if (file.equals(priorityGlossary)) config.setProperty("##PriorityGlossary##", "true");
            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<GlossaryEntry> getGlossaryEntries(String src) {
        List<GlossaryEntry> result = new ArrayList<GlossaryEntry>();
        synchronized (this) {
            for (List<GlossaryEntry> en : glossaries.values()) {
                result.addAll(en);
            }
        }

        addExternalGlossaryEntries(result, src);

        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<GlossaryEntry> search(String src) {
        List<GlossaryEntry> result = new ArrayList<GlossaryEntry>();
        synchronized (this) {
            for (List<GlossaryEntry> en : glossaries.values()) {
                result.addAll(en);
            }
        }

        return result;
    }

    private void addExternalGlossaryEntries(List<GlossaryEntry> result, String src) {

        Language source = Core.getProject().getProjectProperties().getSourceLanguage();
        Language target = Core.getProject().getProjectProperties().getTargetLanguage();

        for (IGlossary gl : externalGlossaries.values()) {
            try {
                result.addAll(gl.search(source, target, src));
            } catch (Exception ex) {
                Log.log(ex);
            }
        }
    }
}
