/*
**************************************************************************
                                 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 "pmtriangle.h"
#include "pmtriangleedit.h"

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

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

#include <klocale.h>

const PMVector point0Default = PMVector( -1.0, 0.0, 0.0 );
const PMVector point1Default = PMVector( 1.0, 0.0, 0.0 );
const PMVector point2Default = PMVector( 0.0, 0.0, 1.0 );
const PMVector normal0Default = PMVector( 0.0, 1.0, 0.0 );
const PMVector normal1Default = PMVector( 0.0, 1.0, 0.0 );
const PMVector normal2Default = PMVector( 0.0, 1.0, 0.0 );


PMViewStructure* PMTriangle::s_pDefaultViewStructure = 0;

PMTriangle::PMTriangle( )
      : Base( )
{
   m_point[0] = point0Default;
   m_point[1] = point1Default;
   m_point[2] = point2Default;
   m_normal[0] = normal0Default;
   m_normal[1] = normal1Default;
   m_normal[2] = normal2Default;
   m_smooth = false;
}

PMTriangle::~PMTriangle( )
{
}

QString PMTriangle::description( ) const
{
   if( m_smooth )
      return i18n( "smooth triangle" );
   return i18n( "triangle" );
}

void PMTriangle::serialize( PMOutputDevice& dev ) const
{
   if( m_smooth )
   {
      dev.objectBegin( "smooth_triangle" );
      
      serializeName( dev );
      dev.writeLine( m_point[0].serialize( ) + ", " + m_normal[0].serialize( ) + "," );
      dev.writeLine( m_point[1].serialize( ) + ", " + m_normal[1].serialize( ) + "," );
      dev.writeLine( m_point[2].serialize( ) + ", " + m_normal[2].serialize( ) );
      
      Base::serialize( dev );
      dev.objectEnd( );
   }
   else
   {
      dev.objectBegin( "triangle" );
      
      serializeName( dev );
      dev.writeLine( m_point[0].serialize( ) + ", " + m_point[1].serialize( )
                     + ", " + m_point[2].serialize( ) );
      
      Base::serialize( dev );
      dev.objectEnd( );
   }
}

void PMTriangle::serialize( QDomElement& e, QDomDocument& doc ) const
{
   e.setAttribute( "point0", m_point[0].serializeXML( ) );
   e.setAttribute( "point1", m_point[1].serializeXML( ) );
   e.setAttribute( "point2", m_point[2].serializeXML( ) );
   e.setAttribute( "normal0", m_normal[0].serializeXML( ) );
   e.setAttribute( "normal1", m_normal[1].serializeXML( ) );
   e.setAttribute( "normal2", m_normal[2].serializeXML( ) );
   e.setAttribute( "smooth", m_smooth );
   Base::serialize( e, doc );
}

void PMTriangle::readAttributes( const PMXMLHelper& h )
{
   m_point[0] = h.vectorAttribute( "point0", point0Default );
   m_point[1] = h.vectorAttribute( "point1", point1Default );
   m_point[2] = h.vectorAttribute( "point2", point2Default );
   m_normal[0] = h.vectorAttribute( "normal0", normal0Default );
   m_normal[1] = h.vectorAttribute( "normal1", normal1Default );
   m_normal[2] = h.vectorAttribute( "normal2", normal2Default );
   m_smooth = h.boolAttribute( "smooth", false );
   Base::readAttributes( h );
}

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

void PMTriangle::setPoint( int i, const PMVector& p )
{
   if( ( i >= 0 ) && ( i <= 2 ) )
   {
      if( p != m_point[i] )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTTriangle, PMPoint0ID + i, m_point[i] );
         m_point[i] = p;
         m_point[i].resize( 3 );
         setViewStructureChanged( );
      }
   }
   else
      kdError( PMArea ) << "Wrong index in PMTriangle::setPoint\n";
}

PMVector PMTriangle::point( int i ) const
{
   if( ( i >= 0 ) && ( i <= 2 ) )
      return m_point[i];
   else
      kdError( PMArea ) << "Wrong index in PMTriangle::setPoint\n";
   return PMVector( 0.0, 0.0, 0.0 );
}

void PMTriangle::setNormal( int i, const PMVector& p )
{
   if( ( i >= 0 ) && ( i <= 2 ) )
   {
      if( p != m_normal[i] )
      {
         if( m_pMemento )
            m_pMemento->addData( PMTTriangle, PMNormal0ID + i, m_normal[i] );
         m_normal[i] = p;
         m_normal[i].resize( 3 );
         setViewStructureChanged( );
      }
   }
   else
      kdError( PMArea ) << "Wrong index in PMTriangle::setNormal\n";
}

PMVector PMTriangle::normal( int i ) const
{
   if( ( i >= 0 ) && ( i <= 2 ) )
      return m_normal[i];
   else
      kdError( PMArea ) << "Wrong index in PMTriangle::setNormal\n";
   return PMVector( 0.0, 0.0, 0.0 );
}

void PMTriangle::setSmoothTriangle( bool on )
{
   if( on != m_smooth )
   {
      if( m_pMemento )
      {
         m_pMemento->addData( PMTTriangle, PMSmoothID, m_smooth );
         m_pMemento->setDescriptionChanged( );
      }
      m_smooth = on;
      setViewStructureChanged( );
   }
}

PMDialogEditBase* PMTriangle::editWidget( QWidget* parent ) const
{
   return new PMTriangleEdit( parent );
}

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

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTTriangle )
      {
         switch( data->valueID( ) )
         {
            case PMPoint0ID:
               setPoint( 0, data->vectorData( ) );
               break;
            case PMPoint1ID:
               setPoint( 1, data->vectorData( ) );
               break;
            case PMPoint2ID:
               setPoint( 2, data->vectorData( ) );
               break;
            case PMNormal0ID:
               setNormal( 0, data->vectorData( ) );
               break;
            case PMNormal1ID:
               setNormal( 1, data->vectorData( ) );
               break;
            case PMNormal2ID:
               setNormal( 2, data->vectorData( ) );
               break;
            case PMSmoothID:
               setSmoothTriangle( data->boolData( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMTriangle::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );
}


bool PMTriangle::isDefault( )
{
   if( ( m_point[0] == point0Default )
       && ( m_point[1] == point1Default )
       && ( m_point[2] == point2Default ) )
      return true;
   return false;
}

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

   PMPointArray& points = m_pViewStructure->points( );

   points[0] = m_point[0];
   points[1] = m_point[1];
   points[2] = m_point[2];
}

PMViewStructure* PMTriangle::defaultViewStructure( ) const
{
   if( !s_pDefaultViewStructure )
   {
      s_pDefaultViewStructure = new PMViewStructure( 3, 3 );
      PMPointArray& points = s_pDefaultViewStructure->points( );
      PMLineArray& lines = s_pDefaultViewStructure->lines( );

      points[0] = point0Default;
      points[1] = point1Default;
      points[2] = point2Default;

      lines[0] = PMLine( 0, 1 );
      lines[1] = PMLine( 1, 2 );
      lines[2] = PMLine( 0, 2 );
   }
   return s_pDefaultViewStructure;
}

void PMTriangle::controlPoints( PMControlPointList& list )
{
   PM3DControlPoint* cp;
   
   cp = new PM3DControlPoint( m_point[0], PMPoint0ID,
                              i18n( "Point 1" ) );
   list.append( cp );
   if( m_smooth )
      list.append( new PMVectorControlPoint( cp, m_normal[0], PMNormal0ID,
                                             i18n( "Normal 1" ) ) );
   
   cp = new PM3DControlPoint( m_point[1], PMPoint1ID,
                              i18n( "Point 2" ) );
   list.append( cp );
   if( m_smooth )
      list.append( new PMVectorControlPoint( cp, m_normal[1], PMNormal1ID,
                                             i18n( "Normal 2" ) ) );
   
   cp = new PM3DControlPoint( m_point[2], PMPoint2ID,
                              i18n( "Point 3" ) );
   list.append( cp );
   if( m_smooth )
      list.append( new PMVectorControlPoint( cp, m_normal[2], PMNormal2ID,
                                             i18n( "Normal 3" ) ) );                                       
}

void PMTriangle::controlPointsChanged( PMControlPointList& list )
{
   PMControlPoint* p;
   PMVector p0, p1, p2;
   PMVector n0, n1, n2;
   double normalDirection = 1.0;
   PMVector triangleNormal;
   bool validNormal = false;
   double d;

   for( p = list.first( ); p; p = list.next( ) )
   {
      switch( p->id( ) )
      {
         case PMPoint0ID:
            p0 = ( ( PM3DControlPoint* ) p )->point( );
            break;
         case PMPoint1ID:
            p1 = ( ( PM3DControlPoint* ) p )->point( );
            break;
         case PMPoint2ID:
            p2 = ( ( PM3DControlPoint* ) p )->point( );
            break;
         case PMNormal0ID:
            n0 = ( ( PMVectorControlPoint* ) p )->vector( );
            break;
         case PMNormal1ID:
            n1 = ( ( PMVectorControlPoint* ) p )->vector( );
            break;
         case PMNormal2ID:
            n2 = ( ( PMVectorControlPoint* ) p )->vector( );
            break;
         default:
            break;
      }
   }
   
   if( m_smooth )
   {
      triangleNormal = PMVector::cross( m_point[1] - m_point[0],
                                        m_point[2] - m_point[0] );
      normalDirection = PMVector::dot( triangleNormal, m_normal[0] );
      if( approxZero( normalDirection  ) )
         normalDirection = PMVector::dot( triangleNormal, m_normal[1] );
      if( approxZero( normalDirection  ) )
         normalDirection = PMVector::dot( triangleNormal, m_normal[2] );
      if( normalDirection < 0 )
         triangleNormal = -triangleNormal;
      if( !approxZero( triangleNormal.abs( ) ) )
      {
         validNormal = true;
         triangleNormal /= triangleNormal.abs( );
      }
   }
   
   for( p = list.first( ); p; p = list.next( ) )
   {
      if( p->changed( ) )
      {
         switch( p->id( ) )
         {
            case PMPoint0ID:
               if( !( p0.approxEqual( p1 ) || p0.approxEqual( p2 ) ) )
                  setPoint( 0, p0 );
               else
                  ( ( PM3DControlPoint* ) p )->setPoint( m_point[0] );
               break;
            case PMPoint1ID:
               if( !( p1.approxEqual( p0 ) || p1.approxEqual( p2 ) ) )
                  setPoint( 1, p1 );
               else
                  ( ( PM3DControlPoint* ) p )->setPoint( m_point[1] );
               break;
            case PMPoint2ID:
               if( !( p2.approxEqual( p0 ) || p2.approxEqual( p1 ) ) )
                  setPoint( 2, p2 );
               else
                  ( ( PM3DControlPoint* ) p )->setPoint( m_point[2] );
               break;
               
            case PMNormal0ID:
               if( validNormal )
               {
                  d = PMVector::dot( triangleNormal, n0 );
                  if( d > 0 )
                     setNormal( 0, n0 );
                  else
                  {
                     setNormal( 0, n0 - ( d - 1e-5 ) * triangleNormal );
                     ( ( PMVectorControlPoint* ) p )->setVector( m_normal[0] );
                  }
               }
               else
                  ( ( PMVectorControlPoint* ) p )->setVector( m_normal[0] );
               break;
            case PMNormal1ID:
               if( validNormal )
               {
                  d = PMVector::dot( triangleNormal, n1 );
                  if( d > 0 )
                     setNormal( 1, n1 );
                  else
                  {
                     setNormal( 1, n1 - ( d - 1e-5 ) * triangleNormal );
                     ( ( PMVectorControlPoint* ) p )->setVector( m_normal[1] );
                  }
               }
               else
                  ( ( PMVectorControlPoint* ) p )->setVector( m_normal[1] );
               break;
            case PMNormal2ID:
               if( validNormal )
               {
                  d = PMVector::dot( triangleNormal, n2 );
                  if( d > 0 )
                     setNormal( 2, n2 );
                  else
                  {
                     setNormal( 2, n2 - ( d - 1e-5 ) * triangleNormal );
                     ( ( PMVectorControlPoint* ) p )->setVector( m_normal[2] );
                  }
               }
               else
                  ( ( PMVectorControlPoint* ) p )->setVector( m_normal[2] );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMTriangle::controlPointsChanged\n";
               break;
         }
      }
   }
}

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