/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2002 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 "pmsqe.h"

#include "pmoutputdevice.h"
#include "pmxmlhelper.h"
#include "pmsqeedit.h"
#include "pmmemento.h"
#include "pmviewstructure.h"
#include "pmdefaults.h"
#include "pmmath.h"

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

#include <klocale.h>

const double c_defaultEastWestExponent = 1.0;
const double c_defaultNorthSouthExponent = 1.0;

PMViewStructure* PMSuperquadricEllipsoid::s_pDefaultViewStructure = 0;
int PMSuperquadricEllipsoid::s_vStep = c_defaultSuperquadricEllipsoidVSteps;
int PMSuperquadricEllipsoid::s_uStep = c_defaultSuperquadricEllipsoidUSteps;
int PMSuperquadricEllipsoid::s_parameterKey = 0;

PMSuperquadricEllipsoid::PMSuperquadricEllipsoid( )
      : Base( )
{
   m_eastWestExponent = c_defaultEastWestExponent;
   m_northSouthExponent = c_defaultNorthSouthExponent;
}

PMSuperquadricEllipsoid::~PMSuperquadricEllipsoid( )
{
}

QString PMSuperquadricEllipsoid::description( ) const
{
   return i18n( "superquadric ellipsoid" );
}

void PMSuperquadricEllipsoid::serialize( PMOutputDevice& dev ) const
{
   dev.objectBegin( "superellipsoid" );

   serializeName( dev );
   dev.writeLine( QString( "<%1, %2>" ).arg( m_eastWestExponent )
                  .arg( m_northSouthExponent ) );
   
   Base::serialize( dev );
   dev.objectEnd( );
}

void PMSuperquadricEllipsoid::serialize( QDomElement& e, QDomDocument& doc ) const
{
   e.setAttribute( "value_e", m_eastWestExponent );
   e.setAttribute( "value_n", m_northSouthExponent );
   Base::serialize( e, doc );
}

void PMSuperquadricEllipsoid::readAttributes( const PMXMLHelper& h )
{
   m_eastWestExponent = h.doubleAttribute( "value_e", c_defaultEastWestExponent );
   m_northSouthExponent = h.doubleAttribute( "value_n", c_defaultNorthSouthExponent );
   Base::readAttributes( h );
}

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

void PMSuperquadricEllipsoid::setEastWestExponent( double e )
{
   if( e != m_eastWestExponent )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTSuperquadricEllipsoid, PMEastWestExponentID,
                              m_eastWestExponent );
      if( e < 0.001 )
      {
         kdError( PMArea ) << "EastWestExponent < 0.001 in PMSuperquadricEllipsoid::setEastWestExponent\n";
         e = 0.001;
      }
      m_eastWestExponent = e;
      setViewStructureChanged( );
   }
}

void PMSuperquadricEllipsoid::setNorthSouthExponent( double n )
{
   if( n != m_northSouthExponent )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTSuperquadricEllipsoid, PMNorthSouthExponentID,
                              m_northSouthExponent );
      if( n < 0.001 )
      {
         kdError( PMArea ) << "NorthSouthExponent < 0.001 in PMSuperquadricEllipsoid::setNorthSouthExponent\n";
         n = 0.001;
      }

      m_northSouthExponent = n;
      setViewStructureChanged( );
   }
}

