////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// 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.style;

import net.sf.saxon.om.AxisInfo;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.tree.iter.AxisIterator;
import net.sf.saxon.type.Type;
import net.sf.saxon.value.Whitespace;

import java.util.ArrayList;
import java.util.List;

/**
 * A stylesheet module represents a module of a stylesheet. It is possible for two modules
 * to share the same stylesheet tree in the case where two includes or imports reference
 * the same URI; in this case the two modules will typically have a different import precedence.
 */
public class StylesheetModule {

    private XSLStylesheet sourceElement;
    private int precedence;
    private int minImportPrecedence;
    private StylesheetModule importer;
    boolean wasIncluded;


    // the value of the inputTypeAnnotations attribute on this module, combined with the values
    // on all imported/included modules. This is a combination of the bit-significant values
    // ANNOTATION_STRIP and ANNOTATION_PRESERVE.
    private int inputTypeAnnotations = 0;

    // A list of all the declarations in the stylesheet and its descendants, in increasing precedence order
    protected List<Declaration> topLevel = new ArrayList<Declaration>();

    public StylesheetModule(XSLStylesheet sourceElement, int precedence) {
        this.sourceElement = sourceElement;
        this.precedence = precedence;
    }

    public void setImporter(StylesheetModule importer) {
        this.importer = importer;
    }

    public StylesheetModule getImporter() {
        return importer;
    }

    /*@NotNull*/ public PrincipalStylesheetModule getPrincipalStylesheetModule() {
        return importer.getPrincipalStylesheetModule();
    }

    public XSLStylesheet getSourceElement() {
        return sourceElement;
    }

    public int getPrecedence() {
        return (wasIncluded ? importer.getPrecedence() : precedence);    }

    /**
     * Indicate that this stylesheet was included (by its "importer") using an xsl:include
     * statement as distinct from xsl:import
     */

    public void setWasIncluded() {
        wasIncluded = true;
    }

    /**
     * Set the minimum import precedence of this module, that is, the lowest import precedence of the modules
     * that it imports. This information is used to decide which template rules are eligible for consideration
     * by xsl:apply-imports
     * @param min the minimum import precedence
     */

    public void setMinImportPrecedence(int min) {
        this.minImportPrecedence = min;
    }

    /**
     * Get the minimum import precedence of this module, that is, the lowest import precedence of the modules
     * that it imports. This information is used to decide which template rules are eligible for consideration
     * by xsl:apply-imports
     * @return the minimum import precedence
     */

    public int getMinImportPrecedence() {
        return this.minImportPrecedence;
    }

    /**
     * Process xsl:include and xsl:import elements.
     */

    public void spliceIncludes() throws XPathException {

        boolean foundNonImport = false;
        topLevel = new ArrayList<Declaration>(50);
        minImportPrecedence = precedence;
        StyleElement previousElement = sourceElement;

        AxisIterator kids = sourceElement.iterateAxis(AxisInfo.CHILD);

        while (true) {
            NodeInfo child = kids.next();
            if (child == null) {
                break;
            }
            if (child.getNodeKind() == Type.TEXT) {
                // in an embedded stylesheet, white space nodes may still be there
                if (!Whitespace.isWhite(child.getStringValueCS())) {
                    previousElement.compileError(
                            "No character data is allowed between top-level elements", "XTSE0120");
                }

            } else if (child instanceof DataElement) {
                foundNonImport = true;
            } else {
                previousElement = (StyleElement) child;
                if (child instanceof XSLGeneralIncorporate) {
                    XSLGeneralIncorporate xslinc = (XSLGeneralIncorporate) child;
                    xslinc.processAttributes();

                    if (xslinc.isImport()) {
                        if (foundNonImport && !sourceElement.isXslt30Processor()) {
                            xslinc.compileError("xsl:import elements must come first", "XTSE0200");
                        }
                    } else {
                        foundNonImport = true;
                    }

                    // get the included stylesheet. This follows the URL, builds a tree, and splices
                    // in any indirectly-included stylesheets.

                    xslinc.validateInstruction();
                    int errors = sourceElement.getPreparedStylesheet().getErrorCount();
                    StylesheetModule inc =
                            xslinc.getIncludedStylesheet(this, precedence);
                    if (inc == null) {
                        return;  // error has been reported
                    }
                    errors = sourceElement.getPreparedStylesheet().getErrorCount() - errors;
                    if (errors > 0) {
                        xslinc.compileError("Reported " + errors  + (errors==1 ? " error" : " errors") +
                                " in " + (xslinc.isImport() ? "imported" : "included") +
                                " stylesheet module", "XTSE0165");
                    }

                    // after processing the imported stylesheet and any others it brought in,
                    // adjust the import precedence of this stylesheet if necessary

                    if (xslinc.isImport()) {
                        precedence = inc.getPrecedence() + 1;
                    } else {
                        precedence = inc.getPrecedence();
                        inc.setMinImportPrecedence(minImportPrecedence);
                        inc.setWasIncluded();
                    }

                    // copy the top-level elements of the included stylesheet into the top level of this
                    // stylesheet. Normally we add these elements at the end, in order, but if the precedence
                    // of an element is less than the precedence of the previous element, we promote it.
                    // This implements the requirement in the spec that when xsl:include is used to
                    // include a stylesheet, any xsl:import elements in the included document are moved
                    // up in the including document to after any xsl:import elements in the including
                    // document.

                    List<Declaration> incchildren = inc.topLevel;
                    for (int j = 0; j < incchildren.size(); j++) {
                        Declaration decl = incchildren.get(j);
                        //StyleElement elem = decl.getSourceElement();
                        int last = topLevel.size() - 1;
                        if (last < 0 || decl.getPrecedence() >= topLevel.get(last).getPrecedence()) {
                            topLevel.add(decl);
                        } else {
                            while (last >= 0 && decl.getPrecedence() < topLevel.get(last).getPrecedence()) {
                                last--;
                            }
                            topLevel.add(last + 1, decl);
                        }
                    }
                } else {
                    foundNonImport = true;
                    Declaration decl = new Declaration(this, (StyleElement)child);
                    topLevel.add(decl);
                }
            }
        }
    }

    /**
     * Get the value of the input-type-annotations attribute, for this module combined with that
     * of all included/imported modules. The value is an or-ed combination of the two bits
     * {@link XSLStylesheet#ANNOTATION_STRIP} and {@link XSLStylesheet#ANNOTATION_PRESERVE}
     * @return the value of the input-type-annotations attribute, for this module combined with that
     * of all included/imported modules
     */

    public int getInputTypeAnnotations() {
        return inputTypeAnnotations;
    }

    /**
     * Set the value of the input-type-annotations attribute, for this module combined with that
     * of all included/imported modules. The value is an or-ed combination of the two bits
     * {@link XSLStylesheet#ANNOTATION_STRIP} and {@link XSLStylesheet#ANNOTATION_PRESERVE}
     * @param annotations the value of the input-type-annotations attribute, for this module combined with that
     * of all included/imported modules.
     */

    public void setInputTypeAnnotations(int annotations) throws XPathException {
        inputTypeAnnotations |= annotations;
        if (inputTypeAnnotations == (XSLStylesheet.ANNOTATION_STRIP | XSLStylesheet.ANNOTATION_PRESERVE)) {
            getPrincipalStylesheetModule().compileError(
                    "One stylesheet module specifies input-type-annotations='strip', " +
                    "another specifies input-type-annotations='preserve'", "XTSE0265");
        }
    }


}

