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

#include "pmoutputdevice.h"
#include "pmxmlhelper.h"
#include "pmjuliafractaledit.h"
#include "pmmemento.h"
#include "pmviewstructure.h"
#include "pm3dcontrolpoint.h"

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

#include <klocale.h>

const PMVector c_defaultJuliaParameter = PMVector( -0.083, 0.0, -0.83, -0.025 );
const PMVector c_defaultSliceNormal = PMVector( 0.0, 0.0, 0.0, 1.0 );
const double c_defaultSliceDistance = 0.0;
const int c_defaultMaxIterations = 20;
const PMJuliaFractal::AlgebraType c_defaultAlgebraType = PMJuliaFractal::Quaternion;
const QString c_defaultAlgebraString = "quaternion";
const PMJuliaFractal::FunctionType c_defaultFunctionType = PMJuliaFractal::FTsqr;
const QString c_defaultFunctionString = "sqr";
const PMVector c_defaultExponent = PMVector( 0.0, 0.0 );
const double c_defaultPrecision = 20.0;



PMJuliaFractal::PMJuliaFractal( )
      : Base( )
{
   m_juliaParameter = c_defaultJuliaParameter;
   m_algebraType = c_defaultAlgebraType;
   m_functionType = c_defaultFunctionType;
   m_maxIterations = c_defaultMaxIterations;
   m_precision = c_defaultPrecision;
   m_sliceNormal = c_defaultSliceNormal;
   m_sliceDistance = c_defaultSliceDistance;
   m_exponent = c_defaultExponent;
}

PMJuliaFractal::~PMJuliaFractal( )
{
}

QString PMJuliaFractal::description( ) const
{
   return i18n( "julia fractal" );
}

void PMJuliaFractal::serialize( PMOutputDevice& dev ) const
{
   dev.objectBegin( "julia_fractal" );

   serializeName( dev );
   dev.writeLine( m_juliaParameter.serialize( ) );
   dev.writeLine( algebraTypeToString( m_algebraType ) );
   
   if( m_functionType == FTpwr )
      dev.writeLine( QString( "pwr(%1, %2)" ).arg( m_exponent[0] ).
                     arg( m_exponent[1] ) );
   else
      dev.writeLine( functionTypeToString( m_functionType ) );
   
   dev.writeLine( QString( "max_iteration %1" ).arg( m_maxIterations ) );
   dev.writeLine( QString( "precision %1" ).arg( m_precision ) );
   dev.writeLine( QString( "slice %1, %2" ).arg( m_sliceNormal.serialize( ) )
                  .arg( m_sliceDistance ) );
   
   
   Base::serialize( dev );
   dev.objectEnd( );
}

void PMJuliaFractal::serialize( QDomElement& e, QDomDocument& doc ) const
{
   e.setAttribute( "julia_parameter", m_juliaParameter.serializeXML( ) );
   e.setAttribute( "algebra_type", algebraTypeToString( m_algebraType ) );
   e.setAttribute( "function_type", functionTypeToString( m_functionType ) );
   e.setAttribute( "max_iterations", m_maxIterations );
   e.setAttribute( "precision", m_precision );
   e.setAttribute( "slice_normal", m_sliceNormal.serializeXML( ) );
   e.setAttribute( "slice_distance", m_sliceDistance );
   e.setAttribute( "exponent", m_exponent.serializeXML( ) );
   Base::serialize( e, doc );
}

void PMJuliaFractal::readAttributes( const PMXMLHelper& h )
{
   m_juliaParameter = h.vectorAttribute( "julia_parameter", c_defaultJuliaParameter );
   m_algebraType = stringToAlgebraType( h.stringAttribute( "algebra_type", c_defaultAlgebraString ) );
   m_functionType = stringToFunctionType( h.stringAttribute( "function_type", c_defaultFunctionString ) );
   m_maxIterations = h.intAttribute( "max_iterations", c_defaultMaxIterations );
   m_precision = h.doubleAttribute( "precision", c_defaultPrecision );
   m_sliceNormal = h.vectorAttribute( "slice_normal", c_defaultSliceNormal );
   m_sliceDistance = h.doubleAttribute( "slice_distance", c_defaultSliceDistance );
   m_exponent = h.vectorAttribute( "exponent", c_defaultExponent );
   Base::readAttributes( h );
}

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

void PMJuliaFractal::setJuliaParameter( const PMVector& p )
{
   if( p != m_juliaParameter )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMJuliaParameterID, m_juliaParameter );
      m_juliaParameter = p;
      m_juliaParameter.resize( 4 );
   }
}

void PMJuliaFractal::setAlgebraType( PMJuliaFractal::AlgebraType t )
{
   if( m_algebraType != t )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMAlgebraTypeID, m_algebraType );
      m_algebraType = t;
   }
}

void PMJuliaFractal::setFunctionType( PMJuliaFractal::FunctionType t )
{
   if( m_functionType != t )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMFunctionTypeID, m_functionType );
      m_functionType = t;
   }
}

