package net.sf.saxon.expr.instruct;

import net.sf.saxon.Configuration;
import net.sf.saxon.Controller;
import net.sf.saxon.event.Builder;
import net.sf.saxon.event.PipelineConfiguration;
import net.sf.saxon.event.Receiver;
import net.sf.saxon.event.SequenceReceiver;
import net.sf.saxon.evpull.BracketedDocumentIterator;
import net.sf.saxon.evpull.EventIterator;
import net.sf.saxon.evpull.SingletonEventIterator;
import net.sf.saxon.expr.*;
import net.sf.saxon.expr.parser.ExpressionTool;
import net.sf.saxon.expr.parser.ExpressionVisitor;
import net.sf.saxon.functions.StringJoin;
import net.sf.saxon.functions.SystemFunction;
import net.sf.saxon.lib.Validation;
import net.sf.saxon.om.*;
import net.sf.saxon.pattern.NodeKindTest;
import net.sf.saxon.pattern.NodeTest;
import net.sf.saxon.trace.ExpressionPresenter;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.util.FastStringBuffer;
import net.sf.saxon.type.*;
import net.sf.saxon.value.StringValue;
import net.sf.saxon.value.TextFragmentValue;
import net.sf.saxon.value.UntypedAtomicValue;

import java.util.Stack;


/**
 * An instruction to create a document node. This corresponds to the xsl:document-node
 * instruction in XSLT. It is also used to support the document node constructor
 * expression in XQuery, and is generated implicitly within an xsl:variable
 * that constructs a temporary tree.
 *
 * <p>Conceptually it represents an XSLT instruction xsl:document-node,
 * with no attributes, whose content is a complex content constructor for the
 * children of the document node.</p>
 */

public class DocumentInstr extends ParentNodeConstructor {

    private boolean textOnly;
    /*@Nullable*/ private String constantText;

    /**
     * Create a document constructor instruction
     * @param textOnly true if the content contains text nodes only
     * @param constantText if the content contains text nodes only and the text is known at compile time,
     *        supplies the textual content
     * @param baseURI the base URI of the instruction
     */

    public DocumentInstr(boolean textOnly,
                         String constantText,
                         String baseURI) {
        this.textOnly = textOnly;
        this.constantText = constantText;
        setBaseURI(baseURI);
    }

    /**
     * An implementation of Expression must provide at least one of the methods evaluateItem(), iterate(), or process().
     * This method indicates which of these methods is prefered. For instructions this is the process() method.
     */

    public int getImplementationMethod() {
        return Expression.EVALUATE_METHOD;
    }

    /**
     * Determine whether this is a "text only" document: essentially, an XSLT xsl:variable that contains
     * a single text node or xsl:value-of instruction.
     * @return true if this is a text-only document
     */

    public boolean isTextOnly() {
        return textOnly;
    }

    /**
     * For a text-only instruction, determine if the text value is fixed and if so return it;
     * otherwise return null
     * @return the fixed text value if appropriate; otherwise null
     */

    public /*@Nullable*/ CharSequence getConstantText() {
        return constantText;
    }

    /**
     * Simplify an expression. This performs any static optimization (by rewriting the expression
     * as a different expression). The default implementation does nothing.
     *
     * @return the simplified expression
     * @throws net.sf.saxon.trans.XPathException
     *          if an error is discovered during expression rewriting
     * @param visitor an expression visitor
     */

    /*@NotNull*/
    public Expression simplify(ExpressionVisitor visitor) throws XPathException {
        setLazyConstruction(visitor.getConfiguration().isLazyConstructionMode());
        return super.simplify(visitor);
    }



    /**
     * Check statically that the sequence of child instructions doesn't violate any obvious constraints
     * on the content of the node
     * @param env the static context
     * @throws XPathException
     */

    protected void checkContentSequence(StaticContext env) throws XPathException {
        checkContentSequence(env, content, validation, getSchemaType());
    }

