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

 Copyright (C) 2010 Alex Buloichik, Didier Briel
               2011 Didier Briel, Guido Leenders
               2019-2020 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.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.omegat.core.Core;
import org.omegat.core.glossaries.GlossaryEntryWithProperties;
import org.omegat.core.glossaries.GlossaryEntryStore;
import org.omegat.core.glossaries.FileGlossary;
import org.omegat.core.glossaries.IGlossary;
import org.omegat.util.Log;
import org.omegat.util.Language;
import org.omegat.util.OStrings;
import org.omegat.util.Preferences;
import org.omegat.util.TMXProp;

import javax.xml.stream.XMLStreamReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamConstants;
import javax.xml.stream.XMLStreamException;

/**
 * Reader for TBX glossaries.
 * 
 * @author Alex Buloichik <alex73mail@gmail.com>
 * @author Didier Briel
 * @author Guido Leenders
 * @author Thomas Cordonnier
 */
public class GlossaryReaderTBX extends FileGlossary {
    private static final XMLInputFactory factory = XMLInputFactory.newInstance();	

    public GlossaryReaderTBX(final File file) throws Exception {
        super(file); 
        TBXContents contents = readMartif(new FileInputStream(file), this); 
        this.entries = contents.entries; this.fileInfo = contents;
    }

    /** Enable to read TBX entries from a string. This is not a constructor, it does only build a list, not a glossary **/
    public static TBXContents readFromString(final String data, IGlossary origin) throws Exception {
        return readMartif(new ByteArrayInputStream(data.getBytes()), origin);
    }
    
    public static class TBXInfo {
        public String version = null, dialect = null;
        
        public String getFormat() { return "TBX" + (version == null ? "" : " " + version) + (dialect == null ? "" : " " + dialect); }
    }
    private TBXInfo fileInfo;
    public String getFormat() { return fileInfo.getFormat(); }
    
    public static class TBXContents extends TBXInfo {
        public List<GlossaryEntryStore> entries = new ArrayList<GlossaryEntryStore>();
    }
    
    private static TBXContents readMartif(final InputStream is, IGlossary origin) throws Exception {
        try {
            XMLStreamReader reader = factory.createXMLStreamReader(is);
            TBXContents contents = new TBXContents();
            while (reader.hasNext()) {
                if (reader.next() == XMLStreamConstants.START_ELEMENT)
                    if (reader.getLocalName().equals("termEntry") || reader.getLocalName().equals("conceptEntry")) readTermEntry(contents.entries, reader, origin);
                    else if (reader.getLocalName().equals("tbx")) { contents.version = "3.0"; contents.dialect = reader.getAttributeValue(null, "type"); }
                    else if (reader.getLocalName().equals("martif")) contents.version = "2.0"; 
                    else if ("XCSURI".equals(reader.getAttributeValue(null,"type"))) { reader.next(); contents.dialect = reader.getText(); }
            }
            Log.log(origin.getShortName() + ": Read " + contents.entries.size() + " entries");
            return contents;
        } finally {
            try { is.close(); } catch (Exception e) {}
        }
    }
    