void PMJuliaFractal::setMaximumIterations( int max )
{
   if( max <= 0 )
   {
      kdError( PMArea ) << "max <= 0 in PMJuliaFractal::setMaximumIterations\n";
      max = 20;
   }
   if( m_maxIterations != max )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMMaxIterationsID, m_maxIterations );
      m_maxIterations = max;
   }
}

void PMJuliaFractal::setPrecision( double p )
{
   if( p < 1.0 )
   {
      kdError( PMArea ) << "p < 1.0 in PMJuliaFractal::setPrecision\n";
      p = 1.0;
   }
   
   if( m_precision != p )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMPrecisionID, m_precision );
      m_precision = p;
   }
}

void PMJuliaFractal::setSliceNormal( const PMVector& n )
{
   if( m_sliceNormal != n )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMSliceNormalID, m_sliceNormal );
      m_sliceNormal = n;
      m_sliceNormal.resize( 4 );
   }
}

void PMJuliaFractal::setSliceDistance( double d )
{
   if( m_sliceDistance != d )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMSliceDistanceID, m_sliceDistance );
      m_sliceDistance = d;
   }
}

void PMJuliaFractal::setExponent( const PMVector& e )
{
   if( m_exponent != e )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTJuliaFractal, PMExponentID, m_exponent );
      m_exponent = e;
      m_exponent.resize( 2 );
   }
}

PMDialogEditBase* PMJuliaFractal::editWidget( QWidget* parent ) const
{
   return new PMJuliaFractalEdit( parent );
}

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

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTJuliaFractal )
      {
         switch( data->valueID( ) )
         {
            case PMJuliaParameterID:
               setJuliaParameter( data->vectorData( ) );
               break;
            case PMAlgebraTypeID:
               setAlgebraType( ( AlgebraType ) data->intData( ) );
               break;
            case PMFunctionTypeID:
               setFunctionType( ( FunctionType ) data->intData( ) );
               break;
            case PMMaxIterationsID:
               setMaximumIterations( data->intData( ) );
               break;
            case PMPrecisionID:
               setPrecision( data->doubleData( ) );
               break;
            case PMSliceNormalID:
               setSliceNormal( data->vectorData( ) );
               break;
            case PMSliceDistanceID:
               setSliceDistance( data->doubleData( ) );
               break;
            case PMExponentID:
               setExponent( data->vectorData( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMJuliaFractal::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );
}

QString PMJuliaFractal::functionTypeToString( PMJuliaFractal::FunctionType t )
{
   QString result = "sqr";
   switch( t )
   {
      case FTsqr:
         result = "sqr";
         break;
      case FTcube:
         result = "cube";
         break;
      case FTexp:
         result = "exp";
         break;
      case FTreciprocal:
         result = "reciprocal";
         break;
      case FTsin:
         result = "sin";
         break;
      case FTasin:
         result = "asin";
         break;
      case FTsinh:
         result = "sinh";
         break;
      case FTasinh:
         result = "asinh";
         break;
      case FTcos:
         result = "cos";
         break;
      case FTacos:
         result = "acos";
         break;
      case FTcosh:
         result = "cosh";
         break;
      case FTacosh:
         result = "acosh";
         break;
      case FTtan:
         result = "tan";
         break;
      case FTatan:
         result = "atan";
         break;
      case FTtanh:
         result = "tanh";
         break;
      case FTatanh:
         result = "atanh";
         break;
      case FTlog:
         result = "log";
         break;
      case FTpwr:
         result = "pwr";
         break;
   }
   return result;
}

PMJuliaFractal::FunctionType PMJuliaFractal::stringToFunctionType( const QString& str )
{
   FunctionType t = c_defaultFunctionType;

   if( str == "sqr" )
      t = FTsqr;
   else if( str == "cube" )
      t = FTcube;
   else if( str == "exp" )
      t = FTexp;
   else if( str == "reciprocal" )
      t = FTreciprocal;
   else if( str == "sin" )
      t = FTsin;
   else if( str == "asin" )
      t = FTasin;
   else if( str == "sinh" )
      t = FTsinh;
   else if( str == "asinh" )
      t = FTasinh;
   else if( str == "cos" )
      t = FTcos;
   else if( str == "acos" )
      t = FTacos;
   else if( str == "cosh" )
      t = FTcosh;
   else if( str == "acosh" )
      t = FTacosh;
   else if( str == "tan" )
      t = FTtan;
   else if( str == "atan" )
      t = FTatan;
   else if( str == "tanh" )
      t = FTtanh;
   else if( str == "atanh" )
      t = FTatanh;
   else if( str == "log" )
      t = FTlog;
   else if( str == "pwr" )
      t = FTpwr;
   return t;
}

QString PMJuliaFractal::algebraTypeToString( PMJuliaFractal::AlgebraType t )
{
   QString result;
   if( t == Quaternion )
      result = "quaternion";
   else
      result = "hypercomplex";
   return result;
}

PMJuliaFractal::AlgebraType PMJuliaFractal::stringToAlgebraType( const QString& str )
{
   AlgebraType t = c_defaultAlgebraType;
   if( str == "quaternion" )
      t = Quaternion;
   else if( str == "hypercomplex" )
      t = Hypercomplex;
   return t;
}
