/*
 * Copyright (c) 2017 [Karol Bucek](http://kares.org/)
 *
 * 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 nokogiri.internals;

import javax.xml.transform.dom.DOMSource;

import org.apache.xml.dtm.DTM;
import nokogiri.internals.dom2dtm.DOM2DTM;
import nokogiri.internals.dom2dtm.DOM2DTMdefaultNamespaceDeclarationNode;
import org.apache.xml.dtm.DTMWSFilter;
import org.apache.xml.res.XMLErrorResources;
import org.apache.xml.res.XMLMessages;
import org.w3c.dom.Node;

/**
 * @author kares
 */
public final class XalanDTMManagerPatch extends org.apache.xml.dtm.ref.DTMManagerDefault
{

  /**
   * Given a W3C DOM node, try and return a DTM handle.
   * Note: calling this may be non-optimal, and there is no guarantee that
   * the node will be found in any particular DTM.
   *
   * @param node Non-null reference to a DOM node.
   *
   * @return a valid DTM handle.
   */
  @Override
  public /* synchronized */ int
  getDTMHandleFromNode(org.w3c.dom.Node node)
  {
    //if (node == null) // "node must be non-null for getDTMHandleFromNode!");
    //    throw new IllegalArgumentException(XMLMessages.createXMLMessage(XMLErrorResources.ER_NODE_NON_NULL, null));
    assert node != null;

    if (node instanceof org.apache.xml.dtm.ref.DTMNodeProxy) {
      return ((org.apache.xml.dtm.ref.DTMNodeProxy) node).getDTMNodeNumber();
    }

    // Find the DOM2DTMs wrapped around this Document (if any)
    // and check whether they contain the Node in question.
    //
    // NOTE that since a DOM2DTM may represent a subtree rather
    // than a full document, we have to be prepared to check more
    // than one -- and there is no guarantee that we will find
    // one that contains ancestors or siblings of the node we're
    // seeking.
    //
    // %REVIEW% We could search for the one which contains this
    // node at the deepest level, and thus covers the widest
    // subtree, but that's going to entail additional work
    // checking more DTMs... and getHandleOfNode is not a
    // cheap operation in most implementations.
    //
    // TODO: %REVIEW% If overflow addressing, we may recheck a DTM
    // already examined. Ouch. But with the increased number of DTMs,
    // scanning back to check this is painful.
    // POSSIBLE SOLUTIONS:
    //   Generate a list of _unique_ DTM objects?
    //   Have each DTM cache last DOM node search?
    for (int i = 0; i < m_dtms.length; i++) {
      DTM thisDTM = m_dtms[i];
      if (thisDTM instanceof DOM2DTM) {
        int handle = ((DOM2DTM) thisDTM).getHandleOfNode(node);
        if (handle != DTM.NULL) {
          return handle;
        }
      }
    }

    // Not found; generate a new DTM.
    //
    // %REVIEW% Is this really desirable, or should we return null
    // and make folks explicitly instantiate from a DOMSource? The
    // latter is more work but gives the caller the opportunity to
    // explicitly add the DTM to a DTMManager... and thus to know when
    // it can be discarded again, which is something we need to pay much
    // more attention to. (Especially since only DTMs which are assigned
    // to a manager can use the overflow addressing scheme.)
    //
    // %BUG% If the source node was a DOM2DTM$defaultNamespaceDeclarationNode
    // and the DTM wasn't registered with this DTMManager, we will create
    // a new DTM and _still_ not be able to find the node (since it will
    // be resynthesized). Another reason to push hard on making all DTMs
    // be managed DTMs.

    // Since the real root of our tree may be a DocumentFragment, we need to
    // use getParent to find the root, instead of getOwnerDocument.  Otherwise
    // DOM2DTM#getHandleOfNode will be very unhappy.
    Node root = node;
    int rootType = root.getNodeType();
    Node p = (rootType == Node.ATTRIBUTE_NODE) ? ((org.w3c.dom.Attr) root).getOwnerElement() : root.getParentNode();
    for (; p != null; p = p.getParentNode()) { root = p; }

    // DOM2DTM dtm = (DOM2DTM) getDTM(new DOMSource(root), false, null);
    DOM2DTM dtm = getDTM(new DOMSource(root), false, null/*, true, true*/);

    int handle;

    if (node instanceof org.apache.xml.dtm.ref.dom2dtm.DOM2DTMdefaultNamespaceDeclarationNode
        || node instanceof DOM2DTMdefaultNamespaceDeclarationNode) {
      // Can't return the same node since it's unique to a specific DTM,
      // but can return the equivalent node -- find the corresponding
      // Document Element, then ask it for the xml: namespace decl.
      handle = dtm.getHandleOfNode(((org.w3c.dom.Attr) node).getOwnerElement());
      handle = dtm.getAttributeNode(handle, node.getNamespaceURI(), node.getLocalName());
    } else {
      handle = dtm.getHandleOfNode(node);

      rootType = root.getNodeType();
      // Is Node actually within the same document? If not, don't search!
      // This would be easier if m_root was always the Document node, but
      // we decided to allow wrapping a DTM around a subtree.
      if ((root == node) ||
          (rootType == Node.DOCUMENT_NODE && root == node.getOwnerDocument()) ||
          (rootType != Node.DOCUMENT_NODE && root.getOwnerDocument() == node.getOwnerDocument())
         ) {
        // If node _is_ in m_root's tree, find its handle
        //
        // %OPT% This check may be improved significantly when DOM
        // Level 3 nodeKey and relative-order tests become
        // available!
        for (Node cursor = node; cursor != null;
             cursor = (cursor.getNodeType() != Node.ATTRIBUTE_NODE)
                      ? cursor.getParentNode()
                      : ((org.w3c.dom.Attr)cursor).getOwnerElement()) {
          if (cursor == root) {
            // We know this node; find its handle.
            return (dtm).getHandleFromNode(node);
          }
        } // for ancestors of node
      } // if node and m_root in same Document
    }

    if (DTM.NULL == handle) {
      throw new RuntimeException(XMLMessages.createXMLMessage(XMLErrorResources.ER_COULD_NOT_RESOLVE_NODE,
                                 null));  //"Could not resolve the node to a handle!");
    }

    return handle;
  }

  private DOM2DTM
  getDTM(DOMSource source, boolean unique, DTMWSFilter whiteSpaceFilter/*, boolean incremental, boolean doIndexing*/)
  {
    int dtmPos = getFirstFreeDTMID();
    int documentID = dtmPos << IDENT_DTM_NODE_BITS;

    DOM2DTM dtm = new DOM2DTM(this, source, documentID, whiteSpaceFilter, m_xsf, true);

    addDTM(dtm, dtmPos, 0);
    return dtm;
  }

}