    protected static void checkContentSequence(StaticContext env, Expression content, int validation, /*@Nullable*/ SchemaType type)
    throws XPathException {
        Expression[] components;
        if (content instanceof Block) {
            components = ((Block)content).getChildren();
        } else {
            components = new Expression[] {content};
        }

        int elementCount = 0;
        boolean isXSLT = content.getHostLanguage() == Configuration.XSLT;
        TypeHierarchy th = env.getConfiguration().getTypeHierarchy();
        for (Expression component : components) {
            ItemType it = component.getItemType(th);
            if (it instanceof NodeTest) {
                int possibleNodeKinds = ((NodeTest) it).getNodeKindMask();
                if (possibleNodeKinds == 1 << Type.ATTRIBUTE) {
                    XPathException de = new XPathException("Cannot create an attribute node whose parent is a document node");
                    de.setErrorCode(isXSLT ? "XTDE0420" : "XPTY0004");
                    de.setLocator(component);
                    throw de;
                } else if (possibleNodeKinds == 1 << Type.NAMESPACE) {
                    XPathException de = new XPathException("Cannot create a namespace node whose parent is a document node");
                    de.setErrorCode(isXSLT ? "XTDE0420" : "XQTY0024");
                    de.setLocator(component);
                    throw de;
                }
                if (possibleNodeKinds == 1 << Type.ELEMENT) {
                    elementCount++;
                    if (elementCount > 1 &&
                            (validation == Validation.STRICT || validation == Validation.LAX || type != null)) {
                        XPathException de = new XPathException("A valid document must have only one child element");
                        if (isXSLT) {
                            de.setErrorCode("XTTE1550");
                        } else {
                            de.setErrorCode("XQDY0061");
                        }
                        de.setLocator(component);
                        throw de;
                    }
                    if (validation == Validation.STRICT && component instanceof FixedElement) {
                        SchemaDeclaration decl = env.getConfiguration().getElementDeclaration(
                                ((FixedElement) component).getElementName().getFingerprint());
                        if (decl != null) {
                            ((FixedElement) component).getContentExpression().
                                    checkPermittedContents(decl.getType(), env, true);
                        }
                    }
                }
            }
        }
    }

    /**
     * Get the static properties of this expression (other than its type). The result is
     * bit-signficant. These properties are used for optimizations. In general, if
     * property bit is set, it is true, but if it is unset, the value is unknown.
     *
     * @return a set of flags indicating static properties of this expression
     */
    @Override
    public int computeSpecialProperties() {
        int p = super.computeSpecialProperties();
        p |= StaticProperty.SINGLE_DOCUMENT_NODESET;
        if (getValidationAction() == Validation.SKIP) {
            p |= StaticProperty.ALL_NODES_UNTYPED;
        }
        return p;
    }

    /**
     * In the case of a text-only instruction (xsl:variable containing a text node or one or more xsl:value-of
     * instructions), return an expression that evaluates to the textual content as an instance of xs:untypedAtomic
     * @return an expression that evaluates to the textual content
     */

    public Expression getStringValueExpression() {
        if (textOnly) {
            if (constantText != null) {
                return new StringLiteral(new UntypedAtomicValue(constantText));
            } else if (content instanceof ValueOf) {
                return ((ValueOf)content).convertToCastAsString();
            } else {
                StringJoin fn = (StringJoin)SystemFunction.makeSystemFunction(
                        "string-join", new Expression[]{content, new StringLiteral(StringValue.EMPTY_STRING)});
                CastExpression cast = new CastExpression(fn, BuiltInAtomicType.UNTYPED_ATOMIC, false);
                ExpressionTool.copyLocationInfo(this, cast);
                return cast;
            }
        } else {
            throw new AssertionError("getStringValueExpression() called on non-text-only document instruction");
        }
    }


    /**
     * Copy an expression. This makes a deep copy.
     *
     * @return the copy of the original expression
     */

    /*@NotNull*/
    public Expression copy() {
        DocumentInstr doc = new DocumentInstr(textOnly, constantText, getBaseURI());
        doc.setContentExpression(content.copy());
        doc.setValidationAction(getValidationAction(), getSchemaType());
        doc.setLazyConstruction(isLazyConstruction());
        return doc;
    }

    /**
     * Get the item type
     * @param th The TypeHierarchy
     * @return the in
     */
    /*@NotNull*/
    public ItemType getItemType(TypeHierarchy th) {
        return NodeKindTest.DOCUMENT;
    }

    public TailCall processLeavingTail(XPathContext context) throws XPathException {
        if (preservingTypes && !textOnly) {
            SequenceReceiver out = context.getReceiver();
            out.startDocument(0);
            content.process(context);
            out.endDocument();
            return null;
        } else {
            Item item = evaluateItem(context);
            if (item != null) {
                SequenceReceiver out = context.getReceiver();
                out.append(item, locationId, NodeInfo.ALL_NAMESPACES);
            }
            return null;
        }
    }

