////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) 2013 Saxonica Limited.
// This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
// If a copy of the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/.
// This Source Code Form is "Incompatible With Secondary Licenses", as defined by the Mozilla Public License, v. 2.0.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

package net.sf.saxon.evpull;

import net.sf.saxon.Configuration;
import net.sf.saxon.om.NamespaceBinding;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.util.FastStringBuffer;
import net.sf.saxon.tree.util.Orphan;
import net.sf.saxon.type.Type;
import net.sf.saxon.value.AtomicValue;

/**
 * The ComplexContentProcessor is an EventIterator that deals with the events occurring between
 * a startElement and endElement (or startDocument and endDocument) according to the XSLT/XQuery
 * rules for constructing complex content. This includes:
 *
 * <ul>
 * <li>Converting atomic values to text nodes (inserting space as a separator between adjacent nodes)</li>
 * <li>Replacing nested document nodes by their children</li>
 * <li>Merging adjacent text nodes and dropping zero-length text nodes</li>
 * <li>Detecting mispositioned or duplicated attribute and namespace nodes</li>
 *
 * </ul>
 *
 * <p>Note that if the content includes nodes such as element nodes, these will not be decomposed into
 * a sequence of tree events, they will simply be returned as nodes.</p>
 */
public class ComplexContentProcessor implements EventIterator {

    private Configuration config;
    private EventIterator base;
    private PullEvent[] startEventStack;  // contains either startElement or startDocument events
    private int depth;
    private NodeInfo pendingTextNode;
    private boolean pendingTextNodeIsMutable;
    private boolean prevAtomic = false;
    private PullEvent pendingOutput = null;

    /**
     * Create the ComplexContentProcessor
     * @param config the Saxon Configuration
     * @param base the EventIterator that delivers the content of the element or document node
     */

    public ComplexContentProcessor(Configuration config, EventIterator base) {
        this.config = config;
        this.base = EventStackIterator.flatten(base);
        startEventStack = new PullEvent[20];
        depth = 0;
    }

    /**
     * Get the next event in the sequence. This will never be an EventIterator.
     *
     * @return the next event, or null when the sequence is exhausted
     * @throws net.sf.saxon.trans.XPathException
     *          if a dynamic evaluation error occurs
     */

    public PullEvent next() throws XPathException {
        if (pendingOutput != null) {
            PullEvent next = pendingOutput;
            pendingOutput = null;
            return next;
        } else {
            return advance();
        }
    }

