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

#include "pmoutputdevice.h"
#include "pmxmlhelper.h"
#include "pmbicubicpatchedit.h"
#include "pmmemento.h"
#include "pmviewstructure.h"
#include "pm3dcontrolpoint.h"
#include "pmmath.h"

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

#include <klocale.h>

const double defaultPatchSize = 6.0;
const int defaultType = 0;
const int defaultUSteps = 3;
const int defaultVSteps = 3;
const double defaultFlatness = 0;

PMBicubicPatch::PMBicubicPatch( )
      : Base( )
{
   int x, z;
   double o = -defaultPatchSize / 2.0, s = defaultPatchSize / 3.0;
   m_patchType = defaultType;
   m_numUSteps = defaultUSteps;
   m_numVSteps = defaultVSteps;
   m_flatness = defaultFlatness;
   for( x = 0; x < 4; x++ )
      for( z = 0; z < 4; z++ )
         m_point[x+z*4] = PMVector( o + s * x, 0, o + s * z );
   m_vsUSteps = 0;
   m_vsVSteps = 0;
}

PMBicubicPatch::~PMBicubicPatch( )
{
}

QString PMBicubicPatch::description( ) const
{
   return i18n( "bicubic patch" );
}

void PMBicubicPatch::serialize( PMOutputDevice& dev ) const
{
   int u, v;
   QString str, line;
   dev.objectBegin( "bicubic_patch" );

   serializeName( dev );
   
   str.setNum( m_patchType );
   dev.writeLine( "type " + str );
   if( !approx( m_flatness, defaultFlatness ) )
   {
      str.setNum( m_flatness );
      dev.writeLine( "flatness " + str );
   }
   str.setNum( m_numUSteps );
   dev.writeLine( "u_steps " + str );
   str.setNum( m_numVSteps );
   dev.writeLine( "v_steps " + str );

   for( v = 0; v < 4; v++ )
   {
      line = m_point[v*4].serialize( );
      for( u = 1; u < 4; u++ )
         line += QString( ", " ) + m_point[u+4*v].serialize( );
      if( v != 3 )
         line += ",";
      dev.writeLine( line );
   }
   
   Base::serialize( dev );
   dev.objectEnd( );
}

void PMBicubicPatch::serialize( QDomElement& e, QDomDocument& doc ) const
{
   int i;

   e.setAttribute( "type", m_patchType );
   e.setAttribute( "flatness", m_flatness );
   e.setAttribute( "uSteps", m_numUSteps );
   e.setAttribute( "vSteps", m_numVSteps );
   
   for( i = 0; i < 16; i++ )
      e.setAttribute( QString( "cp%1" ).arg( i ), m_point[i].serializeXML( ) );
         
   Base::serialize( e, doc );
}

void PMBicubicPatch::readAttributes( const PMXMLHelper& h )
{
   int u, v;
   double o = -defaultPatchSize / 2.0, s = defaultPatchSize / 3.0;

   m_patchType = h.intAttribute( "type", defaultType );
   m_flatness = h.doubleAttribute( "flatness", defaultFlatness );
   m_numUSteps = h.intAttribute( "uSteps", defaultUSteps );
   m_numVSteps = h.intAttribute( "vSteps", defaultVSteps );
   
   for( v = 0; v < 4; v++ )
      for( u = 0; u < 4; u++ )
         m_point[u+v*4] = h.vectorAttribute( QString( "cp%1" ).arg( u+v*4 ),
                                      PMVector( o + s * u, 0, o + s * v ) );
   
   Base::readAttributes( h );
}

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

void PMBicubicPatch::setPatchType( int patchType )
{
   if( ( patchType == 0 ) || ( patchType == 1 ) )
   {
      if( patchType != m_patchType )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTBicubicPatch, PMTypeID, m_patchType );
         m_patchType = patchType;
      }
   }
   else
      kdError( PMArea ) << "Wrong type in PMBicubicPatch::setPatchType( )\n";
}

void PMBicubicPatch::setFlatness( double flatness )
{
   if( flatness >= 0.0 )
   {
      if( flatness != m_flatness )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTBicubicPatch, PMFlatnessID, m_flatness );
         m_flatness = flatness;
      }
   }
   else
      kdError( PMArea ) << "Flatness has to be >= 0 in PMBicubicPatch::setFlatness( )\n";
}

void PMBicubicPatch::setUSteps( int steps )
{
   if( steps >= 0 )
   {
      if( steps != m_numUSteps )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTBicubicPatch, PMUStepsID, m_numUSteps );
         m_numUSteps = steps;
         setViewStructureChanged( );
      }
   }
   else
      kdError( PMArea ) << "uSteps has to be >= 0 in PMBicubicPatch::setUSteps( )\n";
}

void PMBicubicPatch::setVSteps( int steps )
{
   if( steps >= 0 )
   {
      if( steps != m_numVSteps )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTBicubicPatch, PMVStepsID, m_numVSteps );
         m_numVSteps = steps;
         setViewStructureChanged( );
      }
   }
   else
      kdError( PMArea ) << "vSteps has to be >= 0 in PMBicubicPatch::setVSteps( )\n";
}

void PMBicubicPatch::setControlPoint( int i, const PMVector& p )
{
   if( ( i >= 0 ) && ( i <= 15 ) )
   {
      if( p != m_point[i] )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTBicubicPatch, PMCP0ID + i, m_point[i] );
         m_point[i] = p;
         setViewStructureChanged( );
      }
   }
   else
      kdError( PMArea ) << "Wrong index in PMBicubicPatch::setControlPoint( )\n";
}

