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

 Copyright (C) 2017 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.LinkedList;
import java.util.Collections;

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.XMLEvent;


/**
 * Filter for support Xliff 1 files as bilingual (unlike filters3/xml/xliff)
 *
 * @author Thomas Cordonnier
 */
public class Xliff2Filter extends AbstractXliffFilter {
    
    // ---------------------------- IFilter API ----------------------------
    
    @Override
    public String getFileFormatName() {
        return "Xliff 2.x";
    }
    
	protected final String versionPrefix() { return "2."; } // can be 2.0, maybe more in the future

    // ----------------------------- AbstractXmlFilter part ----------------------
    
    /** Current translation unit **/
    private String segId = null;
    private boolean flushedSegment = false;
    
    @Override
    protected boolean processStartElement (StartElement startElement, XMLEventWriter evWriter) throws  XMLStreamException {
        switch (startElement.getName().getLocalPart()) {
            case "xliff": if (namespace == null) namespace = startElement.getName().getNamespaceURI(); break;
            case "file": case "group": case "unit": 
                path += "/" + startElement.getAttributeByName(new QName("id")).getValue(); // Note: in spec, id is REQUIRED
                updateIgnoreScope (startElement);
                break;
            case "segment": 
                segId = startElement.getAttributeByName(new QName("id")).getValue(); flushedSegment = false;
                break;
            case "source": currentBuffer = source; source.clear(); break;
            case "target": currentBuffer = target = new LinkedList<XMLEvent>(); inTarget = true; break;
            case "notes": note.clear(); break;
            case "note": 
                if (startElement.getAttributeByName(new QName("id")) != null)
                    note.add(eFactory.createCharacters("\n\n[" + startElement.getAttributeByName(new QName("id")).getValue() + "] "));
                else if (! note.isEmpty())
                    note.add(eFactory.createCharacters("\n\n"));
                currentBuffer = note; break;
            default:
                if (currentBuffer != null) currentBuffer.add (startElement);
                // <target> must be before any oter-namespace markup
                else if (((ignoreScope == null || ignoreScope.startsWith("!")) && (segId != null)) && (!startElement.getName().getNamespaceURI().equals(namespace))) flushTranslations (evWriter); 
        }
        return !inTarget;
    }

    @Override
    protected boolean processEndElement (EndElement endElement, XMLEventWriter evWriter) throws  XMLStreamException {
        switch (endElement.getName().getLocalPart()) {
            case "source": case "note": currentBuffer = null; break; 
            case "target": 
                currentBuffer = null; 
                if (ignoreScope == null || ignoreScope.startsWith("!")) flushTranslations (evWriter); // we are in the correct place
                inTarget = false; target = null; return false;
            case "segment": 
                if (ignoreScope == null || ignoreScope.startsWith("!")) flushTranslations (evWriter); // if there was no <target> at all 
                if (ignoreScope == null || ignoreScope.startsWith("!")) registerCurrentTransUnit(segId, source, target, ".*");
                segId = null; cleanBuffers(); break;
            case "unit": case "group": case "file":
                segId = ""; path = path.substring(0, path.lastIndexOf('/')); cleanBuffers(); 
                if (endElement.getName().getLocalPart().equals(ignoreScope)) ignoreScope = null;
                else if (ignoreScope != null && ignoreScope.startsWith("!" + endElement.getName().getLocalPart()))
                    ignoreScope = ignoreScope.substring (endElement.getName().getLocalPart().length() + 2);
                break;
            default:
                if (currentBuffer != null) currentBuffer.add (endElement);
        }
        return !inTarget;
    }
    
    @Override protected String[] getPairIdNames (boolean start) { 
        if (start) return new String[] { "id" }; 
        else return new String[] { "startRef", "id" };
    }
	
