/***************************************************************************
                          qdom_add.cpp  -  description
                             -------------------
    begin                : Wed Nov 21 2001
    copyright            : (C) 2001, 2002, 2003 by The KXMLEditor Team
    email                : lvanek@users.sourceforge.net
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program 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.                                   *
 *                                                                         *
 ***************************************************************************/

/** This file contains useful datatypes and functions in addition to the TQt DOM classes. */

#include "qdom_add.h"

#include <kiconloader.h>
#include <kdebug.h>

#include <tqtextstream.h>

#include "kxmleditorfactory.h"
#include "kxesearchdialog.h"

TQPixmap g_iconElement( UserIcon("xml_element",KXMLEditorFactory::instance()) );
TQPixmap g_iconText( UserIcon("xml_text",KXMLEditorFactory::instance()) );
TQPixmap g_iconComment( UserIcon("xml_comment",KXMLEditorFactory::instance()) );
TQPixmap g_iconCDATASection( UserIcon("xml_cdata",KXMLEditorFactory::instance()) );
TQPixmap g_iconProcessingInstruction( UserIcon("xml_procinstr",KXMLEditorFactory::instance()) );
TQPixmap g_iconElement_b( UserIcon("xml_element_b",KXMLEditorFactory::instance()) );
TQPixmap g_iconText_b( UserIcon("xml_text_b",KXMLEditorFactory::instance()) );
TQPixmap g_iconComment_b( UserIcon("xml_comment_b",KXMLEditorFactory::instance()) );
TQPixmap g_iconCDATASection_b( UserIcon("xml_cdata_b",KXMLEditorFactory::instance()) );
TQPixmap g_iconProcessingInstruction_b( UserIcon("xml_procinstr_b",KXMLEditorFactory::instance()) );
TQPixmap g_iconUnknown;

const TQPixmap & domTool_getIconForNodeType( TQDomNode::NodeType type, bool bBookmarked )
{
	if(!bBookmarked)
    { switch(type)
	      { case  TQDomNode::ElementNode: return g_iconElement; break;
		      case	TQDomNode::TextNode: return g_iconText; break;
		      case	TQDomNode::CDATASectionNode: return g_iconCDATASection; break;
		      case	TQDomNode::CommentNode: return g_iconComment; break;
		      case	TQDomNode::ProcessingInstructionNode: return g_iconProcessingInstruction; break;

		      default:
			      kdDebug() << "domTool_getIconForNodeType: unknown node type (" << type << ")" << endl;
	      }
    }
  else
    { switch(type)
	      { case  TQDomNode::ElementNode: return g_iconElement_b; break;
		      case	TQDomNode::TextNode: return g_iconText_b; break;
		      case	TQDomNode::CDATASectionNode: return g_iconCDATASection_b; break;
		      case	TQDomNode::CommentNode: return g_iconComment_b; break;
		      case	TQDomNode::ProcessingInstructionNode: return g_iconProcessingInstruction_b; break;

		      default:
			      kdDebug() << "domTool_getIconForNodeType: unknown node type (" << type << ")" << endl;
	      }
    }
	return g_iconUnknown;
}

// Obtain XPath for all nodes, instead of elements
TQString domTool_getPath( const TQDomNode & node )
{
	if ( node.isNull() )
	{
		kdDebug() << "domTool_getPath: elelent given" << endl;
		return TQString();
	}

  if(node.isElement())
		{
      kdDebug() << "use domTool_getPath( const TQDomElement & domElement ) for elements" << endl;
    }
    
	TQString strReturn;

  TQDomNode parentNode = node.parentNode();
	if ( (!parentNode.isNull()) && (!parentNode.isDocument()) )
	{
    strReturn = domTool_getPath( parentNode.toElement() ); // get the parent's path
    strReturn += "/"; // append slash
    strReturn += node.nodeName(); // append the given node's name
  }
	else
		strReturn = node.nodeName(); // set the given node's name (must be root element)

	return strReturn;
}

// Obtain XPath for elements
TQString domTool_getPath( const TQDomElement & domElement )
{
	if ( domElement.isNull() )
	{
		kdDebug() << "domTool_getPath: no node given" << endl;
		return TQString();
	}

	TQString strReturn;
	TQDomNode parentNode = domElement.parentNode();
	if ( (!parentNode.isNull()) && (!parentNode.isDocument()) )
	{
		// calculate index - only for elements with the same name
		int i = 0;
    bool bUseIndex = false; // index is used only when exist sibling(s) with the same name

    // traverse previous sibling elements with same name and calculate index
    TQDomNode tmpNode = domElement.previousSibling();
    while ( ! tmpNode.isNull() )
    {
      if(tmpNode.isElement())
      {
        TQDomElement domSiblingElement = tmpNode.toElement();

        if(domElement.tagName() == domSiblingElement.tagName())
          { i++; bUseIndex = true;
          }
      }
      tmpNode = tmpNode.previousSibling();
    }

    if(bUseIndex == false)
    {
      // traverse next sibling elements with same name
      // and decide, if index is necessary
      TQDomNode tmpNode = domElement.nextSibling();
      while ( ! tmpNode.isNull() )
      {
        if(tmpNode.isElement())
        {
          TQDomElement domSiblingElement = tmpNode.toElement();

          if(domElement.tagName() == domSiblingElement.tagName())
            bUseIndex = true;
        }

        tmpNode = tmpNode.nextSibling();
        
      }
    }    
		
		strReturn = domTool_getPath( parentNode.toElement() ); // get the parent's path
		strReturn += "/"; // append slash
		strReturn += domElement.nodeName(); // append the given node's name

    if(bUseIndex)
      {
        TQString strIndex;
        strIndex.setNum(i+1);
        strReturn += "[" + strIndex + "]"; // append the index
      }
	}
	else
		strReturn = domElement.nodeName(); // set the given node's name (must be root element)

	return strReturn;
}

