/*
 * 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.ode.bpel.compiler.bom;

import org.apache.ode.bpel.compiler.api.SourceLocation;
import org.apache.ode.utils.DOMUtils;
import org.apache.ode.utils.NSContext;
import org.apache.ode.utils.stl.CollectionsX;
import org.apache.ode.utils.stl.MemberOfFunction;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import javax.xml.namespace.QName;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Common interface to all BPEL object model (BOM) objects. Provides for
 * location information (i.e. line numbers) and namespace context (XML namespace
 * prefix maps).
 */
public class BpelObject implements SourceLocation {

    public static final QName ATTR_LINENO = new QName("urn:org.apache.ode.bpel.compiler", "lineno");

    private final Element _element;

    private final QName _type;

    private final NSContext _nsContext;

    private List<BpelObject> _children = null;

    /** URI of the source document. */
    private URI _docURI;


    public BpelObject(Element el) {
        _element = el;
        _type = new QName(el.getNamespaceURI(), el.getLocalName());
        _nsContext = new NSContext();

        initNSContext(el);
    }

    public QName getType() {
        return _type;
    }

    public Element getElement() {
        return _element;
    }

    /**
     * Get the line number in the BPEL source document where this object is
     * defined.
     *
     * @return line number
     */
    public int getLineNo() {
        return Integer.valueOf(getAttribute(ATTR_LINENO, "-1"));
    }

    /**
     * Get the namespace context for this BPEL object (i.e. prefix-to-namespace
     * mapping).
     *
     * @return namespace context
     */
    public NSContext getNamespaceContext() {
        return _nsContext;
    }


    /**
     * Return the declared extensibility elements. The extensibility elements
     * declared as subelements of this BpelObject will be returned with a value
     * type of org.w3c.dom.Element. The ones declared as extensibility
     * attributes will be returned as a value type of String.
     *
     * @return extensibility qualified names and the full elements value (String
     *         or Element)
     */
    public Map<QName, Object> getExtensibilityElements() {
        // We consider anything that is not in the namespace of this element to be an
        // extensibility element/attribute.
        HashMap<QName, Object> ee = new HashMap<QName,Object>();
        for (BpelObject child  :getChildren()) {
            if (child.getType().getNamespaceURI() != null && !child.getType().getNamespaceURI().equals(getType().getNamespaceURI()))
                ee.put(child.getType(), child.getElement());
        }

        NamedNodeMap nnm = getElement().getAttributes();
        for (int i = 0; i < nnm.getLength(); ++i) {
            Node n = nnm.item(i);
            if (n.getNamespaceURI() != null && !n.getNamespaceURI().equals(getType().getNamespaceURI()))
                ee.put(new QName(n.getNamespaceURI(), n.getLocalName()), n.getTextContent());
        }
        return ee;

    }


    public Element getExtensibilityElement(QName extElName) {
        BpelObject e = getFirstChild(extElName);
        if (e == null)
            return null;
        return e.getElement();
    }

    public Element getFirstExtensibilityElement() {
        Element child = null;
        NodeList nl = getElement().getChildNodes();
        for (int i = 0; i < nl.getLength(); ++i) {
            Node node = nl.item(i);
            if (node.getNodeType() == Node.ELEMENT_NODE &&
                    !getType().getNamespaceURI().equals(node.getNamespaceURI())) {
                child = (Element)node;
                break;
            }
        }
        return child;
    }

    /**
     * Is this a BPEL 1.1 object?
     * @return boolean
     */
    public boolean is11() {
        return getType().getNamespaceURI() != null &&
            (getType().getNamespaceURI().equals(Bpel11QNames.NS_BPEL4WS_2003_03)
                    || getType().getNamespaceURI().equals(Bpel11QNames.NS_BPEL4WS_PARTNERLINK_2003_05));
    }

    public boolean is20Draft() {
        return getType().getNamespaceURI() != null &&
            (getType().getNamespaceURI().equals(Bpel20QNames.NS_WSBPEL2_0)
                    || getType().getNamespaceURI().equals(Bpel20QNames.NS_WSBPEL_PARTNERLINK_2004_03));
    }

