/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2000-2001 by Luis Carvalho
    email                : lpassos@mail.telepac.pt
**************************************************************************

**************************************************************************
*                                                                        *
*  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 "pmnormal.h"
#include "pmoutputdevice.h"
#include "pmxmlhelper.h"
#include "pmmemento.h"
#include "pmnormaledit.h"
#include "pmlistpattern.h"
#include "pmpattern.h"

#include <kdebug.h>
#include "pmglobals.h"
#include <klocale.h>

bool PMNormal::s_linkPossibilitiesCreated = false;
QValueList<PMDeclare::PMDeclareType> PMNormal::s_linkPossibilities;
double bumpSizeDefault = 0.0;

PMNormal::PMNormal( )
      : Base( )
{
   m_enableBumpSize = false;
   m_bumpSize = bumpSizeDefault;
}

PMNormal::~PMNormal( )
{
}

bool PMNormal::isA( PMObjectType t ) const
{
   if( t == PMTNormal )
      return true;
   return Base::isA( t );
}

QString PMNormal::description( ) const
{
   return i18n( "normal" );
}


void PMNormal::serialize( PMOutputDevice& dev ) const
{
   QString str1;
   bool object = true;

   if( parent( ) )
      if( parent( )->type( ) == PMTNormalMap )
         object = false;
   
   if( object )
      dev.objectBegin( "normal" );
   Base::serialize( dev );
   if( m_enableBumpSize )
   {
      str1.setNum( m_bumpSize );
      dev.writeLine( "bump_size " + str1 );
   }
   if( object )
      dev.objectEnd( );
}

void PMNormal::serialize( QDomElement& e, QDomDocument& doc ) const
{
   Base::serialize( e, doc );
   e.setAttribute( "enable_bump_size", m_enableBumpSize );
   e.setAttribute( "bump_size", m_bumpSize );
}

void PMNormal::readAttributes( const PMXMLHelper& h )
{
   Base::readAttributes( h );
   m_enableBumpSize = h.boolAttribute( "enable_bump_size", false );
   m_bumpSize = h.doubleAttribute( "bump_size", bumpSizeDefault );
}

void PMNormal::countChild( PMObjectType t, PMObject* o,
                           int& bumpMap, int& listpattern, int& pattern,
                           int& normalMap, int& slopeMap, int& warp, int& blend,
                           bool& afterPattern, bool& afterNormalMap,
                           bool& afterSlopeMap, bool& afterWarp, 
                           bool& hasDepth, bool& afterBlend,
                           bool& afterTransform, bool afterInsertPoint ) const
{
   switch( t )
   {
      case PMTBumpMap:
         bumpMap++;
         break;
      case PMTNormalList:
         listpattern++;
         if( o && ( ( PMNormalList* )o )->depth( ) != 0.0 )
            hasDepth = true;
         break;
      case PMTPattern:
         if( !afterInsertPoint )
            afterPattern = true;
         pattern++;
         if( o && ( ( PMPattern* )o )->depth( ) != 0.0 )
            hasDepth = true;
         break;
      case PMTWarp:
         if( !afterInsertPoint )
            afterWarp = true;
         warp++;
         break;
      case PMTNormalMap:
         if( !afterInsertPoint )
            afterNormalMap = true;
         normalMap++;
         break;
      case PMTSlopeMap:
         if( !afterInsertPoint )
            afterSlopeMap = true;
         slopeMap++;
         break;
      case PMTBlendMapModifiers:
         if( !afterInsertPoint )
            afterBlend = true;
         blend++;
         break;
      case PMTScale:
      case PMTRotate:
      case PMTTranslate:
      case PMTMatrix:
         if( !afterInsertPoint )
            afterTransform = true;
         break;
      default:
         break;
   }
}

