package org.xdef.impl.saxon;

import javax.xml.namespace.QName;
import javax.xml.xquery.XQException;
import javax.xml.xquery.XQItem;
import javax.xml.xquery.XQItemType;
import javax.xml.xquery.XQResultSequence;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.xdef.XDAttr;
import org.xdef.XDContainer;
import org.xdef.XDText;
import org.xdef.XDValue;
import static org.xdef.XDValueID.XD_ANYURI;
import static org.xdef.XDValueID.XD_ATTR;
import static org.xdef.XDValueID.XD_BOOLEAN;
import static org.xdef.XDValueID.XD_BYTE;
import static org.xdef.XDValueID.XD_BYTES;
import static org.xdef.XDValueID.XD_CHAR;
import static org.xdef.XDValueID.XD_DATETIME;
import static org.xdef.XDValueID.XD_DOUBLE;
import static org.xdef.XDValueID.XD_DURATION;
import static org.xdef.XDValueID.XD_ELEMENT;
import static org.xdef.XDValueID.XD_INT;
import static org.xdef.XDValueID.XD_LONG;
import static org.xdef.XDValueID.XD_QNAME;
import static org.xdef.XDValueID.XD_TEXT;
import org.xdef.impl.code.DefBigInteger;
import org.xdef.impl.code.DefBoolean;
import org.xdef.impl.code.DefBytes;
import org.xdef.impl.code.DefContainer;
import org.xdef.impl.code.DefDate;
import org.xdef.impl.code.DefDecimal;
import org.xdef.impl.code.DefDouble;
import org.xdef.impl.code.DefDuration;
import org.xdef.impl.code.DefElement;
import org.xdef.impl.code.DefLong;
import org.xdef.impl.code.DefQName;
import org.xdef.impl.code.DefString;
import org.xdef.impl.code.DefUri;
import org.xdef.impl.code.XQueryImpl;
import org.xdef.proc.XXNode;
import org.xdef.sys.SException;
import org.xdef.sys.SRuntimeException;
import org.xdef.xml.KXqueryExpr;

/** Saxon implementation of XQuery.
 * @author Vaclav Trojan
 */
public class XQuerySaxonImpl implements XQueryImpl {

    /** Create empty instance of this class. */
    public XQuerySaxonImpl() {
        if (!KXqueryExpr.isXQueryImplementation()) {
            throw new RuntimeException("XQuery implementation is not available");
        }
    }

    private static boolean findVariable(QName[] qnames, QName qname) {
        for (QName qn : qnames) {
            if (qname.equals(qn)) {
                return true;
            }
        }
        return false;
    }