    /*@Nullable*/ private PullEvent advance() throws XPathException {
        while (true) {
            if (depth == 0) {
                PullEvent e = base.next();
                if (e instanceof StartElementEvent) {
                    push(e);
                } else if (e instanceof StartDocumentEvent) {
                    push(e);
                }
                return e;
            } else {
                PullEvent e = base.next();
                if (e instanceof StartElementEvent) {
                    prevAtomic = false;
                    push(e);
                    if (pendingTextNode != null) {
                        pendingOutput = e;
                        PullEvent next = pendingTextNode;
                        pendingTextNode = null;
                        return next;
                    } else {
                        return e;
                    }
                } else if (e instanceof StartDocumentEvent) {
                    prevAtomic = false;
                    push(e);
                    if (pendingTextNode != null) {
                        pendingOutput = e;
                        PullEvent next = pendingTextNode;
                        pendingTextNode = null;
                        return next;
                    } else {
                        //continue;
                    }
                } else if (e instanceof EndElementEvent) {
                    prevAtomic = false;
                    pop();
                    if (pendingTextNode != null) {
                        pendingOutput = e;
                        PullEvent next = pendingTextNode;
                        pendingTextNode = null;
                        return next;
                    } else {
                        return e;
                    }
                } else if (e instanceof EndDocumentEvent) {
                    prevAtomic = false;
                    pop();
                    if (pendingTextNode != null) {
                        pendingOutput = e;
                        PullEvent next = pendingTextNode;
                        pendingTextNode = null;
                        return next;
                    } else {
                        return e;
                    }
                } else if (e instanceof NodeInfo) {
                    prevAtomic = false;
                    switch (((NodeInfo)e).getNodeKind()) {
                        case Type.TEXT:
                            if (pendingTextNode == null) {
                                pendingTextNode = (NodeInfo)e;
                                pendingTextNodeIsMutable = false;
                            } else if (pendingTextNodeIsMutable) {
                                FastStringBuffer sb = (FastStringBuffer)((Orphan)pendingTextNode).getStringValueCS();
                                sb.append(((NodeInfo)e).getStringValueCS());
                            } else {
                                Orphan o = new Orphan(config);
                                o.setNodeKind(Type.TEXT);
                                FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.SMALL);
                                sb.append(pendingTextNode.getStringValueCS());
                                sb.append(((NodeInfo)e).getStringValueCS());
                                o.setStringValue(sb);
                                pendingTextNode = o;
                                pendingTextNodeIsMutable = true;
                            }
                            continue;
                        default:
                            if (pendingTextNode != null) {
                                pendingOutput = e;
                                PullEvent next = pendingTextNode;
                                pendingTextNode = null;
                                return next;
                            } else {
                                return e;
                            }
                    }
                } else if (e instanceof AtomicValue) {
                    if (prevAtomic) {
                        FastStringBuffer sb = (FastStringBuffer)((Orphan)pendingTextNode).getStringValueCS();
                        sb.append(' ');
                        sb.append(((AtomicValue)e).getStringValueCS());
                    } else if (pendingTextNode != null) {
                        prevAtomic = true;
                        if (pendingTextNodeIsMutable) {
                            FastStringBuffer sb = (FastStringBuffer)((Orphan)pendingTextNode).getStringValueCS();
                            sb.append(((AtomicValue)e).getStringValueCS());
                        } else {
                            Orphan o = new Orphan(config);
                            o.setNodeKind(Type.TEXT);
                            FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.SMALL);
                            sb.append(pendingTextNode.getStringValueCS());
                            sb.append(((AtomicValue)e).getStringValueCS());
                            o.setStringValue(sb);
                            pendingTextNode = o;
                            pendingTextNodeIsMutable = true;
                        }
                    } else {
                        prevAtomic = true;
                        Orphan o = new Orphan(config);
                        o.setNodeKind(Type.TEXT);
                        FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.SMALL);
                        sb.append(((AtomicValue)e).getStringValueCS());
                        o.setStringValue(sb);
                        pendingTextNode = o;
                        pendingTextNodeIsMutable = true;
                    }
                    //continue;
                } else {
                    throw new AssertionError("Unknown event");
                }
            }

        }
    }

    /**
     * Push a startElement or startDocument event onto the stack. At the same time, if it is a startElement
     * event, remove any redundant namespace declarations
     * @param p the startElement or startDocument event
     */

    private void push(PullEvent p) {
        if (depth >= startEventStack.length - 1) {
            PullEvent[] b2 = new PullEvent[depth*2];
            System.arraycopy(startEventStack, 0, b2, 0, startEventStack.length);
            startEventStack = b2;
        }
        if (p instanceof StartElementEvent) {
            int retained = 0;
            NamespaceBinding[] nsp = ((StartElementEvent)p).getLocalNamespaces();
            for (int nspi = 0; nspi < nsp.length; nspi++) {
                if (nsp[nspi] == null) {
                    break;
                }
                retained++;
              outer:
                for (int i=depth-1; i>=0; i--) {
                    PullEvent q = startEventStack[i];
                    if (q instanceof StartElementEvent) {
                        NamespaceBinding[] nsq = ((StartElementEvent)q).getLocalNamespaces();
                        for (int nsqi = 0; nsqi < nsq.length; nsqi++) {
                            if (nsp[nspi] == nsq[nsqi]) {
                                nsp[nspi] = null;
                                retained--;
                                break outer;
                            } else if (nsp[nspi].getPrefix().equals(nsq[nsqi].getPrefix())) {
                                break outer;
                            }
                        }
                    }
                }
            }
            if (retained < nsp.length) {
                NamespaceBinding[] nsr = new NamespaceBinding[retained];
                int nsri = 0;
                for (int nspi=0; nspi<nsp.length; nspi++) {
                    if (nsp[nspi] != null) {
                        nsr[nsri++] = nsp[nspi];
                        if (nsri == retained) {
                            break;
                        }
                    }
                }
                ((StartElementEvent)p).setLocalNamespaces(nsr);
            }
        }
        startEventStack[depth++] = p;
        prevAtomic = false;
    }

    private void pop() {
        depth--;
        prevAtomic = false;
    }


    /**
     * Determine whether the EventIterator returns a flat sequence of events, or whether it can return
     * nested event iterators
     *
     * @return true if the next() method is guaranteed never to return an EventIterator
     */

    public boolean isFlatSequence() {
        return true;
    }
}

