/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.camel.converter.saxon;

import java.util.LinkedList;
import java.util.List;
import javax.xml.transform.dom.DOMSource;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import net.sf.saxon.Configuration;
import net.sf.saxon.dom.DOMNodeList;
import net.sf.saxon.dom.NodeOverNodeInfo;
import net.sf.saxon.om.DocumentInfo;
import net.sf.saxon.om.NodeInfo;
import net.sf.saxon.trans.XPathException;
import net.sf.saxon.type.Type;

import org.apache.camel.Converter;
import org.apache.camel.Exchange;
import org.apache.camel.FallbackConverter;
import org.apache.camel.TypeConverter;
import org.apache.camel.spi.TypeConverterRegistry;

@Converter
public final class SaxonConverter {

    private SaxonConverter() {
    }

    @Converter
    public static Document toDOMDocument(NodeInfo node) throws XPathException {
        switch (node.getNodeKind()) {
        case Type.DOCUMENT:
            // DOCUMENT type nodes can be wrapped directly
            return (Document) NodeOverNodeInfo.wrap(node);
        case Type.ELEMENT:
            // ELEMENT nodes need to build a new DocumentInfo before wrapping
            Configuration config = node.getConfiguration();
            DocumentInfo documentInfo = config.buildDocument(node);
            return (Document) NodeOverNodeInfo.wrap(documentInfo);
        default:
            return null;
        }
    }

    @Converter
    public static Node toDOMNode(NodeInfo node) {
        return NodeOverNodeInfo.wrap(node);
    }

    @Converter
    public static DOMSource toDOMSourceFromNodeInfo(NodeInfo nodeInfo) {
        return new DOMSource(toDOMNode(nodeInfo));
    }

    @Converter
    public static NodeList toDOMNodeList(List<? extends NodeInfo> nodeList) {
        List<Node> domNodeList = new LinkedList<Node>();
        if (nodeList != null) {
            for (NodeInfo ni : nodeList) {
                domNodeList.add(NodeOverNodeInfo.wrap(ni));
            }
        }
        return new DOMNodeList(domNodeList);
    }

    @FallbackConverter
    public static <T> T convertTo(Class<T> type, Exchange exchange, Object value, TypeConverterRegistry registry) {
        if (NodeInfo.class.isAssignableFrom(value.getClass())) {
            // use a fallback type converter so we can convert the embedded body if the value is NodeInfo
            NodeInfo ni = (NodeInfo) value;
            // first try to find a Converter for Node
            TypeConverter tc = registry.lookup(type, Node.class);
            if (tc != null) {
                Node node = NodeOverNodeInfo.wrap(ni);
                return tc.convertTo(type, exchange, node);
            }
            // if this does not exist we can also try NodeList (there are some type converters for that) as
            // the default Xerces Node implementation also implements NodeList.
            tc = registry.lookup(type, NodeList.class);
            if (tc != null) {
                List<NodeInfo> nil = new LinkedList<NodeInfo>();
                nil.add((NodeInfo) value);
                return tc.convertTo(type, exchange, toDOMNodeList(nil));
            }
        } else if (List.class.isAssignableFrom(value.getClass())) {
            TypeConverter tc = registry.lookup(type, NodeList.class);
            if (tc != null) {
                List<NodeInfo> lion = new LinkedList<NodeInfo>();
                for (Object o : (List<?>) value) {
                    if (o instanceof NodeInfo) {
                        lion.add((NodeInfo) o);
                    }
                }
                if (lion.size() > 0) {
                    NodeList nl = toDOMNodeList(lion);
                    return tc.convertTo(type, exchange, nl);
                }
            }
        } else if (NodeOverNodeInfo.class.isAssignableFrom(value.getClass())) {
            // NodeOverNode info is a read-only Node implementation from Saxon. In contrast to the JDK
            // com.sun.org.apache.xerces.internal.dom.NodeImpl class it does not implement NodeList, but
            // many Camel type converters are based on that interface. Therefore we convert to NodeList and
            // try type conversion in the fallback type converter.
            TypeConverter tc = registry.lookup(type, NodeList.class);
            if (tc != null) {
                List<Node> domNodeList = new LinkedList<Node>();
                domNodeList.add((NodeOverNodeInfo) value);
                return tc.convertTo(type, exchange, new DOMNodeList(domNodeList));
            }
        }

        return null;
    }
}
