/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2000-2001 by Andreas Zehender
    email                : zehender@kde.org
**************************************************************************

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


#include "pmcompositeobject.h"
#include "pmxmlhelper.h"
#include "pmmemento.h"
#include "pmviewstructure.h"

#include <tqdom.h>

PMMetaObject* PMCompositeObject::s_pMetaObject = 0;

PMCompositeObject::PMCompositeObject( PMPart* part )
      : Base( part )
{
   m_pFirstChild = 0;
   m_pLastChild = 0;
   m_selectedChildren = 0;
   m_bViewStructureChanged = true;
   m_pViewStructure = 0;
}

PMCompositeObject::PMCompositeObject( const PMCompositeObject& c )
      : Base( c )
{
   m_pFirstChild = 0;
   m_pLastChild = 0;
   m_selectedChildren = 0;
   m_bViewStructureChanged = true;
   m_pViewStructure = 0;

   PMObject* o = c.m_pFirstChild;
   for( ; o; o = o->nextSibling( ) )
      appendChild( o->copy( ) );
}

PMCompositeObject::~PMCompositeObject( )
{
   PMObject* tmp;
   PMObject* next;

   tmp = m_pFirstChild;

   while( tmp )
   {
      next = tmp->m_pNextSibling;
      delete tmp;
      tmp = next;
   }

   if( m_pViewStructure )
      delete m_pViewStructure;
}

PMMetaObject* PMCompositeObject::metaObject( ) const
{
   if( !s_pMetaObject )
      s_pMetaObject = new PMMetaObject( "CompositeObject", Base::metaObject( ) );
   return s_pMetaObject;
}

void PMCompositeObject::cleanUp( ) const
{
   if( s_pMetaObject )
   {
      delete s_pMetaObject;
      s_pMetaObject = 0;
   }
   Base::cleanUp( );
}

PMObject* PMCompositeObject::childAt( uint index ) const
{
   PMObject* tmp;
   uint i = 0;

   for( tmp = m_pFirstChild; tmp && ( i < index ); tmp = tmp->nextSibling( ) )
      i++;
   return tmp;
}

int PMCompositeObject::findChild( PMObject* o )
{
   if( o->parent( ) != this )
      return -1;

   PMObject* tmp;
   int index = 0;

   for( tmp = m_pFirstChild; tmp; tmp = tmp->nextSibling( ) )
   {
      if( tmp == o )
         return index;
      else
         index++;
   }
   return -1;
}

bool PMCompositeObject::insertChild( PMObject* o, int i )
{
   if( i < 0 )
      return appendChild( o );
   else
   {
      if( i == 0 )
      {
         if( canInsert( o, 0 ) )
         {
            o->m_pNextSibling = m_pFirstChild;
            o->m_pPrevSibling = 0;
            if( m_pFirstChild )
               m_pFirstChild->m_pPrevSibling = o;
            m_pFirstChild = o;
            if( !m_pLastChild )
               m_pLastChild = o;
            o->m_pParent = this;
         }
         else
            return false;
      }
      else
      {
         PMObject* tmp = childAt( ( uint ) ( i - 1 ) );
         if( !tmp )
         {
            kdError( PMArea ) << "Index too big" << "\n";
            return false;
         }

         if( canInsert( o, tmp ) )
         {
            o->m_pPrevSibling = tmp;
            o->m_pNextSibling = tmp->m_pNextSibling;
            if( tmp->m_pNextSibling )
               tmp->m_pNextSibling->m_pPrevSibling = o;
            else
               m_pLastChild = o;
            tmp->m_pNextSibling = o;
            o->m_pParent = this;
         }
         else
            return false;
      }
      childAdded( o );
      return true;
   }
   return false;
}

bool PMCompositeObject::appendChild( PMObject* o )
{
   if( canInsert( o, m_pLastChild ) )
   {
      o->m_pParent = this;
      o->m_pPrevSibling = m_pLastChild;
      o->m_pNextSibling = 0;
      if( m_pLastChild )
         m_pLastChild->m_pNextSibling = o;
      else
         m_pFirstChild = o;
      m_pLastChild = o;

      childAdded( o );
      return true;
   }
   return false;
}

bool PMCompositeObject::insertChildAfter( PMObject* obj, PMObject* after )
{
   if( canInsert( obj, after ) )
   {
      if( after->m_pParent == this )
      {
         obj->m_pParent = this;
         obj->m_pPrevSibling = after;
         obj->m_pNextSibling = after->m_pNextSibling;
         if( after->m_pNextSibling )
            after->m_pNextSibling->m_pPrevSibling = obj;
         else
            m_pLastChild = obj;
         after->m_pNextSibling = obj;

         childAdded( obj );
         return true;
      }
      else
      {
         kdError( PMArea ) << "Object after is no child" << "\n";
         return false;
      }
   }
   return false;
}

