/*
 * Copyright (c) 1998-2012 Caucho Technology -- all rights reserved
 *
 * This file is part of Resin(R) Open Source
 *
 * Each copy or derived work must preserve the copyright notice and this
 * notice unmodified.
 *
 * Resin Open Source is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * Resin Open Source is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty
 * of NON-INFRINGEMENT.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Resin Open Source; if not, write to the
 *   Free SoftwareFoundation, Inc.
 *   59 Temple Place, Suite 330
 *   Boston, MA 02111-1307  USA
 *
 * @author Scott Ferguson
 */

package com.caucho.xml2;

import com.caucho.util.L10N;
import com.caucho.vfs.Depend;
import com.caucho.vfs.WriteStream;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.UserDataHandler;

import javax.xml.namespace.QName;
import java.io.IOException;
import java.util.ArrayList;

/**
 * QAbstractNode is an abstract implementation for any DOM node.
 */
public abstract class QAbstractNode implements CauchoNode, java.io.Serializable {
  protected static L10N L = new L10N(QAbstractNode.class);
  
  QDocument _owner;

  QNode _parent;

  QAbstractNode _next;
  QAbstractNode _previous;

  String _systemId;
  String _filename;
  int _line;

  protected QAbstractNode()
  {
  }

  protected QAbstractNode(QDocument owner)
  {
    _owner = owner;
  }

  public void setLocation(String systemId, String filename,
                          int line, int column)
  {
    _systemId = systemId;
    _filename = filename; 
    _line = line;
  }

  /**
   * Returns the node's source filename.
   */
  public String getFilename()
  {
    if (_filename != null)
      return _filename;
    else if (_previous != null)
      return _previous.getFilename();
    else if (_parent != null)
      return _parent.getFilename();
    else
      return null;
  }

  /**
   * Returns the base URI of the node.
   */
  public String getBaseURI()
  {
    if (_systemId != null)
      return _systemId;
    else if (_previous != null)
      return _previous.getBaseURI();
    else if (_parent != null)
      return _parent.getBaseURI();
    else
      return getFilename();
  }

  /**
   * Returns the base URI
   */
  public static String baseURI(Node node)
  {
    if (node instanceof QAbstractNode)
      return ((QAbstractNode) node).getBaseURI();
    else
      return null;
  }

  /**
   * Returns the node's source line.
   */
  public int getLine()
  {
    if (_filename != null)
      return _line;
    else if (_previous != null)
      return _previous.getLine();
    else if (_parent != null)
      return _parent.getLine();
    else
      return 0;
  }

  public int getColumn()
  {
    return 0;
  }

  /**
   * Returns the owning document.
   */
  public Document getOwnerDocument()
  {
    return _owner;
  }

  public boolean isSupported(String feature, String version)
  {
    return _owner.getImplementation().hasFeature(feature, version);
  }

  /**
   * Returns a feature value.
   */
  public Object getFeature(String feature, String version)
  {
    return null;
  }

  /**
   * Sets a feature value.
   */
  public void setFeature(String feature, boolean value)
  {
  }

  /**
   * Compares the document position
   */
  public short compareDocumentPosition(Node node)
  {
    return 0;
  }

  /**
   * Looks up a prefix value.
   */
  public String lookupPrefix(String feature)
  {
    return null;
  }
  
  /**
   * Returns true if the node has attributes.
   */
  public boolean hasAttributes()
  {
    return false;
  }

  public String getPrefix()
  {
    return "";
  }

  public void setPrefix(String prefix)
  {
  }

  public Object setUserData(String key, Object value, UserDataHandler userData)
  {
    return null;
  }

  public Object getUserData(String data)
  {
    return null;
  }

  public String getCanonicalName()
  {
    return getNodeName();
  }

  public String getLocalName()
  {
    return getNodeName();
  }

  public String getNamespaceURI()
  {
    return "";
  }

  public QName getQName()
  {
    return new QName(getNodeName(), getNamespaceURI());
  }

  public String getNodeValue() { return null; }

  public void setNodeValue(String value) {}

  public Node getParentNode() { return _parent; }

  public NodeList getChildNodes() 
  { 
    return new QEmptyNodeList();
  }

  public Node getFirstChild() { return null; }

  public Node getLastChild() { return null; }

  public Node getPreviousSibling() { return _previous; }

  public Node getNextSibling() { return _next; }

  public NamedNodeMap getAttributes() { return null; }

  public Node insertBefore(Node newChild, Node refChild)
    throws DOMException
  { 
    throw new QDOMException(DOMException.HIERARCHY_REQUEST_ERR, "");
  }

  public Node replaceChild(Node newChild, Node refChild)
    throws DOMException
  { 
    throw new QDOMException(DOMException.HIERARCHY_REQUEST_ERR, "");
  }

  public Node removeChild(Node oldChild) throws DOMException
  { 
    throw new QDOMException(DOMException.HIERARCHY_REQUEST_ERR, "");
  }