    /**
     * In streaming mode, process the first half of the instruction (to start a new document or element)
     * @param contextStack the dynamic evaluation context
     * @param state   a stack on which the instruction can save state information for use during the corresponding
     */

    public void processLeft(Stack<XPathContext> contextStack, Stack<Object> state) throws XPathException {
        // TODO: support validation
        XPathContext context = contextStack.peek();
        context.getReceiver().startDocument(0);
    }

    /**
     * In streaming mode, proecss the right half of the instruction (to end a new document or element)
     * @param contextStack the dynamic evaluation context
     * @param state   a stack on which the instruction can save state information for use during the corresponding
     */

    public void processRight(Stack<XPathContext> contextStack, Stack<Object> state) throws XPathException {
        contextStack.peek().getReceiver().endDocument();
    }

    /**
     * Evaluate as an item.
     */

    public Item evaluateItem(XPathContext context) throws XPathException {
        Controller controller = context.getController();
        assert controller != null;
        if (isLazyConstruction() && (
                !controller.getExecutable().isSchemaAware() ||
                        (validation == Validation.PRESERVE && getSchemaType() == null))) {
            return context.getConfiguration().makeUnconstructedDocument(this, context);
            //return new UnconstructedDocument(this, context);
        } else {

            DocumentInfo root;
            if (textOnly) {
                CharSequence textValue;
                if (constantText != null) {
                    textValue = constantText;
                } else {
                    FastStringBuffer sb = new FastStringBuffer(FastStringBuffer.SMALL);
                    SequenceIterator iter = content.iterate(context);
                    while (true) {
                        Item item = iter.next();
                        if (item==null) break;
                        sb.append(item.getStringValueCS());
                    }
                    textValue = sb.condense();
                }
                root = new TextFragmentValue(textValue, getBaseURI());
                ((TextFragmentValue)root).setConfiguration(controller.getConfiguration());
            } else {
                try {
                	SequenceReceiver saved = context.getReceiver();

                    Builder builder = controller.makeBuilder();
                    
                    builder.setBaseURI(getBaseURI());
                    builder.setTiming(false);

                    PipelineConfiguration pipe = controller.makePipelineConfiguration();
                    pipe.setHostLanguage(getHostLanguage());
                    //pipe.setBaseURI(baseURI);
                    builder.setPipelineConfiguration(pipe);

                    context.changeOutputDestination(
                            builder,
                            validation,
                            getSchemaType());
                    Receiver out = context.getReceiver();
                    out.open();
                    out.startDocument(0);

                    content.process(context);

                    out.endDocument();
                    out.close();
                    context.setReceiver(saved);
                    root = (DocumentInfo)builder.getCurrentRoot();
                } catch (XPathException e) {
                    e.maybeSetLocation(this);
                    e.maybeSetContext(context);
                    throw e;
                }
            }
            return root;
        }
    }

    public EventIterator iterateEvents(XPathContext context) throws XPathException {
        if (validation != Validation.PRESERVE) {
            // Schema validation can't be done in pull mode
            return new SingletonEventIterator(evaluateItem(context));
        }
        return new BracketedDocumentIterator(content.iterateEvents(context));

    }


    /**
     * Get the name of this instruction for diagnostic and tracing purposes
     * (the string "document-constructor")
     */

    public int getInstructionNameCode() {
        return StandardNames.XSL_DOCUMENT;
    }

    /**
     * Diagnostic print of expression structure. The abstract expression tree
     * is written to the supplied output destination.
     */

    public void explain(ExpressionPresenter out) {
        out.startElement("documentNode");
        out.emitAttribute("validation", Validation.toString(validation));
        final SchemaType schemaType = getSchemaType();
        if (schemaType != null) {
            out.emitAttribute("type", schemaType.getDescription());
        }        
        content.explain(out);
        out.endElement();
    }
}

//
// The contents of this file are subject to the Mozilla Public License Version 1.0 (the "License");
// you may not use this file except in compliance with the License. You may obtain a copy of the
// License at http://www.mozilla.org/MPL/
//
// Software distributed under the License is distributed on an "AS IS" basis,
// WITHOUT WARRANTY OF ANY KIND, either express or implied.
// See the License for the specific language governing rights and limitations under the License.
//
// The Original Code is: all this file
//
// The Initial Developer of the Original Code is Saxonica Limited.
// Portions created by ___ are Copyright (C) ___. All rights reserved.
//
// Contributor(s):
//