/*
 * Copyright (c) 1998-2006 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.xml;

import java.io.*;
import org.w3c.dom.*;
import com.caucho.vfs.*;

/**
 * QNode represents any node that can have children.
 */
public abstract class QNode extends QAbstractNode {
  protected QAbstractNode _firstChild;
  protected QAbstractNode _lastChild;

  public String getNodeValue() { return null; }

  public void setNodeValue(String value) {}

  /**
   * Returns a node list of the children.
   */
  public NodeList getChildNodes() 
  { 
    return new ChildNodeList();
  }

  public Node getFirstChild()
  {
    return _firstChild;
  }

  public Node getLastChild()
  {
    return _lastChild;
  }

  public Node getPreviousSibling()
  {
    return _previous;
  }

  public Node getNextSibling()
  {
    return _next;
  }

  public NamedNodeMap getAttributes() { return null; }

  public Node insertBefore(Node newChild, Node refChild)
    throws DOMException
  { 
    QAbstractNode qNewChild = (QAbstractNode) newChild;
    QAbstractNode qRefChild = (QAbstractNode) refChild;

    if (qNewChild._owner != _owner && qNewChild._owner != this)
      throw new QDOMException(DOMException.WRONG_DOCUMENT_ERR, 
			      "insertBefore new child from wrong document");

    qNewChild.remove();

    if (qRefChild != null && qRefChild._parent != this)
      throw new QDOMException(DOMException.NOT_FOUND_ERR, "insertBefore has no such child");

    if (qNewChild instanceof QDocumentFragment) {
      QDocumentFragment frag = (QDocumentFragment) qNewChild;
      QAbstractNode first = frag._firstChild;
      QAbstractNode next = null;
      for (QAbstractNode ptr = first; ptr != null; ptr = next) {
	next = ptr._next;
	insertBefore(ptr, refChild);
      }

      return first;
    }

    qNewChild._parent = this;

    if (refChild == null) {
      if (_firstChild == null) {
	qNewChild._previous = null;
	qNewChild._next = null;
	_firstChild = _lastChild = qNewChild;
      } else {
	_lastChild._next = qNewChild;
	qNewChild._previous = _lastChild;
	_lastChild = qNewChild;
	qNewChild._next = null;
      }

      return qNewChild;
    }

    qNewChild._previous = qRefChild._previous;
    qNewChild._next = qRefChild;
    if (qRefChild._previous == null)
      _firstChild = qNewChild;
    else
      qRefChild._previous._next = qNewChild;
    qRefChild._previous = qNewChild;

    return qNewChild;
  }

  public Node replaceChild(Node newChild, Node refChild)
    throws DOMException
  { 
    QAbstractNode qNewChild = (QAbstractNode) newChild;
    QAbstractNode qRefChild = (QAbstractNode) refChild;

    if (qRefChild == null || qRefChild._parent != this)
      throw new QDOMException(DOMException.NOT_FOUND_ERR, "ref is not child");

    if (qNewChild == null || qNewChild._owner != _owner)
      throw new QDOMException(DOMException.WRONG_DOCUMENT_ERR, 
			      "wrong document");

    if (_owner != null)
      _owner._changeCount++;
    
    qNewChild._previous = qRefChild._previous;
    qNewChild._next = qRefChild._next;
    qNewChild._parent = this;

    if (qNewChild._previous == null)
      _firstChild = qNewChild;
    else
      qNewChild._previous._next = qNewChild;

    if (qNewChild._next == null)
      _lastChild = qNewChild;
    else
      qNewChild._next._previous = qNewChild;

    qRefChild._previous = null;
    qRefChild._next = null;
    qRefChild._parent = null;

    return qRefChild;
  }

  public Node removeChild(Node oldChild) throws DOMException
  { 
    QAbstractNode qOldChild = (QAbstractNode) oldChild;

    if (qOldChild != null && qOldChild._parent != this) {
      throw new QDOMException(DOMException.NOT_FOUND_ERR, 
			      "removeChild has no such child");
    }

    if (_owner != null)
      _owner._changeCount++;
    
    if (qOldChild._previous == null)
      _firstChild = qOldChild._next;
    else
      qOldChild._previous._next = qOldChild._next;

    if (qOldChild._next == null)
      _lastChild = qOldChild._previous;
    else
      qOldChild._next._previous = qOldChild._previous;

    qOldChild._parent = null;
    qOldChild._next = null;
    qOldChild._previous = null;

    return qOldChild;
  }

  public Node appendChild(Node newNode) throws DOMException
  {
    QAbstractNode qNewNode = (QAbstractNode) newNode;

    if (qNewNode._owner != _owner && qNewNode._owner != this) {
      throw new QDOMException(DOMException.WRONG_DOCUMENT_ERR,
                              "can't appendChild from different document");
    }

    qNewNode.remove();

    if (qNewNode instanceof QDocumentFragment) {
      QDocumentFragment frag = (QDocumentFragment) qNewNode;
      QAbstractNode first = frag._firstChild;
      QAbstractNode next = null;
      for (QAbstractNode ptr = first; ptr != null; ptr = next) {
	next = ptr._next;
	appendChild(ptr);
      }

      return first;
    }

    qNewNode._parent = this;
    qNewNode._next = null;
    qNewNode._previous = _lastChild;
    if (_lastChild == null) {
      _lastChild = qNewNode;
      _firstChild = qNewNode;
    }
    else {
      _lastChild._next = qNewNode;
      _lastChild = qNewNode;
    }

    return qNewNode;
  }

  public boolean hasChildNodes()
  {
    return _firstChild != null;
  }

  public void setTextContent(String content)
  {
    QText text = new QText(content);
    text._owner = _owner;

    _firstChild = _lastChild = text;
  }

  public void normalize()
  {
  }

  public boolean checkValid()
    throws Exception
  {
    if (! super.checkValid())
      throw new Exception("super bad: " + this);

    if (_firstChild != null && _firstChild._previous != null)
      throw new Exception("first child bad: " + this);

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

    QAbstractNode ptr = _firstChild;
    for (; ptr != null; ptr = ptr._next) {
      if (ptr._parent != this)
	throw new Exception("child parent bad:" + this + " " + ptr);
      if (ptr._owner != _owner && ptr._owner != this)
	throw new Exception("child owner bad:" + this + " " + ptr + " " +
			    ptr._owner + " " + _owner);
      if (ptr._next != null && ptr._next._previous != ptr)
	throw new Exception("child links bad:" + this + " " + ptr);
    }

    ptr = _lastChild;
    for (; ptr != null; ptr = ptr._previous) {
      if (ptr._parent != this)
	throw new Exception("child parent bad:" + this + " " + ptr);
      if (ptr._owner != _owner && ptr._owner != this)
	throw new Exception("child owner bad:" + this + " " + ptr);
      if (ptr._previous != null && ptr._previous._next != ptr)
	throw new Exception("child links bad:" + this + " " + ptr);
    }

    return true;
  }

  public QAbstractNode getNextPreorder()
  {
    if (_firstChild != null)
      return _firstChild;
    else 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 equals(Object arg)
  {
    return this == arg;
  }

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

  // NodeList methods

  class ChildNodeList implements NodeList {
    /**
     * Returns the child with the given index.
     */
    public Node item(int index)
    {
      QAbstractNode ptr = _firstChild;
      for (; ptr != null && index > 0; index--) {
	ptr = ptr._next;
      }

      return ptr;
    }

    /**
     * Returns the number of children.
     */
    public int getLength()
    {
      int index = 0;
      for (QAbstractNode ptr = _firstChild; ptr != null; ptr = ptr._next)
	index++;

      return index;
    }
  }
}
