/***************************************************************************
                          pmtorus.cpp  -  description
                             -------------------
    copyright            : (C) 2001 Philippe Van Hecke
    email                : lephiloux@tiscalinet.be
 ***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   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 "pmtorus.h"

#include "pmoutputdevice.h"
#include "pmxmlhelper.h"
#include "pmtorusedit.h"
#include "pmmemento.h"
#include "pmviewstructure.h"
#include "pm3dcontrolpoint.h"
#include "pmdistancecontrolpoint.h"
#include "pmdefaults.h"

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

#include <klocale.h>

/** default param for the Torus */
const double c_defaultminorRadius = 0.25;
const double c_defaultmajorRadius = 0.5;
const bool c_defaultsturm = false;

/** default Torus structure */
PMViewStructure* PMTorus::s_pDefaultViewStructure = 0;

int PMTorus::s_vStep = c_defaultTorusVSteps;
int PMTorus::s_uStep = c_defaultTorusUSteps;
int PMTorus::s_parameterKey = 0;

PMTorus::PMTorus( )
      : Base( )
{
   m_minorRadius = c_defaultminorRadius;
   m_majorRadius = c_defaultmajorRadius;
   m_sturm = c_defaultsturm ;
}

PMTorus::~PMTorus( )
{
}


QString PMTorus::description( ) const
{
   return i18n( "torus" );
}

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

void PMTorus::serialize( PMOutputDevice& dev ) const
{
   dev.objectBegin( "torus" );
   serializeName( dev );
   QString strMinor;
   QString strMajor;
   strMinor.setNum( m_minorRadius );
   strMajor.setNum( m_majorRadius );

   dev.writeLine(strMajor + ", " + strMinor);
   if( m_sturm )
      dev.writeLine( QString( "sturm" ) );

   Base::serialize( dev );
   dev.objectEnd( );
}

void PMTorus::serialize( QDomElement& e, QDomDocument& doc ) const
{
   e.setAttribute( "minor_radius", m_minorRadius );
   e.setAttribute( "major_radius", m_majorRadius );
   e.setAttribute( "sturm", m_sturm );

   Base::serialize( e, doc );
}

void PMTorus::readAttributes( const PMXMLHelper& h )
{
   m_minorRadius = h.doubleAttribute( "minor_radius", c_defaultminorRadius );
   m_majorRadius = h.doubleAttribute( "major_radius", c_defaultmajorRadius );
   m_sturm = h.boolAttribute( "sturm", c_defaultsturm );

   Base::readAttributes( h );
}