    /** Converts List<XMLEvent> to OmegaT format, with <x0/>, <g0>...</g0>, etc. Also build maps to be reused later **/
    protected String buildTags (List<XMLEvent> srcList, boolean reuse) {
        if (!reuse) { tagsMap.clear(); for (Character c: tagsCount.keySet()) tagsCount.put(c,0); }
        StringBuffer res = new StringBuffer(), saveBuf = null;
        for (XMLEvent ev: srcList)
            if (ev.isCharacters()) res.append(ev.asCharacters().getData());
            else if (ev.isStartElement()) {
                StartElement stEl = ev.asStartElement();
                String name = stEl.getName().getLocalPart(); char prefix = findPrefix(stEl);
                Integer count = tagsCount.get(prefix); if (count == null) count = 0; tagsCount.put(prefix, count + 1);                
                switch (name) {
                    case "mrk": break;
                    case "ph": case "cp": res.append(startPair(reuse, true, stEl, prefix, count, toPair(stEl))); break; // empty element
                    case "sc": case "sm": res.append(startPair(reuse, false, stEl, prefix, count, toPair(stEl))); break; // empty element, paired, start
                    case "ec": case "em": res.append(endPair(reuse, stEl, prefix, count, toPair(stEl))); break;// empty element, paired, end
                    case "pc": default: startStackElement(reuse, stEl, prefix, count, res); break;
                }
            }
            else if (ev.isEndElement()) {
                EndElement endEl = ev.asEndElement();
                switch (endEl.getName().getLocalPart()) {
                    case "mrk": break;
                    case "ph": case "cp": case "sc": case "ec": break; // Should be empty!!!
                    case "pc": default: { String pop = tagStack.pop(); tagsMap.put("/" + pop, Collections.singletonList(ev)); res.append("</").append(pop).append(">"); } break;
                }
            }
        return res.toString();
    }
    
    protected char findPrefix (StartElement stEl) {
        String name = stEl.getName().getLocalPart();
        if (name.equals("pc")) return 'g';
        if (name.equals("sc") || name.equals("ec")) return 't';
        if (name.equals("sm") || name.equals("em")) return 'a';
        if (! stEl.getName().getNamespaceURI().equals(this.namespace)) return 'o'; // other (normally not allowed by specification)
        Attribute type = stEl.getAttributeByName(new QName("type"));
        if (type != null && type.getValue().equals("fmt")) {
            type = stEl.getAttributeByName(new QName("subType"));
            if (type != null && type.getValue().startsWith("xlf:")) return type.getValue().charAt(4);
        }
        // default
        return name.charAt(0); 
    }
      
    /** Replace <target> by OmegaT's translation, if found **/
    private void flushTranslations (XMLEventWriter evWriter) throws XMLStreamException {
        if (evWriter == null) return; if (flushedSegment) return;
        if (target == null) return;

        final QName __TARGET = new QName(namespace, "target");
        evWriter.add (eFactory.createStartElement(__TARGET, null, null)); 
        String src = buildTags (source, false);
        String tra = entryTranslateCallback.getTranslation(segId, src, path);
        if (tra != null)
            for (XMLEvent ev: restoreTags(tra)) evWriter.add (ev);
        else
            for (XMLEvent ev: target) evWriter.add (ev);
        evWriter.add (eFactory.createEndElement(__TARGET, null)); flushedSegment = true;
    }

    /** Creates a tag for given pseudo-tag **/
    protected StartElement createAddTagElement (char type, int idx) {
		List<Attribute> attrs = new LinkedList<Attribute>();
        attrs.add (eFactory.createAttribute(new QName("id"), "pseudo-" + idx));
        attrs.add (eFactory.createAttribute(new QName("type"), "fmt"));
        switch (type) {
            case '\u2460': attrs.add (eFactory.createAttribute(new QName("subType"), "xlf:i")); break;
            case '\u2461': attrs.add (eFactory.createAttribute(new QName("subType"), "xlf:b")); break;
            case '\u2462': attrs.add (eFactory.createAttribute(new QName("subType"), "xlf:u")); break;                                
        }
		return eFactory.createStartElement(new QName(namespace, "pc"), attrs.iterator(), null);
	}    
    
}