bool PMNormal::canInsert( PMObjectType t,
                          int bumpMap, int listpattern, int pattern,
                          int normalMap, int slopeMap, int warp,
                          int blend, bool afterPattern, 
                          bool afterNormalMap, bool afterSlopeMap, 
                          bool afterWarp, bool /*hasDepth*/, bool afterBlend,
                          bool afterTransform ) const
{
   switch( t )
   {
      case PMTBumpMap:
         if( bumpMap + listpattern + pattern + normalMap + slopeMap + warp + blend == 0 )
            return true;
         break;
      case PMTNormalList:
         if( bumpMap + listpattern + pattern + normalMap + slopeMap + warp == 0 )
            return true;
         break;
      case PMTPattern:
         if( !bumpMap
             && ( ( pattern + listpattern ) == 0 )
             && !afterNormalMap && !afterSlopeMap && !afterWarp
             && !afterBlend && !afterTransform )
            return true;
         break;
      case PMTWarp:
         if( pattern && afterPattern )
            return true;
         break;
      case PMTNormalMap:
         if( ( bumpMap + normalMap + listpattern == 0 ) && !afterSlopeMap
             /*&& !hasDepth*/ && !afterBlend
             && ( !pattern || afterPattern ) )
            return true;
         break;
      case PMTSlopeMap:
         if( ( bumpMap + slopeMap + listpattern == 0 ) &&
             ( !pattern || afterPattern )
             && !afterBlend )
            return true;
         break;
      case PMTBlendMapModifiers:
         if( !blend
             && ( !( bumpMap + normalMap + slopeMap ) || afterNormalMap || afterSlopeMap )
             && ( !pattern || afterPattern )
             && !afterTransform )
            return true;
         break;
      case PMTScale:
      case PMTRotate:
      case PMTTranslate:
      case PMTMatrix:
         if( !pattern || afterPattern )
            return true;
      case PMTComment:
      case PMTRaw:
         return true;
      default:
         break;
   }
   return false;
}

bool PMNormal::canInsert( PMObjectType t, const PMObject* after,
                           const PMObjectList* objectsBetween ) const
{
   int bumpMap = 0, listpattern = 0, pattern = 0, normalMap = 0, 
       slopeMap= 0, warp = 0, blend = 0;
   bool afterPattern = false, afterNormalMap = false, afterSlopeMap = false,
        afterWarp = false, hasDepth = false, afterBlend = false,
        afterTransform = false;
   bool afterInsertPoint = false;
   PMObject* o;
   
   // count child objects
   if( !after )
      afterInsertPoint = true;
   for( o = firstChild( ); o; o = o->nextSibling( ) )
   {
      countChild( o->type( ), o, bumpMap, listpattern, pattern, normalMap, 
                  slopeMap, warp, blend, afterPattern, afterNormalMap, 
                  afterSlopeMap, afterWarp, hasDepth, afterBlend,
                  afterTransform, afterInsertPoint );
      if( o == after )
         afterInsertPoint = true;
   }
   if( objectsBetween )
   {
      PMObjectListIterator it( *objectsBetween );
      for( ; it.current( ); ++it )
         countChild( it.current( )->type( ), it.current( ), 
                     bumpMap, listpattern, pattern, normalMap, 
                     slopeMap, warp, blend, afterPattern, afterNormalMap, 
                     afterSlopeMap, afterWarp, hasDepth, afterBlend,
                     afterTransform, false );
   }
   
   return canInsert( t, bumpMap, listpattern, pattern, normalMap, slopeMap,
                     warp, blend, afterPattern, afterNormalMap, afterSlopeMap,
                     afterWarp, hasDepth, afterBlend, afterTransform );
}