    private static void readTermEntry(List<GlossaryEntryStore> result, final XMLStreamReader reader, IGlossary origin) throws Exception {
        String sLang = Core.getProject().getProjectProperties().getSourceLanguage().getLanguageCode();
        String tLang = Core.getProject().getProjectProperties().getTargetLanguage().getLanguageCode();

        StringBuilder note = new StringBuilder();
        List<TMXProp> enProp = new ArrayList<>(), srcProp = new ArrayList<>(), traProp = new ArrayList<>();        
        List<String> sTerms = new ArrayList<String>();
        List<String> tTerms = new ArrayList<String>();

        while (reader.hasNext()) {
            int next = reader.next();
            if ((next == XMLStreamConstants.END_ELEMENT) && (reader.getLocalName().equals("termEntry") || reader.getLocalName().equals("conceptEntry"))) {
                for (String s : sTerms) {
                    boolean addedForLang = false;
                    for (String t : tTerms) {
                        result.add(new GlossaryEntryWithProperties(s, t, note.toString(), enProp, srcProp, traProp, origin));
                        addedForLang = true;
                    }
                    if (!addedForLang) { // An entry is created just to get the definition
                        result.add(new GlossaryEntryWithProperties(s, "", note.toString(), enProp, srcProp, traProp, origin));
                    }
                }
                return;
            }
            
            if (next == XMLStreamConstants.START_ELEMENT)
                if (reader.getLocalName().equals("note")) appendLine(note, readContent(reader, "note"));
                else if (reader.getLocalName().equals("descripGrp")) appendProperty(enProp, "", reader);
                else if (reader.getLocalName().equals("descrip")) appendProperty(enProp, "", reader);
                else if (reader.getLocalName().equals("langSet") || reader.getLocalName().equals("langSec")) {
                    String lang = reader.getAttributeValue(null, "lang");
                    lang = new Language(lang).getLanguageCode();
                TIGLOOP:
                    while (reader.hasNext()) {
                        next = reader.next();
                        if ((next == XMLStreamConstants.END_ELEMENT) && ((reader.getLocalName().equals("langSet") || reader.getLocalName().equals("langSec")))) break TIGLOOP;
                        
                        if (next == XMLStreamConstants.START_ELEMENT)
                            if (reader.getLocalName().equals("tig") || reader.getLocalName().equals("ntig") || reader.getLocalName().equals("termSec")) {
                                if (sLang.equalsIgnoreCase(lang)) sTerms.add(readContent(reader, "term"));
                                else if (tLang.equalsIgnoreCase(lang)) tTerms.add(readContent(reader, "term"));
                            }
                            else if (reader.getLocalName().equals("termNote")) {
                                if (sLang.equalsIgnoreCase(lang)) appendProperty(srcProp, "", reader);
                                else if (tLang.equalsIgnoreCase(lang)) appendProperty(traProp, "", reader);
                            }
                            else if (reader.getLocalName().equals("descripGrp")) {
                                if (sLang.equalsIgnoreCase(lang)) appendProperty(srcProp, "", reader);
                                else if (tLang.equalsIgnoreCase(lang)) appendProperty(traProp, "", reader);
                            }
                            else if (reader.getLocalName().equals("descrip")) {
                                if (sLang.equalsIgnoreCase(lang)) appendProperty(srcProp, "", reader);
                                else if (tLang.equalsIgnoreCase(lang)) appendProperty(traProp, "", reader);
                            }
                            else if (reader.getLocalName().equals("admin")) {
                                if (sLang.equalsIgnoreCase(lang)) appendProperty(srcProp, "", reader);
                                else if (tLang.equalsIgnoreCase(lang)) appendProperty(traProp, "", reader);
                            }
                    }
                }
        }
    }

    protected static void appendProperty(List<TMXProp> list, final String prefix, final XMLStreamReader reader) throws XMLStreamException {
        String line = null;
        if (reader.getLocalName().equals("descrip"))
            if ("context".equals(reader.getAttributeValue(null, "type"))) {
                if (Preferences.isPreferenceDefault(Preferences.GLOSSARY_TBX_DISPLAY_CONTEXT, true)) {
                    list.add(new TMXProp(prefix + "context", readContent(reader, "descrip")));
                }
            } else {
                list.add(new TMXProp(prefix + reader.getAttributeValue(null, "type"), readContent(reader, "descrip")));
            }        
        if (reader.getLocalName().equals("descripGrp")) {
            String prefix2 = ""; if (reader.getAttributeValue(null, "id") != null) prefix2 = reader.getAttributeValue(null, "id") + ".";
            while (reader.hasNext()) {
                int next = reader.next();
                if (next == XMLStreamConstants.END_ELEMENT)
                    if (reader.getLocalName().equals("descripGrp")) break;
                if (next == XMLStreamConstants.START_ELEMENT)
                    if (reader.getLocalName().equals("descrip")) appendProperty(list, prefix2 + prefix, reader);
            }
        }
        if (reader.getLocalName().equals("termNote")) list.add(new TMXProp(prefix + reader.getAttributeValue(null, "type"), readContent(reader, "termNote")));
        if (reader.getLocalName().equals("admin")) list.add(new TMXProp(prefix + reader.getAttributeValue(null, "type"), readContent(reader, "admin")));
    }

    protected static void appendLine(final StringBuilder str, String line) {
        if (line.isEmpty()) { // No need to append empty lines
            return;
        }
        if (str.length() > 0) {
            str.append('\n');
        }
        str.append(line);
    }

    protected static String readContent(final XMLStreamReader reader, String mark) throws XMLStreamException {
        StringBuilder res = new StringBuilder(); boolean in = reader.getLocalName().equals(mark); int next;
        while (reader.hasNext()) {
            next = reader.next();
            if (next == XMLStreamConstants.END_ELEMENT)
                if (reader.getLocalName().equals(mark)) break;
                else if (reader.getLocalName().equals("hi")) if (in) res.append("* ");
            if (next == XMLStreamConstants.START_ELEMENT)
                if (reader.getLocalName().equals(mark)) in = true;
                else if (reader.getLocalName().equals("hi")) if (in) res.append(" *");
            if (next == XMLStreamConstants.CHARACTERS)
                if (in) res.append(reader.getText());
        }
        return res.toString();
    }
}
