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

 Copyright (C) 2017-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.filters4.xml.xliff;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLEventWriter;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.EndElement;
import javax.xml.stream.events.StartElement;
import javax.xml.stream.events.Characters;
import javax.xml.stream.events.XMLEvent;

import org.omegat.core.Core;
import org.omegat.core.data.EntryKey;
import org.omegat.core.data.IProject;
import org.omegat.core.data.TMXEntry;
import org.omegat.core.data.PrepareTMXEntry;
import org.omegat.core.data.SourceTextEntry.SourceTranslationInfo;
import org.omegat.core.data.SourceTextEntry.SourceTranslationEntry;
import org.omegat.filters2.Instance;
import org.omegat.util.Preferences;

/**
 * Filter for support SDL-Xliff, based on XLIFF-1
 *
 * @creator Thomas Cordonnier
 */
public class SdlXliff extends Xliff1Filter {
    
    // ---------------------------- IFilter API ----------------------------
    
    @Override
    public String getFileFormatName() {
        return "SDL-Xliff";
    }

    @Override
    public Instance[] getDefaultInstances() {
        return new Instance[]  { new Instance("*.sdlxliff") };
    }
    
    @Override
    public boolean isFileSupported(java.io.File inFile, Map<String, String> config, org.omegat.filters2.FilterContext context) {
        try {
            StartElement el = findEvent(inFile, java.util.regex.Pattern.compile(".*/.*:xliff"));
            if (el == null) return false;
			namespace = el.getName().getNamespaceURI();
            if (el.getAttributeByName(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "version")) != null) return true;                
            return super.isFileSupported(inFile, config, context);
        } catch (Exception npe) {
            return false; // version attribute is mandatory
        }
    }
    
    // ----------------------------- specific part ----------------------
    
    private String currentMid = null;
    private Map<String, StringBuffer> sdlComments = new TreeMap<>();
    private StringBuffer commentBuf = null;
    private Map<UUID, String> omegatNotes = new TreeMap<>();
    private Map<String, UUID> defaultNoteLocations = new TreeMap<>();
    private Map<EntryKey, UUID> altNoteLocations = new TreeMap<>();
    private Map<String, List<XMLEvent>> tagDefs = new TreeMap<>();
    private Map<String, StringBuffer> segProps = new TreeMap<>();
	private String currentProp = null;
	private java.util.Set<String> midSet = new java.util.HashSet<String>();
	private boolean has_seg_defs = false;
    
    
    @Override
    protected boolean processStartElement (StartElement startElement, XMLEventWriter evWriter) throws  XMLStreamException {
        if (startElement.getName().getLocalPart().equals("cmt-def")) {
            sdlComments.put (startElement.getAttributeByName(new QName("id")).getValue(), commentBuf = new StringBuffer());
            return true;
        }
        if (startElement.getName().getLocalPart().equals("mrk"))
			if (startElement.getAttributeByName(new QName("mtype")).getValue().equals("seg"))
				midSet.add(currentMid = startElement.getAttributeByName(new QName("mid")).getValue());
            else if (startElement.getAttributeByName(new QName("mtype")).getValue().equals("x-sdl-comment")) {
                String id = startElement.getAttributeByName(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "cid")).getValue();
                this.addNoteFromSource (currentMid, sdlComments.get(id).toString());
            }
        if (startElement.getName().getLocalPart().equals("tag")) 
            tagDefs.put(startElement.getAttributeByName(new QName("id")).getValue(), currentBuffer = new java.util.LinkedList<XMLEvent>());
        if (startElement.getName().equals(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "seg"))) {
			segProps.clear(); currentProp = null; has_seg_defs = true; // start a new set of properties
            Attribute mid = startElement.getAttributeByName(new QName("id"));
		    if (mid != null) currentMid = mid.getValue();
			if (evWriter != null) {
                List<Attribute> l = new java.util.LinkedList<Attribute>(); 
                for (java.util.Iterator I = startElement.getAttributes(); I.hasNext(); ) {
                    Attribute A = (Attribute) I.next(); 
                    if (! A.getName().getLocalPart().equals("conf")) l.add (A); 
					else 
						if (mid == null) l.add(eFactory.createAttribute("conf", A.getValue()));
						else {
						    String value = "Translated";
						    if ((Core.getMainWindow() != null) && (Core.getMainWindow().getMainMenu().getRevisionMenuItem().isSelected()))  {
								PrepareTMXEntry entry = this.currentEntryTranslation(mid.getValue());
								if (entry.getPropValue("revisor") != null) value = "ApprovedTranslation";
						    }
						    l.add(eFactory.createAttribute("conf", value));
						}
                }
                if (mid == null) {
                    Attribute A = startElement.getAttributeByName(new QName("conf"));
                    if (A != null) l.add(A);
                }
                else if (this.currentEntryTranslation(mid.getValue()) != null) {
                    Attribute na = eFactory.createAttribute("conf", "Translated");
                    l.add (na);
                }
                evWriter.add (eFactory.createStartElement(startElement.getName(), l.iterator(), startElement.getNamespaces()));
                return false; // we already added current element
            }
		}
        if (startElement.getName().equals(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "value"))) 
			segProps.put (currentProp = startElement.getAttributeByName(new QName("key")).getValue(), commentBuf = new StringBuffer());
		if (startElement.getName().getLocalPart().equals("trans-unit")) has_seg_defs = false;
        return super.processStartElement(startElement, evWriter);
    } 

    @Override
    protected boolean processEndElement (EndElement endElement, XMLEventWriter evWriter) throws  XMLStreamException {
        if (endElement.getName().equals(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "value"))) { 
			commentBuf = null; currentProp = null; 
		}
        if (endElement.getName().getLocalPart().equals("seg")) {
			PrepareTMXEntry entry = currentEntryTranslation(currentMid);
			midSet.remove(currentMid); currentMid = null;
			if ((evWriter != null) && (entry != null)) {
				if (segProps.get("last_modified_by") == null) { // null, not empty = no such value in the file
					evWriter.add (eFactory.createStartElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value")); 
					evWriter.add (eFactory.createAttribute(new QName("key"), "last_modified_by"));
					evWriter.add (eFactory.createCharacters(entry.changer != null ? entry.changer : Preferences.getPreferenceDefault(Preferences.TEAM_AUTHOR, System.getProperty("user.name"))));
					evWriter.add (eFactory.createEndElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value"));
				}
				if (segProps.get("modified_on") == null) {// null, not empty = no such value in the file
					evWriter.add (eFactory.createStartElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value")); 
					evWriter.add (eFactory.createAttribute(new QName("key"), "modified_on"));
					evWriter.add (eFactory.createCharacters(TRADOS_DATE_FORMAT.format(entry.changeDate != 0 ? new java.util.Date(entry.changeDate) : new java.util.Date())));
					evWriter.add (eFactory.createEndElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value"));
				}
			}
		}
        if (endElement.getName().getLocalPart().equals("trans-unit")) {
			if (evWriter != null) {
				if ((midSet.size() > 0) && (! has_seg_defs)) evWriter.add (eFactory.createStartElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "seg-defs"));
				for (String mid0: midSet) {	// those which were not generated by previous lines
					evWriter.add (eFactory.createStartElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "seg"));
					evWriter.add (eFactory.createAttribute("id", mid0));
					PrepareTMXEntry entry = currentEntryTranslation(currentMid);
					String value = "Translated";
					if ((Core.getMainWindow() != null) && (Core.getMainWindow().getMainMenu().getRevisionMenuItem().isSelected()))  {
						if (entry.getPropValue("revisor") != null) value = "ApprovedTranslation";
					}
					evWriter.add (eFactory.createAttribute("conf", value));
					if (entry != null) {
						evWriter.add (eFactory.createStartElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value")); 
						evWriter.add (eFactory.createAttribute(new QName("key"), "last_modified_by"));
						evWriter.add (eFactory.createCharacters(entry.changer != null ? entry.changer : Preferences.getPreferenceDefault(Preferences.TEAM_AUTHOR, System.getProperty("user.name"))));
						evWriter.add (eFactory.createEndElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value"));

						evWriter.add (eFactory.createStartElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value")); 
						evWriter.add (eFactory.createAttribute(new QName("key"), "modified_on"));
						evWriter.add (eFactory.createCharacters(TRADOS_DATE_FORMAT.format(entry.changeDate != 0 ? new java.util.Date(entry.changeDate) : new java.util.Date())));
						evWriter.add (eFactory.createEndElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "value"));
					}
					evWriter.add (eFactory.createEndElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "seg"));
				}
				if ((midSet.size() > 0) && (! has_seg_defs)) evWriter.add (eFactory.createEndElement("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "seg-defs"));
			}
			midSet.clear();
		}
		
        if (endElement.getName().getLocalPart().equals("tag")) currentBuffer = null;
        if (endElement.getName().getLocalPart().equals("cmt-def")) commentBuf = null;
        if (endElement.getName().getLocalPart().equals("cmt-defs")) {
            if (evWriter != null) {
                IProject proj = Core.getProject();
                proj.iterateByDefaultTranslations((String source, TMXEntry trans) -> {
                    if (! trans.hasNote()) return;
                    
                    UUID id = UUID.randomUUID(); omegatNotes.put(id, trans.note); defaultNoteLocations.put(source, id); 
                    createSdlNote (id, trans, evWriter);
                });
                proj.iterateByMultipleTranslations((EntryKey key, TMXEntry trans) -> {
                    if (! trans.hasNote()) return;
                    
                    UUID id = UUID.randomUUID(); omegatNotes.put(id, trans.note); altNoteLocations.put (key, id);
                    createSdlNote (id, trans, evWriter);
                });
            }
        }
        if (endElement.getName().getLocalPart().equals("tag-defs"))
            if (evWriter != null)
                for (String type: new String[] { "italic", "bold", "underline", "superscript","subscript"}) 
					if (tagDefs.get("omegat-" + type) == null) { // does not yet exist in the source file
						evWriter.add (eFactory.createStartElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "tag"), null, null)); 
						evWriter.add (eFactory.createAttribute(new QName("id"), "omegat-" + type));
						evWriter.add (eFactory.createStartElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "bpt"), null, null)); 
						evWriter.add (eFactory.createAttribute(new QName("name"), "cf"));
						evWriter.add (eFactory.createAttribute(new QName("word-end"), "false"));
						evWriter.add (eFactory.createAttribute(new QName("can-hide"), "true"));
						evWriter.add (eFactory.createCharacters("<cf " + type + "=\"true\">"));
						evWriter.add (eFactory.createEndElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "bpt"), null)); 
						evWriter.add (eFactory.createStartElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "ept"), null, null)); 
						evWriter.add (eFactory.createAttribute(new QName("name"), "cf"));
						evWriter.add (eFactory.createAttribute(new QName("word-end"), "false"));
						evWriter.add (eFactory.createAttribute(new QName("can-hide"), "true"));
						evWriter.add (eFactory.createCharacters("</cf>"));
						evWriter.add (eFactory.createEndElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "ept"), null));                                     
						evWriter.add (eFactory.createEndElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "tag"), null)); 
					}
        
        return super.processEndElement (endElement, evWriter);
    }
    
    // Do not generate tag for comment inside source
    protected boolean isIngoredTag (StartElement stEl) {
        return (stEl.getName().equals(new QName("urn:oasis:names:tc:xliff:document:1.2", "mrk"))
            && stEl.getAttributeByName(new QName("mtype")).getValue().equals("x-sdl-comment"))
            || super.isIngoredTag(stEl);
    }    
    
    @Override
    protected char findPrefix (StartElement stEl) {
        if (stEl.getName().getLocalPart().equals("g")) 
            try {
                String tagId = stEl.getAttributeByName(new QName("id")).getValue();
                List<XMLEvent> contents = tagDefs.get (tagId);
                for (XMLEvent ev: contents)
                    if (ev.isCharacters()) {
                        String txt = ev.asCharacters().getData();
                        if (txt.contains("italic") && ! txt.contains("bold")) return 'i';
                        if (! txt.contains("italic") && (txt.contains("bold") || txt.contains("strong"))) return 'b';
                        if (txt.contains("size")) return 's';
                        if (txt.contains("color")) return 'c';
                        if (txt.contains("footnote")) return 'n';
                        if (txt.contains("cf")) return 'f'; // format
                    } else if (ev.isStartElement()) {
                        String name = ev.asStartElement().getName().getLocalPart();
                        if (name.equals("bpt") || name.equals("ept")) {
                            name = ev.asStartElement().getAttributeByName(new QName("name")).getValue();
                            if (name.equals("italic") || name.equals("em")) return 'i';
                            if (name.equals("bold") || name.equals("strong")) return 'b';
                        }
					}
            }
            catch (Exception e) {}
        // default
        return super.findPrefix (stEl);
    }    
    
	@Override
	public boolean isUsingStandardNote() { return false; }
	
    private static void createSdlNote(UUID id, TMXEntry trans, XMLEventWriter evWriter) {
        List<Attribute> attr = java.util.Collections.singletonList(eFactory.createAttribute(new QName("id"), id.toString()));
        try {
            evWriter.add (eFactory.createStartElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "cmt-def"), attr.iterator(), null)); 
            evWriter.add (eFactory.createStartElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "Comments"), null, null)); 
            evWriter.add (eFactory.createStartElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "Comment"), null, null));
            evWriter.add (eFactory.createCharacters (trans.note));
            evWriter.add (eFactory.createEndElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "Comment"), null)); 
            evWriter.add (eFactory.createEndElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "Comments"), null)); 
            evWriter.add (eFactory.createEndElement(new QName("http://sdl.com/FileTypes/SdlXliff/1.0", "cmt-def"), null)); 
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected boolean processCharacters (Characters event, XMLEventWriter evWriter) throws XMLStreamException { 
        if (commentBuf != null) { 
			commentBuf.append(event.toString());
			if (currentMid != null) {
				PrepareTMXEntry entry = currentEntryTranslation(currentMid);
				if ((evWriter != null) && (entry != null)) {
					if ("last_modified_by".equals(currentProp)) {
						evWriter.add (eFactory.createCharacters(entry.changer != null ? entry.changer : Preferences.getPreferenceDefault(Preferences.TEAM_AUTHOR, System.getProperty("user.name"))));					
						return false;
					}
					if ("modified_on".equals(currentProp)) {
						evWriter.add (eFactory.createCharacters(TRADOS_DATE_FORMAT.format(entry.changeDate != 0 ? new java.util.Date(entry.changeDate) : new java.util.Date())));
						return false;
					}
				}
			}
		}			
        return super.processCharacters(event, evWriter);
    }    

    // This method is called only during translation generation: so we can use it to add notes!
    @Override
    protected List<XMLEvent> restoreTags (String unitId, String path, String src, String tra) {
        List<XMLEvent> res = super.restoreTags(unitId, path, src, tra);
        EntryKey key = new EntryKey("", src, unitId, null,null,path); UUID addNote = null;
        if (altNoteLocations.get(key) != null) addNote = altNoteLocations.get(key);        
        else if (defaultNoteLocations.get(src) != null) addNote = defaultNoteLocations.get(src);
        if ((addNote != null) && (omegatNotes.get(addNote) != null)) {
            List<Attribute> attr = new java.util.LinkedList<Attribute>();
            attr.add (eFactory.createAttribute("sdl", "http://sdl.com/FileTypes/SdlXliff/1.0", "cid", addNote.toString()));
            attr.add (eFactory.createAttribute(new QName("mtype"), "x-sdl-comment"));
            res.add (0, eFactory.createStartElement(new QName("urn:oasis:names:tc:xliff:document:1.2", "mrk"), attr.iterator(), null));            
            res.add (eFactory.createEndElement(new QName("urn:oasis:names:tc:xliff:document:1.2", "mrk"), null));             
        }
        return res;
    }
    
    /** Creates a tag for given pseudo-tag. As usual, SDL does not use the standard way **/
    @Override
    public StartElement createAddTagElement (char type, int idx) {
        List<Attribute> attrs = java.util.Collections.emptyList();
        switch (type) {
            case '\u2460': attrs = java.util.Collections.singletonList(eFactory.createAttribute(new QName("id"), "omegat-italic")); break;
            case '\u2461': attrs = java.util.Collections.singletonList(eFactory.createAttribute(new QName("id"), "omegat-bold")); break;
            case '\u2462': attrs = java.util.Collections.singletonList(eFactory.createAttribute(new QName("id"), "omegat-underline")); break;
            case '\u2463': attrs = java.util.Collections.singletonList(eFactory.createAttribute(new QName("id"), "omegat-superscript")); break;
            case '\u2464': attrs = java.util.Collections.singletonList(eFactory.createAttribute(new QName("id"), "omegat-subscript")); break;
        }
        return eFactory.createStartElement(new QName("urn:oasis:names:tc:xliff:document:1.2", "g"), attrs.iterator(), null);
    }    

    /** Remove entries with only tags **/
    @Override
    protected boolean isToIgnore (String src, String tra) {
        if (tra == null) return false;
        while (src.startsWith("<")) src = src.substring(Math.max(1, src.indexOf(">") + 1));
        while (tra.startsWith("<")) tra = tra.substring(Math.max(1, tra.indexOf(">") + 1));
        return (src.length() == 0) && (tra.length() == 0);
    }
    
	public static final java.text.SimpleDateFormat TRADOS_DATE_FORMAT = new java.text.SimpleDateFormat("M/d/y H:m:s");
	
	@Override
	protected SourceTranslationInfo buildTranlsationInfo (String tra, String note) {
		long creationDate = 0L, changeDate = 0L;
		StringBuffer buf = segProps.get("created_by");
		String creator = buf == null ? null : buf.toString();
		try { creationDate = TRADOS_DATE_FORMAT.parse (segProps.get("created_on").toString()).getTime(); } catch (Exception e) {}
		buf = segProps.get("last_modified_by");
		String changer = buf == null ? null : buf.toString();
		try { changeDate = TRADOS_DATE_FORMAT.parse (segProps.get("modified_on").toString()).getTime(); } catch (Exception e) {}
		if (creator == null) creator = changer;
		if (changer != null)
			return new SourceTranslationEntry (tra, note, false, creator, creationDate, changer, changeDate); // XLIFF does not implement fuzzy
		else
			return super.buildTranlsationInfo(tra, note);
	}
	
	@Override protected boolean isStandardTranslationState() { return false; } // because SDLXLIFF does not have attributes in target	
}