int PMNormal::canInsert( const QValueList<PMObjectType>& list,
                          const PMObject* after ) const
{
   int bumpMap = 0, listpattern = 0, pattern = 0, normalMap = 0, 
       slopeMap = 0, warp = 0, blend = 0;
   bool afterPattern = false, afterNormalMap = false, afterSlopeMap = false,
        afterWarp = false, hasDepth = false, afterBlend = false,
        afterTransform = false;
   bool afterInsertPoint = false;

   QValueList<PMObjectType>::ConstIterator it;
   PMObject* o = firstChild( );
   PMObjectType t;
   int numInserts = 0;

   // count child objects
   if( !after )
      afterInsertPoint = true;
   for( o = firstChild( ); o; o = o->nextSibling( ) )
   {
      countChild( o->type( ), o, bumpMap, listpattern, pattern, normalMap, 
                  slopeMap, warp, blend, afterPattern, afterNormalMap, 
                  afterSlopeMap, afterWarp, hasDepth, afterBlend, 
                  afterTransform, afterInsertPoint );
      if( o == after )
         afterInsertPoint = true;
   }

   for( it = list.begin( ); it != list.end( ); ++it )
   {
      t = *it;
      if( canInsert( t, bumpMap, listpattern, pattern, normalMap, slopeMap, 
                     warp, blend, afterPattern, afterNormalMap, afterSlopeMap,
                     afterWarp, hasDepth, afterBlend, afterTransform ) )
         numInserts++;
      /* FIXME This is sort of a hack. In fact, we are assuming, in a list of
       * types, that if a normal pattern is specified, depth() has a coherent
       * value with the appearance of normal_map. It definitely needs a better
       * solution. 
       *    Maybe we should have PMTPattern and PMTPatternDepth? */
      countChild( t, NULL, bumpMap, listpattern, pattern, normalMap, slopeMap,
                  warp, blend, afterPattern, afterNormalMap, afterSlopeMap,
                  afterWarp, hasDepth, afterBlend, afterTransform, false );
   }
   return numInserts;
}

int PMNormal::canInsert( const PMObjectList& list,
                          const PMObject* after ) const
{
   int bumpMap = 0, listpattern = 0, pattern = 0, normalMap = 0, 
       slopeMap = 0, warp = 0, blend = 0;
   bool afterPattern = false, afterNormalMap = false, afterSlopeMap = false,
        afterWarp = false, hasDepth = false, afterBlend = false,
        afterTransform = false;
   bool afterInsertPoint = false;
   PMObjectListIterator it( list );
   PMObject* o;
   PMObjectType t;
   int numInserts = 0;
   
   // count child objects
   if( !after )
      afterInsertPoint = true;
   for( o = firstChild( ); o; o = o->nextSibling( ) )
   {
      countChild( o->type( ), o, bumpMap, listpattern, pattern, normalMap, 
                  slopeMap, warp, blend, afterPattern, afterNormalMap, 
                  afterSlopeMap, afterWarp, hasDepth, afterBlend, 
                  afterTransform, afterInsertPoint );
      if( o == after )
         afterInsertPoint = true;
   }
   
   for( ; it.current( ); ++it )
   {
      t = it.current( )->type( );
      if( canInsert( t, bumpMap, listpattern, pattern, normalMap, slopeMap, 
                     warp, blend, afterPattern, afterNormalMap, afterSlopeMap,
                     afterWarp, hasDepth, afterBlend, afterTransform ) )
         numInserts++;
      
      countChild( t, it.current( ), bumpMap, listpattern, pattern, normalMap, 
                  slopeMap, warp, blend, afterPattern, afterNormalMap, 
                  afterSlopeMap, afterWarp, hasDepth, afterBlend, 
                  afterTransform, false );
   }
   
   return numInserts;
}


QValueList<PMDeclare::PMDeclareType> PMNormal::linkPossibilities( ) const
{
   if( !s_linkPossibilitiesCreated )
   {
      s_linkPossibilities.append( PMDeclare::NormalDeclare );
      s_linkPossibilitiesCreated = true;
   }
   return s_linkPossibilities;
}

PMDialogEditBase* PMNormal::editWidget( QWidget* parent ) const
{
   return new PMNormalEdit( parent );
}

void PMNormal::enableBumpSize( bool c )
{
   if( c != m_enableBumpSize )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTNormal, PMEnableBumpSizeID, m_enableBumpSize );
      m_enableBumpSize = c;
   }
}

void PMNormal::setBumpSize( double c )
{
   if( c != m_bumpSize )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTNormal, PMBumpSizeID, m_bumpSize );
      m_bumpSize = c;
   }
}

void PMNormal::restoreMemento( PMMemento* s )
{
   PMMementoDataIterator it( s );
   PMMementoData* data;

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTNormal )
      {
         switch( data->valueID( ) )
         {
            case PMEnableBumpSizeID:
               enableBumpSize( data->boolData( ) );
               break;
            case PMBumpSizeID:
               setBumpSize( data->doubleData( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMNormal::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );
}