PMDialogEditBase* PMSuperquadricEllipsoid::editWidget( QWidget* parent ) const
{
   return new PMSuperquadricEllipsoidEdit( parent );
}

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

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTSuperquadricEllipsoid )
      {
         switch( data->valueID( ) )
         {
            case PMEastWestExponentID:
               setEastWestExponent( data->doubleData( ) );
               break;
            case PMNorthSouthExponentID:
               setNorthSouthExponent( data->doubleData( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMSuperquadricEllipsoid::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );
}


bool PMSuperquadricEllipsoid::isDefault( )
{
   if( ( m_eastWestExponent == c_defaultEastWestExponent ) &&
       ( m_northSouthExponent == c_defaultNorthSouthExponent ) )
      return true;
   return false;
}

void PMSuperquadricEllipsoid::createViewStructure( )
{
   if( !m_pViewStructure )
   {
      m_pViewStructure = new PMViewStructure( defaultViewStructure( ) );
      m_pViewStructure->points( ).detach( );
   }
   
   createPoints( m_pViewStructure->points( ), m_eastWestExponent,
                 m_northSouthExponent );
}

PMViewStructure* PMSuperquadricEllipsoid::defaultViewStructure( ) const
{
   if( !s_pDefaultViewStructure )
   {
      int u, v;
      // transform u and v steps to sphere u/v steps
      int uStep = s_uStep * 4;
      int vStep = s_vStep * 8;

      s_pDefaultViewStructure =
         new PMViewStructure( vStep * ( uStep - 1 ) + 2,
                              vStep * ( uStep - 1 ) * 2 + vStep );
      
      PMLineArray& lines = s_pDefaultViewStructure->lines( );
      
   
      // points
      createPoints( s_pDefaultViewStructure->points( ),
                    c_defaultEastWestExponent, c_defaultNorthSouthExponent );

      int offset = 0;

      // horizontal lines
      for( u = 0; u < ( uStep - 1 ); u++ )
      {
         for( v = 0; v < ( vStep - 1 ); v++ )
            lines[offset + v] =
               PMLine( u * vStep + v + 1, u * vStep + v + 2 );
         lines[offset + vStep - 1] =
            PMLine( u * vStep + 1, u * vStep + vStep );
         
         offset += vStep;
      }

      // vertical lines
      // lines at the "north pole"
      for( v = 0; v < vStep; v++ )
         lines[offset + v] = PMLine( 0, v + 1 );
      offset += vStep;

      for( v = 0; v < vStep; v++ )
      {
         for( u = 0; u < ( uStep - 2 ); u++ )
         {
            lines[offset + u] =
               PMLine( u * vStep + v + 1, ( u + 1 ) * vStep + v + 1 );
         }
         offset += ( uStep - 2 );
      }
      // lines at the "south pole"
      for( v = 0; v < vStep; v++ )
         lines[offset + v] = PMLine( ( uStep - 2 ) * vStep + v + 1,
                                     ( uStep - 1 ) * vStep + 1 );
      // offset += vStep;
   }
   
   return s_pDefaultViewStructure;
}

void PMSuperquadricEllipsoid::createPoints( PMPointArray& points,
                                            double e, double n )
{
   int u, v;
   int zi;
   int pbase = 0, pref = 0;

   if( e <= 0.001 )
      e = 0.001;
   if( n <= 0.001 )
      n = 0.001;
   
   double c2_e = 2.0 / e;
   double c2_n = 2.0 / n;
   double cn_2 = n / 2.0;
   double ce_2 = e / 2.0;
   double cn_e = n / e;
//   double ce_n = e / n;
   double z = 0.0, c = 0.0, a = 0.0, a2 = 0.0, x = 0.0, y = 0.0;
   double k = 0.0, k2 = 0.0, du = 0.0, dv = 0.0;
   PMPoint p;
   
   points[0] = PMPoint( 0, 0, 1 );
   pbase++;

   for( zi = 0; zi < 2; zi++ )
   {
      for( u = 0; u < s_uStep; u++ )
      {
         du = ( double ) ( u + 1 ) / ( double ) s_uStep;
         if( zi == 1 )
            du = 1.0 - du;
         k = tan( M_PI / 4.0 * pow( du, n < 1.0 ? n : sqrt( n ) ) );
         k2 = 1 / ( pow( k, c2_n ) + 1 );
         z = pow( k2, cn_2 );
         if( zi == 1 )
            z *= k;
         c = pow( 1 - pow( z, c2_n ), cn_e );
         
         for( v = 0; v < ( s_vStep + 1 ); v++ )
         {
            dv = ( double ) v / ( double ) s_vStep;
            a = tan( M_PI / 4.0 * pow( dv, e < 1.0 ? e : sqrt( e ) ) );
            a2 = 1 + pow( a, c2_e );
            x = pow( c / a2, ce_2 );
            y = x * a;

            points[pbase+v] = PMPoint( x, y, z );
         }
         // 1/8

         pref = pbase + 2 * s_vStep;
         for( v = 0; v < s_vStep; v++, pref-- )
         {
            p = points[pbase+v];
            x = p[0];
            p[0] = p[1];
            p[1] = x;
            points[pref] = p;
         }
         // 1/4
      
         pref = pbase + 4 * s_vStep;
         for( v = 0; v < ( 2 * s_vStep ); v++, pref-- )
         {
            p = points[pbase+v];
            p[0] = -p[0];
            points[pref] = p;
         }
         // 1/2
      
         pref = pbase + 8 * s_vStep - 1;
         for( v = 1; v < ( 4 * s_vStep ); v++, pref-- )
         {
            p = points[pbase+v];
            p[1] = -p[1];
            points[pref] = p;
         }
      
         pbase += 8 * s_vStep;
      }
   }
   
   for( u = 0; u < ( s_uStep * 2 - 1 ); u++ )
   {
      pbase = 1 + u * s_vStep * 8;
      pref = 1 + ( s_uStep * 4 - 2 - u ) * s_vStep * 8;
         
      for( v = 0; v < ( s_vStep * 8 ); v++, pref++ )
      {
         p = points[pbase + v];
         p[2] = -p[2];
         points[pref] = p;
      }
   }
   points[ s_vStep * 8 * ( s_uStep * 4 - 1 ) + 1 ] = PMPoint( 0, 0, -1 );
}

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

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

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