    /** Execute XPath expression and return result.
     * @param x query Expression.
     * @param node node or null.
     * @param xNode node model or null.
     * @return The string representation of value of the object.
     * @throws SRuntimeException if an error occurs.
     */
    @Override
    public final XDContainer exec(final KXqueryExpr x, final Node node, final XXNode xNode) {
        try {
            QName[] qnames = x.getAllExternalVariables();
            if (xNode != null && qnames != null && qnames.length > 0) {
                String[] names = xNode.getVariableNames();
                for (String s : names) {
                    XDValue var = xNode.getVariable(s);
                    if (var == null) {
                        continue;
                    }
                    if (s.charAt(0) == '#') {
                        s = s.substring(1);
                    }
                    QName qname = new QName(s);
                    if (!findVariable(qnames, qname)) {
                        continue;
                    }
                    switch (var.getItemId()) {
                        case XD_ELEMENT: x.bindValue(qname, var.getElement()); break;
                        case XD_ATTR: x.bindValue(qname, ((XDAttr) var).attrValue()); break;
                        case XD_TEXT: x.bindValue(qname, ((XDText) var).characterDataValue()); break;
                        case XD_BOOLEAN: x.bindValue(qname, var.booleanValue()); break;
                        case XD_LONG: x.bindValue(qname, var.longValue()); break;
                        case XD_INT: x.bindValue(qname, var.intValue()); break;
                        case XD_DOUBLE: x.bindValue(qname, var.doubleValue()); break;
                        case XD_BYTE: x.bindValue(qname, var.byteValue()); break;
                        case XD_CHAR: x.bindValue(qname, var.charValue()); break;
                        case XD_BYTES: x.bindValue(qname, ((DefBytes) var).getBytes()); break;
                        case XD_DATETIME: x.bindValue(qname, var.datetimeValue()); break;
                        case XD_DURATION: x.bindValue(qname, ((DefDuration) var).durationValue()); break;
                        case XD_QNAME: x.bindValue(qname, ((DefQName) var).getQName()); break;
                        case XD_ANYURI: x.bindValue(qname, ((DefUri) var).getURI()); break;
                        default: x.bindValue(qname, var.toString());
                    }
                }
            }
            DefContainer result = new DefContainer();
            int count = 0;
            XQResultSequence seq = (XQResultSequence) (node == null ? x.evaluate() : x.evaluate(node));
            if (seq == null) {
                return result;
            }
            while(seq.next()) {
                XQItem item = seq.getItem();
                if (item == null) {
                    break;
                }
                count++;
                switch (item.getItemType().getItemKind()) {
                    case XQItemType.XQITEMKIND_ATOMIC: {
                        switch (item.getItemType().getBaseType()) {
                            case XQItemType.XQBASETYPE_ANYURI:
                                result.addXDItem(new DefUri(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_BOOLEAN:
                                result.addXDItem(new DefBoolean(item.getBoolean())); continue;
                            case XQItemType.XQBASETYPE_BYTE: result.addXDItem(new DefLong(item.getByte())); continue;
                            case XQItemType.XQBASETYPE_INT:
                            case XQItemType.XQBASETYPE_INTEGER: result.addXDItem(new DefLong(item.getInt())); continue;
                            case XQItemType.XQBASETYPE_SHORT:
                            case XQItemType.XQBASETYPE_UNSIGNED_BYTE:
                                result.addXDItem(new DefLong(item.getShort())); continue;
                            case XQItemType.XQBASETYPE_UNSIGNED_SHORT:
                                result.addXDItem(new DefLong(item.getInt())); continue;
                            case XQItemType.XQBASETYPE_LONG:
                            case XQItemType.XQBASETYPE_UNSIGNED_INT:
                            case XQItemType.XQBASETYPE_POSITIVE_INTEGER:
                            case XQItemType.XQBASETYPE_NEGATIVE_INTEGER:
                            case XQItemType.XQBASETYPE_NONNEGATIVE_INTEGER:
                            case XQItemType.XQBASETYPE_NONPOSITIVE_INTEGER:
                                result.addXDItem(new DefLong(item.getLong())); continue;
                            case XQItemType.XQBASETYPE_FLOAT:
                                result.addXDItem(new DefDouble(item.getFloat())); continue;
                            case XQItemType.XQBASETYPE_DOUBLE:
                                result.addXDItem(new DefDouble(item.getDouble())); continue;
                            case XQItemType.XQBASETYPE_DECIMAL:
                                result.addXDItem(new DefDecimal(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_UNSIGNED_LONG:
                                result.addXDItem(new DefBigInteger(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_QNAME:
                                result.addXDItem(new DefQName(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_DURATION:
                            case XQItemType.XQBASETYPE_DAYTIMEDURATION:
                            case XQItemType.XQBASETYPE_YEARMONTHDURATION:
                                result.addXDItem(new DefDuration(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_DATE:
                            case XQItemType.XQBASETYPE_DATETIME:
                            case XQItemType.XQBASETYPE_GDAY:
                            case XQItemType.XQBASETYPE_GMONTH:
                            case XQItemType.XQBASETYPE_GMONTHDAY:
                            case XQItemType.XQBASETYPE_GYEAR:
                            case XQItemType.XQBASETYPE_GYEARMONTH:
                                result.addXDItem(new DefDate(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_BASE64BINARY:
                                result.addXDItem(DefBytes.parseBase64(item.getAtomicValue())); continue;
                            case XQItemType.XQBASETYPE_HEXBINARY:
                                result.addXDItem(DefBytes.parseHex(item.getAtomicValue())); continue;
                            default: result.addXDItem(new DefString(item.getAtomicValue()));
                        }
                        continue;
                    }
                    case XQItemType.XQITEMKIND_ATTRIBUTE:
                    case XQItemType.XQITEMKIND_COMMENT:
                    case XQItemType.XQITEMKIND_PI:
                    case XQItemType.XQITEMKIND_TEXT:
                        result.addXDItem(new DefString(item.getNode().getNodeValue())); continue;
                    case XQItemType.XQITEMKIND_DOCUMENT:
                    case XQItemType.XQITEMKIND_DOCUMENT_ELEMENT:
                        result.addXDItem(new DefElement(((Document) item.getNode()).getDocumentElement())); continue;
                    case XQItemType.XQITEMKIND_ELEMENT:
                        result.addXDItem(new DefElement((Element) item.getNode())); continue;
                    case XQItemType.XQITEMKIND_DOCUMENT_SCHEMA_ELEMENT:
                    case XQItemType.XQITEMKIND_SCHEMA_ATTRIBUTE:
                    case XQItemType.XQITEMKIND_SCHEMA_ELEMENT: continue;
                    case XQItemType.XQITEMKIND_ITEM:
                        result.addXDItem(new DefString(item.getItemAsString(null))); continue;
                    default: result.addXDItem(new DefString(item.getAtomicValue()));
                }
            }
            return result;
        } catch (SRuntimeException ex) {
            throw ex;
        } catch (SException ex) {
            throw new SRuntimeException(((SException) ex).getReport(), ex);
        } catch (XQException | DOMException ex) {
            throw new SRuntimeException(ex.toString());
        }
    }
}
