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

 Copyright (C) 2013 Thomas Cordonnier
               2015 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.core.matching.external;

import java.util.List;
import java.util.ArrayList;
import java.util.Properties;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.Writer;
import java.io.OutputStreamWriter;

import org.omegat.core.data.SourceTextEntry;
import org.omegat.core.data.PrepareTMXEntry;
import org.omegat.core.data.ProjectProperties;
import org.omegat.core.data.ProtectedPart;
import org.omegat.core.search.TextExpression;
import org.omegat.core.Core;
import org.omegat.util.OConsts;
import org.omegat.util.Language;

/**
 * Reads/writes a Tab-separated values text file as a memory. This format is used by other CAT tools.
 * 
 * @author Thomas Cordonnier
 */
public class TextFileMemory implements IBrowsableMemory, IWritableExternalMemory {
    private File file;
    private List<PrepareTMXEntry> entries = new ArrayList<PrepareTMXEntry>();
    private boolean write;
    
    private static java.text.DateFormat WF_DATE_FORMAT = new java.text.SimpleDateFormat("yyyyMMdd~hhmmss");
    
    /** This constructor is used when the file is inside the project. If the path contains "write" it is writeable. **/
    public TextFileMemory(final ProjectProperties props, final File file, final boolean extTmxLevel2,
            final boolean useSlash) throws java.io.IOException {
        this.file = file;
        this.write = file.getPath().contains("write");
        
        try (BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(file), OConsts.UTF8))) {
            // BOM (byte order mark) bugfix
            in.mark(1);
            int ch = in.read();
            if (ch != 0xFEFF)
                in.reset();

            for (String s = in.readLine(); s != null; s = in.readLine()) {
                // skip lines that start with '%'
                if (s.startsWith("%")) continue;

                // divide lines on tabs
                String tokens[] = s.split("\t");
                // check token list to see if it has a valid string
                if (tokens.length < 3 || tokens[0].length() == 0) continue;

                PrepareTMXEntry entry = new PrepareTMXEntry();
                entry.source = tokens[4].replace("&'9;","\t");
                entry.translation = tokens[6].replace("&'9;","\t");
                entry.creator = tokens[1];
                try { entry.creationDate = WF_DATE_FORMAT.parse(tokens[0]).getTime(); } catch (Exception e) {}				
                entry.changeDate = Long.parseLong (tokens[2]); // version number
                
                entries.add(entry);
            }
        }
    }
    
    /** 
     * This constructor is to be used with a .properties file inside the project. The file must <i>not</i> be in the project!
     * Sample config file:
     * <code>
     * class=org.omegat.core.matching.external.TextFileMemory
     * file=/where/is/your/file.txt
     * write=true
     * </code>
     **/
    public TextFileMemory(Properties prop) throws IOException {
        this(Core.getProject().getProjectProperties(), new File (prop.getProperty("file")), false,false);
        if (prop.getProperty("write") != null) this.write = prop.getProperty("write").equalsIgnoreCase("true");
    }

    public String getMemoryName() {
        return file.getPath();
    }

    public String getProviderName() {
        return file.getPath().substring(file.getPath().lastIndexOf('.') + 1) + " file";
    }
    
    /**
     * Returns matching TMX entries
     */
    public List<PrepareTMXEntry> findMatchingTranslations (Language sLang, Language tLang, String text, int minScore, int maxCount) {
        return entries;
    }
    
    public IEntryCursor.TMXEntriesCursor browseAllEntries() {
        return new IEntryCursor.TMXEntriesCursor(entries);
    }
    
    public boolean isWriteMode() {
        return write;
    }
    
    public boolean mustWrite(PrepareTMXEntry entryContents, SourceTextEntry entrySource) {
        return true;
    }
    
    public void registerTranslation(Language sLang, Language tLang, PrepareTMXEntry entryContents, SourceTextEntry entrySource) throws Exception {
        String encoding = OConsts.UTF8;
        if (!file.exists()) {
            File parentFile = file.getParentFile();
            if (parentFile != null) {
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                }
            }
            file.createNewFile();
        } else if (isUTF16LE(file)) {
            encoding = OConsts.UTF16LE;
        }
        try (Writer wr = new OutputStreamWriter(new FileOutputStream(file, true), encoding)) {
            wr.append(WF_DATE_FORMAT.format(entryContents.creationDate)).append('\t');
            wr.append(entryContents.creator).append('\t');
            long version = 0; for (PrepareTMXEntry pe: entries) if (pe.source.equals(entryContents.source)) if (version < pe.changeDate) version = pe.changeDate + 1;
            wr.append(Long.toString(version)).append('\t'); // TU version
            wr.append(sLang.toString()).append('\t').append(entryContents.source).append('\t');
            wr.append(tLang.toString()).append('\t').append(entryContents.translation);
            wr.append(System.getProperty("line.separator"));
        }		
    }
    
    private static boolean isUTF16LE(final File file) throws IOException {
        boolean ret = false;
        try (FileInputStream stream = new FileInputStream(file)) {
            byte[] bytes = new byte[2];

            // BOM (byte order mark) detection
            if (stream.read(bytes, 0, 2) == 2 && bytes[0] == (byte) 0xFF && bytes[1] == (byte) 0xFE) {
                ret = true;
            }
        }
        return ret;
    }
    
    public Iterable<PrepareTMXEntry> search(int numberOfResults, TextExpression searchSource, TextExpression searchTarget, TextExpression searchNotes, boolean andSearch,
        TextExpression author, // NOTE: does not take DGT-specific "translator" fields, because they would be meaningless for external providers
        long dateAfter, long dateBefore) {
        return entries;
    }	
}