bool PMCompositeObject::insertChildBefore( PMObject* obj, PMObject* before )
{
   if( before )
   {
      if( canInsert( obj, before->m_pPrevSibling ) )
      {
         if( before->m_pParent == this )
         {
            obj->m_pParent = this;
            obj->m_pPrevSibling = before->m_pPrevSibling;
            obj->m_pNextSibling = before;
            if( before->m_pPrevSibling )
               before->m_pPrevSibling->m_pNextSibling = obj;
            else
               m_pFirstChild = obj;
            before->m_pPrevSibling = obj;

            childAdded( obj );
            return true;
         }
         else
         {
            kdError( PMArea ) << "Object before is no child" << "\n";
            return false;
         }
      }
   }
   return false;
}

bool PMCompositeObject::takeChild( PMObject* o )
{
   if( ( PMObject* ) this == o->m_pParent )
   {
      // deselect the object and all child objects of o
      if( o->isSelected( ) )
         o->setSelected( false );
      else if( o->selectedChildren( ) > 0 )
         o->deselectChildren( );

      // remove it, but do NOT delete it.
      if( o->m_pPrevSibling )
         o->m_pPrevSibling->m_pNextSibling = o->m_pNextSibling;
      else
         m_pFirstChild = o->m_pNextSibling;
      if( o->m_pNextSibling )
         o->m_pNextSibling->m_pPrevSibling = o->m_pPrevSibling;
      else
         m_pLastChild = o->m_pPrevSibling;

      o->m_pParent = 0;
      o->m_pPrevSibling = 0;
      o->m_pNextSibling = 0;

      childRemoved( o );
      return true;
   }
   kdError( PMArea ) << "o is no child" << "\n";
   return false;
}

bool PMCompositeObject::takeChild( uint i )
{
   PMObject* tmp = childAt( i );
   if( tmp )
      return takeChild( tmp );
   kdError( PMArea ) << "Index too big";
   return false;
}

void PMCompositeObject::serialize( TQDomElement& e, TQDomDocument& doc ) const
{
   PMObject* tmp;

   for( tmp = m_pFirstChild; tmp; tmp = tmp->m_pNextSibling )
      e.appendChild( tmp->serialize( doc ) );
}

int PMCompositeObject::countChildren( ) const
{
   int num = 0;
   PMObject* tmp;

   for( tmp = m_pFirstChild; tmp; tmp = tmp->m_pNextSibling )
      num++;
   return num;
}

void PMCompositeObject::adjustSelectedChildren( int num )
{
   m_selectedChildren += num;
   if( m_selectedChildren < 0 )
   {
      kdError( PMArea ) << "num too big in PMCompositeObject::adjustSelectedChildren( )\n";
      m_selectedChildren = 0;
   }
   if( m_pParent )
      m_pParent->adjustSelectedChildren( num );
}

void PMCompositeObject::deselectChildren( )
{
   PMObject* tmp;
   if( m_selectedChildren > 0 )
   {
      tmp = m_pFirstChild;
      while( tmp && ( m_selectedChildren > 0 ) )
      {
         if( tmp->isSelected( ) )
            tmp->setSelected( false );
         else if( tmp->selectedChildren( ) > 0 )
            tmp->deselectChildren( );

         tmp = tmp->m_pNextSibling;
      }
   }
}


PMViewStructure* PMCompositeObject::viewStructure( )
{
   if( m_pViewStructure )
   {
      if( m_pViewStructure->parameterKey( ) != viewStructureParameterKey( ) )
      {
         // the default view structure or the parameters (detail level)
         // have changed
         m_bViewStructureChanged = true;
         delete m_pViewStructure;
         m_pViewStructure = 0;
      }
   }

   if( m_bViewStructureChanged )
   {
      PMViewStructure* dvs = defaultViewStructure( );
      if( dvs )
         if( dvs->parameterKey( ) == -1 ) // newly created view structure
            dvs->setParameterKey( viewStructureParameterKey( ) );

      if( isDefault( ) )
      {
         if( dvs )
         {
            if( m_pViewStructure )
            {
               if( *m_pViewStructure != *dvs )
               {
                  delete m_pViewStructure;
                  m_pViewStructure = new PMViewStructure( dvs );
               }
            }
            else
               m_pViewStructure = new PMViewStructure( dvs );
         }

         if( !m_pViewStructure )
            kdError( PMArea ) << "isDefault( ) returned true, but no default view structure is provided\n";
      }
      else
      {
         if( dvs )
         {
            if( m_pViewStructure && ( *m_pViewStructure == *dvs ) )
            {
               delete m_pViewStructure;
               m_pViewStructure = 0;
            }
         }
         createViewStructure( );
         if( m_pViewStructure )
            m_pViewStructure->setParameterKey( viewStructureParameterKey( ) );
      }
      m_bViewStructureChanged = false;
   }
   return m_pViewStructure;
}

void PMCompositeObject::setViewStructureChanged( )
{
   m_bViewStructureChanged = true;
   if( m_pMemento )
      m_pMemento->setViewStructureChanged( );
}