    protected boolean isAttributeSet(String attrname) {
        return null != getAttribute(attrname, null);
    }

    protected <T extends BpelObject> List<T> getChildren(Class<T> cls) {
        return CollectionsX.filter(new ArrayList<T>(), getChildren(), cls);
    }

    protected <T extends BpelObject> T getFirstChild(Class<T> cls) {
        List<T> children = getChildren(cls);
        if (children.size() == 0)
            return null;
        return children.get(0);
    }

    protected List<BpelObject> getChildren(final QName type) {
        return CollectionsX.filter(new ArrayList<BpelObject>(), getChildren(), new MemberOfFunction<BpelObject>() {
            @Override
            public boolean isMember(BpelObject o) {
                return o.getType().equals(type);
            }
        });
    }

    protected BpelObject getFirstChild(final QName type) {
        return CollectionsX.find_if(getChildren(), new MemberOfFunction<BpelObject>() {
            @Override
            public boolean isMember(BpelObject o) {
                return o.getType().equals(type);
            }
        });
    }

    protected QName rewriteTargetNS(QName target) {
        return new QName(getType().getNamespaceURI(), target.getLocalPart());
    }

    protected List<BpelObject> getChildren() {
        if (_children == null) {
            _children = new ArrayList<BpelObject>();
            NodeList nl = _element.getChildNodes();
            for (int i = 0; i < nl.getLength(); ++i) {
                Node node = nl.item(i);
                if (node.getNodeType() == Node.ELEMENT_NODE)
                    _children.add(createBpelObject((Element) node));
            }
        }

        return _children;

    }

    protected BpelObject createBpelObject(Element element) {
        return BpelObjectFactory.getInstance().createBpelObject(element,_docURI);
    }

    protected String getAttribute(QName name, String dflt) {
        String val = _element.getAttributeNS(name.getNamespaceURI(), name.getLocalPart());
        if (val == null || "".equals(val))
            return dflt;
        return val;
    }

    protected String getAttribute(String name, String dflt) {
        String val = _element.getAttribute(name);
        if (val == null || "".equals(val))
            return dflt;
        return val;
    }

    protected String getAttribute(String name) {
        return getAttribute(name, null);
    }

    protected <T> T getAttribute(String attrName, Map<String, T> suppressJoinFailure, T notset) {
        String val = getAttribute(attrName, null);
        if (val == null || "".equals(val))
            return notset;
        return suppressJoinFailure.get(val);
    }



    /**
     * Initialize object's namespace context (recursively).
     *
     * @param el
     *            object's associated element.
     */
    private void initNSContext(Element el) {
        if (el.getParentNode() != null && el.getParentNode().getNodeType() == Node.ELEMENT_NODE)
            initNSContext((Element) el.getParentNode());
        NamedNodeMap attrs = el.getAttributes();
        for (int i = 0; i < attrs.getLength(); ++i) {
            Attr attr = (Attr) attrs.item(i);
            if (!attr.getName().startsWith("xmlns:"))
                continue;
            String prefix = attr.getLocalName();
            String uri = attr.getValue();

            _nsContext.register(prefix, uri);
        }

        Attr dflt = el.getAttributeNode("xmlns");
        if (dflt != null) {
            _nsContext.register("", dflt.getTextContent());
        }

    }

    public String getTextValue() {
        getElement().normalize();
        for (Node n = getElement().getFirstChild(); n != null; n = n.getNextSibling())
            switch (n.getNodeType()) {
            case Node.TEXT_NODE:
            case Node.ELEMENT_NODE:
            case Node.CDATA_SECTION_NODE:
                return n.getNodeValue();
            }
        return null;
    }

    @Override
    public String toString() {
        return DOMUtils.domToString(_element);
    }

    public int getColumnNo() {
        return 0;
    }

    public String getPath() {
        return null;
    }

    public URI getURI() {
        return _docURI;
    }

    public void setURI(URI uri) {
        _docURI = uri;
    }
}