  public Node appendChild(Node newNode) throws DOMException
  { 
    throw new QDOMException(DOMException.HIERARCHY_REQUEST_ERR, "");
  }

  public boolean hasChildNodes() { return false; }

  public boolean equals(Node arg, boolean deep)
  {
    return this == arg;
  }

  void remove()
  {
    if (_owner != null)
      _owner._changeCount++;
    
    if (_previous != null)
      _previous._next = _next;
    else if (_parent != null)
      _parent._firstChild = _next;

    if (_next != null)
      _next._previous = _previous;
    else if (_parent != null)
      _parent._lastChild = _previous;

    _previous = null;
    _next = null;
    _parent = null;
  }

  public QAbstractNode getNextPreorder()
  {
    if (_next != null)
      return _next;

    for (QNode ptr = _parent; ptr != null; ptr = ptr._parent) {
      if (ptr._next != null)
        return ptr._next;
    }

    return null;
  } 

  public boolean hasContent() { return false; }

  public QAbstractNode getNextContent() 
  {
    for (QAbstractNode node = _next; node != null; node = node._next) {
      if (node.hasContent())
        return node;
    }

    return null;
  }

  public QAbstractNode getPreviousContent() 
  {
    for (QAbstractNode node = _previous; node != null; node = node._previous) {
      if (node.hasContent())
        return node;
    }

    return null;
  }

  public String getTextValue()
  {
    return getNodeValue();
  }

  /**
   * Support the same and the implementation
   */
  public boolean supports(String feature, String version)
  {
    return _owner._implementation.hasFeature(feature, version);
  }

  public void normalize()
  {
    
  }

  public Node cloneNode(boolean deep)
  {
    return _owner.importNode(this, deep);
  }

  // DOM level 3

  public short compareTreePosition(Node other)
  {
    throw new UnsupportedOperationException();
  }

  public String getTextContent()
    throws DOMException
  {
    return XmlUtil.textValue(this);
  }
  
  public void setTextContent(String textContent)
    throws DOMException
  {
    throw new UnsupportedOperationException();
  }

  public boolean isSameNode(Node other)
  {
    return this == other;
  }

  public String lookupNamespacePrefix(String namespaceURI, 
                                      boolean useDefault)
  {
    throw new UnsupportedOperationException();
  }

  public boolean isDefaultNamespace(String namespaceURI)
  {
    throw new UnsupportedOperationException();
  }

  public String lookupNamespaceURI(String prefix)
  {
    throw new UnsupportedOperationException();
  }

  public boolean isEqualNode(Node arg)
  {
    return equals(arg);
  }

  public Node getInterface(String feature)
  {
    throw new UnsupportedOperationException();
  }

  /*
  public Object setUserData(String key, 
                            Object data, 
                            UserDataHandler handler)
  {
    throw new UnsupportedOperationException();
  }
  
  public Object getUserData(String key)
  {
    throw new UnsupportedOperationException();
  }
  */

  // Caucho stuff

  public ArrayList<Depend> getDependencyList()
  {
    if (_owner != null)
      return _owner.getDependencyList();
    else
      return null;
  }
  
  boolean isNameValid(String name)
  {
    if (name == null || name.length() == 0)
      return false;

    if (! XmlChar.isNameStart(name.charAt(0)))
      return false;
    
    for (int i = 1; i < name.length(); i++) {
      char ch = name.charAt(i);
      if (! XmlChar.isNameChar(ch))
        return false;
    }

    return true;
  }

  public boolean checkValid()
    throws Exception
  {
    if (_parent == null) {
      if (_next != null || _previous != null)
        throw new Exception("null bad: " + this);
      else
        return true;
    }

    if (_parent._owner != _owner && _owner != _parent)
      throw new Exception("owner bad: " + this);

    QAbstractNode ptr = _parent._firstChild;
    for (; ptr != null && ptr != this; ptr = ptr._next) {
    }
    if (ptr == null)
      throw new Exception("not in parent: " + this);

    ptr = _parent._lastChild;
    for (; ptr != null && ptr != this; ptr = ptr._previous) {
    }
    if (ptr == null)
      throw new Exception("not in parent: " + this);

    if (_next == null && _parent._lastChild != this)
      throw new Exception("bad tail: " + this);

    else if (_next != null && _next._previous != this)
      throw new Exception("bad link: " + this);

    if (_previous == null && _parent._firstChild != this)
      throw new Exception("bad head: " + this);
    else if (_previous != null && _previous._next != this)
      throw new Exception("bad link: " + this);

    return true;
  }

  void print(XmlPrinter out) throws IOException
  {
  }

  public void print(WriteStream out) throws IOException
  {
    new XmlPrinter(out).printXml(this);
  }

  public void printPretty(WriteStream out) throws IOException
  {
    new XmlPrinter(out).printPrettyXml(this);
  }

  public void printHtml(WriteStream out) throws IOException
  {
    new XmlPrinter(out).printHtml(this);
  }

  private Object writeReplace()
  {
    return new SerializedXml(this);
  }
}