PMVector PMBicubicPatch::controlPoint( int i ) const
{
   if( ( i >= 0 ) && ( i <= 15 ) )
      return m_point[i];
   else
      kdError( PMArea ) << "Wrong index in PMBicubicPatch::controlPoint( )\n";
   return PMVector( 0, 0, 0 );
}

PMDialogEditBase* PMBicubicPatch::editWidget( QWidget* parent ) const
{
   return new PMBicubicPatchEdit( parent );
}

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

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTBicubicPatch )
      {
         bool wrongID = false;
         switch( data->valueID( ) )
         {
            case PMTypeID:
               setPatchType( data->intData( ) );
               break;
            case PMFlatnessID:
               setFlatness( data->doubleData( ) );
               break;
            case PMUStepsID:
               setUSteps( data->intData( ) );
               break;
            case PMVStepsID:
               setVSteps( data->intData( ) );
               break;
            default:
               wrongID = true;
               break;
         }
         if( ( data->valueID( ) >= PMCP0ID ) && ( data->valueID( ) <= PMCP15ID ) )
         {
            setControlPoint( data->valueID( ) - PMCP0ID, data->vectorData( ) );
            wrongID = false;
         }
         if( wrongID )
            kdError( PMArea ) << "Wrong ID in PMBicubicPatch::restoreMemento\n";
      }
   }
   Base::restoreMemento( s );
}

void PMBicubicPatch::createViewStructure( )
{
   int u, v, i, j;
   int uSteps = m_numUSteps, vSteps = m_numVSteps;
   if( uSteps > 5 ) uSteps = 5;
   if( vSteps > 5 ) vSteps = 5;
   if( uSteps < 0 ) uSteps = 0;
   if( vSteps < 0 ) vSteps = 0;

   // bugfix: Swap u and v
   int segmentsU = pmpot( 2, vSteps );
   int segmentsV = pmpot( 2, uSteps );

   int np = ( segmentsU + 1 ) * ( segmentsV + 1 );
   int nl = segmentsU * ( segmentsV + 1 ) + ( segmentsU + 1 ) * segmentsV;
   
   int offset = 0;
   
   if( !m_pViewStructure )
   {
      m_pViewStructure = new PMViewStructure( np, nl );
      m_vsUSteps = uSteps + 1;
   }
   else
   {
      if( m_pViewStructure->points( ).size( ) != ( unsigned ) np )
         m_pViewStructure->points( ).resize( np );
      if( m_pViewStructure->lines( ).size( ) != ( unsigned ) nl )
         m_pViewStructure->lines( ).resize( nl );
   }

   if( ( m_vsUSteps != uSteps ) || ( m_vsVSteps != vSteps ) )
   {
      PMLineArray& lines = m_pViewStructure->lines( );
      int poffset = 0;
      for( v = 0; v < ( segmentsV + 1 ); v++ )
      {
         for( u = 0; u < segmentsU; u++ )
         {
            lines[offset + u] = PMLine( poffset, poffset + 1 );
            poffset++;
         }
         poffset++;
         offset += segmentsU;
      }
      poffset = 0;
      for( v = 0; v < segmentsV; v++ )
      {
         for( u = 0; u < ( segmentsU + 1 ); u++ )
         {
            lines[offset + u] = PMLine( poffset, poffset + segmentsU + 1 );
            poffset++;
         }
         offset += segmentsU + 1;
      }
      m_vsUSteps = uSteps;
      m_vsVSteps = vSteps;
   }

   PMPointArray& points = m_pViewStructure->points( );

   offset = 0;
   double incU = 1.0 / segmentsU;
   double incV = 1.0 / segmentsV;

   PMVector* hp[4];
   for( v = 0; v < 4; v++ )
      hp[v] = new PMVector[segmentsU+1];

   PMVector tp[4];

   double cu, cv;

   // points in u direction
   for( v = 0; v < 4; v++ )
   {
      for( u = 1; u < segmentsU; u++ )
      {
         cu = u * incU;
         
         for( i = 0; i < 4; i++ )
            tp[i] = m_point[v*4+i];
         for( i = 3; i > 0; i-- )
            for( j = 0; j < i; j++ )
               tp[j] = tp[j] * ( 1 - cu ) + tp[j+1] * cu;
         hp[v][u] = tp[0];
      }
      hp[v][0] = m_point[v*4];
      hp[v][segmentsU] = m_point[v*4+3];
   }

   for( v = 0; v <= segmentsV; v++ )
   {
      cv = v * incV;
      for( u = 0; u <= segmentsU; u++ )
      {
         for( i = 0; i < 4; i++ )
            tp[i] = hp[i][u];
         for( i = 3; i > 0; i-- )
            for( j = 0; j < i; j++ )
               tp[j] = tp[j] * ( 1 - cv ) + tp[j+1] * cv;
         points[offset] = tp[0];
         offset++;
      }
   }
   
   for( v = 0; v < 4; v++ )
      delete[] hp[v];
}

void PMBicubicPatch::controlPoints( PMControlPointList& list )
{
   int u, v;
   for( v = 0; v < 4; v++ )
      for( u = 0; u < 4; u++ )
         list.append( new PM3DControlPoint( m_point[u+v*4], u+v*4,
                                            i18n( "Point (%1, %2)" ).arg( u ).arg( v ) ) );
}

void PMBicubicPatch::controlPointsChanged( PMControlPointList& list )
{
   PMControlPoint* p;

   for( p = list.first( ); p; p = list.next( ) )
   {
      if( p->changed( ) )
      {
         setControlPoint( p->id( ), p->position( ) );
      }
   }
}