PMDialogEditBase* PMTorus::editWidget( QWidget* parent ) const
{

 return new PMTorusEdit( parent );
}

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

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTTorus )
      {
         switch( data->valueID( ) )
         {
            case PMMinorRadiusID:
               setMinorRadius( data->doubleData( ) );
               break;
            case PMMajorRadiusID:
               setMajorRadius( data->doubleData( ) );
               break;
            case PMSturmID:
               setSturm( data->boolData( ) );
            default:
               kdError( PMArea ) << "Wrong ID in PMTorus::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );

}

void PMTorus::controlPoints( PMControlPointList& list )
{
   PMVector majorCenter( 0, 0, 0 );
   /** control points of the major radius */
   list.append( new PMDistanceControlPoint( majorCenter, PMVector( 1.0, 0.0, 0.0 ),
                                            m_majorRadius, PMMajorRadiusID,
                                            i18n( "Major radius (x)" ) ) );
   PMDistanceControlPoint* rcp =
      new PMDistanceControlPoint( majorCenter, PMVector( 0.0, 0.0, 1.0 ),
                                  m_majorRadius, PMMajorRadiusID,
                                  i18n( "Major radius (z)" ) );
   list.append( rcp );
   
   PMVector minorCenter( 0.0, 0.0, m_majorRadius );
   list.append( new PMDistanceControlPoint( rcp, PMVector( 0.0, 1.0, 0.0 ),
                                            m_minorRadius, PMMinorRadiusID,
                                            i18n( "Minor radius (y)" ) ) );
   list.append( new PMDistanceControlPoint( rcp, PMVector( 0.0, 0.0, 1.0 ),
                                            m_minorRadius, PMMinorRadiusID,
                                            i18n( "Minor radius (z)" ) ) );
}

void PMTorus::controlPointsChanged( PMControlPointList& list )
{
   bool majorChanged = false, minorChanged = false;
   PMControlPoint* p;
   for( p = list.first( ); p; p = list.next( ) )
   {
      if( p->changed( ) )
      {
         switch( p->id( ) )
         {
            case PMMinorRadiusID:
               setMinorRadius( ( ( PMDistanceControlPoint* ) p )->distance( ) );
               ( ( PMDistanceControlPoint* ) p )->setDistance( m_minorRadius );
               minorChanged = true;
               break;
            case PMMajorRadiusID:
               setMajorRadius( ( ( PMDistanceControlPoint* ) p )->distance( ) );
               ( ( PMDistanceControlPoint* ) p )->setDistance( m_majorRadius );
               majorChanged = true;
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMTorus::controlPointsChanged\n";
               break;
         }
      }
   }
   
   if( majorChanged )
      for( p = list.first( ); p; p = list.next( ) )
         if( p->id( ) == PMMajorRadiusID )
            ( ( PMDistanceControlPoint* ) p )->setDistance( m_majorRadius );
   if( minorChanged )
      for( p = list.first( ); p; p = list.next( ) )
         if( p->id( ) == PMMinorRadiusID )
            ( ( PMDistanceControlPoint* ) p )->setDistance( m_minorRadius );
}

bool PMTorus::isDefault( )
{
  if( ( m_minorRadius == c_defaultminorRadius )
      && ( m_majorRadius == c_defaultmajorRadius ) )
      return true;
   return false;
}

void PMTorus::createViewStructure( )
{
   if( !m_pViewStructure )
   {
      m_pViewStructure = new PMViewStructure( defaultViewStructure( ) );
      m_pViewStructure->points( ).detach( );
   }

   createPoints( m_pViewStructure->points( ), m_minorRadius,
                 m_majorRadius );

}

PMViewStructure* PMTorus::defaultViewStructure( ) const
{
   if( !s_pDefaultViewStructure )
   {
      int u, v;

      s_pDefaultViewStructure =
         new PMViewStructure(  s_vStep  *  s_uStep ,
                               s_vStep *  s_uStep  * 2 ) ;

      PMLineArray& lines = s_pDefaultViewStructure->lines( );


      // points
      createPoints( s_pDefaultViewStructure->points( ), c_defaultminorRadius,
                    c_defaultmajorRadius );
      for( u = 0; u <  s_uStep   ; u++ )
      {
         for( v = 0 ; v < s_vStep ; v++ )
         {
            
            lines[ u * s_vStep + v ] = PMLine( u * s_vStep  + v, u * s_vStep + ( (v+1) %  s_vStep ) );
            lines[ s_uStep * s_vStep + u * s_vStep + v ] = PMLine( u * s_vStep  + v, ( (u + 1) % s_uStep ) * s_vStep + v );
            
         } // end for v
         
      } // end for u

   }

   return s_pDefaultViewStructure;
}

void PMTorus::setMinorRadius( double minor_radius )
{
   if( m_minorRadius != minor_radius )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTTorus, PMMinorRadiusID, m_minorRadius );
      m_minorRadius =  minor_radius;
      setViewStructureChanged( );
   }
}

void PMTorus::setMajorRadius( double major_radius )
{
   if( m_majorRadius != major_radius )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTTorus, PMMajorRadiusID, m_majorRadius );
      m_majorRadius =  major_radius;
      setViewStructureChanged( );
   }
}

void PMTorus::setSturm( bool sturm )
{
   if( m_sturm != sturm )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTTorus, PMSturmID, m_sturm );
      m_sturm =  sturm;
      setViewStructureChanged( );
   }

}

void PMTorus::setUSteps( int u )
{
   if( u >= 2 )
   {
      s_uStep = u;
      if( s_pDefaultViewStructure )
      {
         delete s_pDefaultViewStructure;
         s_pDefaultViewStructure = 0;
      }
   }
   else
      kdDebug( PMArea ) << "PMTorus::setUSteps: U must be greater than 1\n";
   s_parameterKey++;
}

void PMTorus::setVSteps( int v )
{
   if( v >= 4 )
   {
      s_vStep = v;
      if( s_pDefaultViewStructure )
      {
         delete s_pDefaultViewStructure;
         s_pDefaultViewStructure = 0;
      }
   }
   else
      kdDebug( PMArea ) << "PMTorus::setVSteps: V must be greater than 3\n";
   s_parameterKey++;
}

void PMTorus::createPoints( PMPointArray& points, double minor_radius,
                             double  major_radius )
{
   double l_UradStep = ( 2.0 * M_PI ) / s_uStep;
   double l_VradStep = ( 2.0 * M_PI ) / s_vStep;
   double l_u = l_UradStep;
   int u, v;

   for( u = 0; u <   s_uStep ; u++ )
   {
      double l_v = 0.0;
      double y = minor_radius * sin ( l_u );
      double l_rcosu = major_radius + minor_radius  * cos( l_u );

      for( v = 0; v <  s_vStep ; v++ )
      {
         double x = l_rcosu * cos( l_v );
         double z = l_rcosu * sin( l_v );
         points[u * s_vStep + v ] = PMPoint( x, y, z );
         l_v = l_v + l_VradStep;
      }
      l_u = l_u + l_UradStep;
   }
}

void PMTorus::cleanUp( ) const
{
   if( s_pDefaultViewStructure )
      delete s_pDefaultViewStructure;
   s_pDefaultViewStructure = 0;
   Base::cleanUp( );
}