unsigned int domTool_getLevel( const TQDomNode & node )
{
	if ( node.isNull() )
	{
		kdDebug() << "domTool_getLevel: internal implementation error - the given node is an empty one" << endl;
		return 0;
	}

	unsigned int iLevel = 0;
	TQDomNode parentNode = node.parentNode();
	while ( ! parentNode.isNull() )
	{
		iLevel++;
		parentNode = parentNode.parentNode();
	}

	return iLevel - 1;
}

TQString domTool_save( const TQDomNode & node, int iIndent )
{
	TQString strXML;
	TQTextStream ts( & strXML, IO_WriteOnly );

  node.save(ts, iIndent);
	return strXML;
}

TQDomNode domTool_prevNode( const TQDomNode & node )
{
	if ( node.isNull() )
	{
		kdDebug() << "domTool_prevNode: internal implementation error - the given node is an empty one" << endl;
		return TQDomNode();
	}

	if ( ! node.previousSibling().isNull() ) // if there is a prev. sibling
	{                                    // return its last grand child (if there is any)
		TQDomNode prevNode = node.previousSibling();
		while ( ! prevNode.lastChild().isNull() )
			prevNode = prevNode.lastChild();
		return prevNode;
	}
	else                        // if there is no prev. sibling, return
		return node.parentNode(); // the nodes parent (if there is any)

}

TQDomNode domTool_nextNode( const TQDomNode & node )
{
	if ( node.isNull() )
	{
		kdDebug() << "domTool_nextNode: internal implementation error - the given node is an empty one" << endl;
		return TQDomNode();
	}

	// checking for a child
	if ( ! node.firstChild().isNull() )
		return node.firstChild();

	// there is no child -> checking for the next sibling
	if ( ! node.nextSibling().isNull() )
		return node.nextSibling();

	// there is no next sibling -> checking for parents' next sibling(s)
	TQDomNode nodeParent = node.parentNode();
	while ( ! nodeParent.isNull() )
	{
		if ( ! nodeParent.nextSibling().isNull() )
			return nodeParent.nextSibling();

		nodeParent = nodeParent.parentNode(); // parent has no sibling - try its parent
	}

	// parent has no parents anymore
	return TQDomNode(); // return empty node
}

TQDomNode domTool_matchingNode( const TQDomNode & node, const TQString & szPath )
{
  if(szPath.length() == 0)
    return TQDomNode(); // return void node

  TQString szNodePath = node.isDocument() ? TQString("") : domTool_getPath(node);
	if ( szPath == szNodePath ) // test if the strings match
		return node;

   /* L.V. those optimalizations disallow find proc. instr. at doc. level

      //  we will find any node in root element subtree
      if ( szPath.length() <= szNodePath.length() ) // the given string must be longer
        return TQDomNode(); // otherwise we don't need to check the childs

      if ( szPath.left(szNodePath.length()) != szNodePath ) // the nodes path must be left part of the given path
        return TQDomNode(); // otherwise we don't need to check the childs
  */   
     
	// recursively check the childs
	TQDomNode nodeChild = node.firstChild();
	TQDomNode nodeTmp;
	while ( ! nodeChild.isNull() )
	{
		nodeTmp = domTool_matchingNode( nodeChild, szPath );
		if ( ! nodeTmp.isNull() )
			return nodeTmp;
		nodeChild = nodeChild.nextSibling();
	}

	return TQDomNode(); // nothing found -> return empty node
}

bool domTool_match( TQDomNode node, const KXESearchDialog * const pConditions )
{
	if ( node.isNull() )
	{
		kdDebug() << "domTool_match: internal implementation error - the given node is an empty one" << endl;
		return false;
	}

	if ( ! pConditions )
	{
		kdDebug() << "domTool_match: internal implementation error - the given pointer is a null pointer" << endl;
		return false;
	}

	switch ( node.nodeType() )
	{
		case TQDomNode::ElementNode: // ----------------------------------------
		{
			if ( pConditions->getInElementNames() )
			{
				if ( node.toElement().tagName().find( pConditions->getSearchString(), 0, pConditions->getMatchCase() ) >= 0 )
					return true;
			}

			if ( ( pConditions->getInAttributeNames() ) || ( pConditions->getInAttributeValues() ) )
			{
				TQDomNamedNodeMap list = node.toElement().attributes();
				unsigned int iLength = list.length();
				if ( iLength <= 0 )
					return false;     // no attributes

				for ( unsigned int iRow = 0; iRow < iLength; iRow++ )
				{
					if ( pConditions->getInAttributeNames() )
						if ( list.item(iRow).toAttr().name().find( pConditions->getSearchString(), 0, pConditions->getMatchCase() ) >= 0 )
							return true;
					if ( pConditions->getInAttributeValues() )
						if ( list.item(iRow).toAttr().value().find( pConditions->getSearchString(), 0, pConditions->getMatchCase() ) >= 0 )
							return true;
				}
				return false;
			}

			return false;
			break;
		}

		case TQDomNode::TextNode: // ----------------------------------------
		case TQDomNode::CDATASectionNode:
		case TQDomNode::CommentNode:
		{
			if ( pConditions->getInContents() )
			{
				if ( node.toCharacterData().data().find( pConditions->getSearchString(), 0, pConditions->getMatchCase() ) >= 0 )
					return true;
				else
					return false;
			}
			else
				return false;

			break;
		}

// TODO implement this for the other node types (eg proc.instr.)

		default:
			kdDebug() << "domTool_match: unknown node type (" << node.nodeType() << ")" << endl;
	}

	return true;
}

