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

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

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

#include <klocale.h>

PMViewStructure* PMCamera::s_pDefaultViewStructure = 0;

const PMVector locationDefault = PMVector( 0.0, 0.0, 0.0 );
const PMVector lookAtDefault = PMVector( 0.0, 0.0, 1.0 );
const PMVector directionDefault = PMVector( 0.0, 0.0, 1.0 );
const PMVector upDefault = PMVector( 0.0, 1.0, 0.0 );
const PMVector rightDefault = PMVector( 4.0/3.0, 0.0, 0.0 );
const PMVector skyDefault = PMVector( 0.0, 1.0, 0.0 );
//const double aspectDefault = 1.0;
const bool angleEnabledDefault = false;
const double angleDefault = 90.0;
const PMCamera::CameraType cameraTypeDefault = PMCamera::Perspective;
const int cylinderTypeDefault = 1;
const bool focalBlurDefault = false;
const double apertureDefault = 0.4;
const int blurSamplesDefault = 10;
const PMVector focalPointDefault = PMVector( 0.0, 0.0, 0.0 );
const double confidenceDefault = 0.9;
const double varianceDefault = 0.008;

PMCamera::PMCamera( )
      : Base( )
{
   m_location = locationDefault;
   m_lookAt = lookAtDefault;
   m_direction = directionDefault;
   
   m_up = upDefault;
   m_right = rightDefault;
   
   m_sky = skyDefault;
   m_angle = angleDefault;

   m_cameraType = cameraTypeDefault;
   m_cylinderType = cylinderTypeDefault;

   m_focalBlurEnabled = focalBlurDefault;
   m_aperture = apertureDefault;
   m_blurSamples = blurSamplesDefault;
   m_focalPoint = focalPointDefault;
   m_confidence = confidenceDefault;
   m_variance = varianceDefault;
}

PMCamera::~PMCamera( )
{
}

QString PMCamera::description( ) const
{
   return i18n( "camera" );
}

void PMCamera::serialize( PMOutputDevice& dev ) const
{
   dev.objectBegin( "camera" );
   QString str;

   serializeName( dev );

   if( m_cameraType == Cylinder )
   {
      str.setNum( m_cylinderType );
      dev.writeLine( "cylinder " + str );
   }
   else
      dev.writeLine( cameraTypeToString( m_cameraType ) );
   
   dev.writeLine( "location " + m_location.serialize( ) );
   dev.writeLine( "sky " + m_sky.serialize( ) );
   dev.writeLine( "direction " + m_direction.serialize( ) );
   dev.writeLine( "right " + m_right.serialize( ) );
   dev.writeLine( "up " + m_up.serialize( ) );
   dev.writeLine( "look_at " + m_lookAt.serialize( ) );
      
   if( ( m_cameraType != Orthographic ) && ( m_cameraType != Omnimax )
       && ( m_cameraType != Panoramic ) && m_angleEnabled )
   {
      str.setNum( m_angle );
      dev.writeLine( "angle " + str );
   }
   if( m_focalBlurEnabled && ( m_cameraType == Perspective ) )
   {
      str.setNum( m_aperture );
      dev.writeLine( "aperture " + str );
      str.setNum( m_blurSamples );
      dev.writeLine( "blur_samples " + str );
      dev.writeLine( "focal_point " + m_focalPoint.serialize( ) );
      str.setNum( m_confidence );
      dev.writeLine( "confidence " + str );
      str.setNum( m_variance );
      dev.writeLine( "variance " + str );
   }
   
   Base::serialize( dev );
   dev.objectEnd( );
}

void PMCamera::serialize( QDomElement& e, QDomDocument& doc ) const
{
   e.setAttribute( "camera_type", cameraTypeToString( m_cameraType ) );
   e.setAttribute( "cylinder_type", m_cylinderType );
   e.setAttribute( "location", m_location.serializeXML( ) );
   e.setAttribute( "sky", m_sky.serializeXML( ) );
   e.setAttribute( "direction", m_direction.serializeXML( ) );
   e.setAttribute( "right", m_right.serializeXML( ) );
   e.setAttribute( "up", m_up.serializeXML( ) );
   e.setAttribute( "look_at", m_lookAt.serializeXML( ) );
   e.setAttribute( "angle_enabled", m_angleEnabled );
   e.setAttribute( "angle", m_angle );
   e.setAttribute( "focal_blur", m_focalBlurEnabled );
   e.setAttribute( "aperture", m_aperture );
   e.setAttribute( "blur_samples", m_blurSamples );
   e.setAttribute( "focal_point", m_focalPoint.serializeXML( ) );
   e.setAttribute( "confidence", m_confidence );
   e.setAttribute( "variance", m_variance );
   
   Base::serialize( e, doc );
}

void PMCamera::readAttributes( const PMXMLHelper& h )
{
   m_cameraType = stringToCameraType( h.stringAttribute( "camera_type",
                                                         "perspective" ) );
   m_cylinderType = h.intAttribute( "cylinder_type", cylinderTypeDefault );
   m_location = h.vectorAttribute( "location", locationDefault );
   m_sky = h.vectorAttribute( "sky", skyDefault );
   m_direction = h.vectorAttribute( "direction", directionDefault );
   m_right = h.vectorAttribute( "right", rightDefault );
   m_up = h.vectorAttribute( "up", upDefault );
   m_lookAt = h.vectorAttribute( "look_at", lookAtDefault );
   m_angleEnabled = h.boolAttribute( "angle_enabled", angleEnabledDefault );
   m_angle = h.doubleAttribute( "angle", angleDefault );
   m_focalBlurEnabled = h.boolAttribute( "focal_blur", focalBlurDefault );
   m_aperture = h.doubleAttribute( "aperture", apertureDefault );
   m_blurSamples = h.intAttribute( "blur_samples", blurSamplesDefault );
   m_focalPoint = h.vectorAttribute( "focal_point", focalPointDefault );
   m_confidence = h.doubleAttribute( "confidence", confidenceDefault );
   m_variance = h.doubleAttribute( "variance", varianceDefault );
   Base::readAttributes( h );
}

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


bool PMCamera::canInsert( PMObjectType t, const PMObject*,
                          const PMObjectList* ) const
{
   switch( t )
   {
      case PMTComment:
      case PMTRaw:
      case PMTTranslate:
      case PMTRotate:
      case PMTScale:
      case PMTMatrix:
      case PMTNormal:
         return true;
         break;
      default:
         break;
   }
   return false;
}


void PMCamera::setLocation( const PMVector& p )
{
   if( p != m_location )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMLocationID, m_location );
      m_location = p;
      m_location.resize( 3 );
      setViewStructureChanged( );
   }
}

void PMCamera::setLookAt( const PMVector& p )
{
   if( p != m_lookAt )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMLookAtID, m_lookAt );
      m_lookAt = p;
      m_lookAt.resize( 3 );
      setViewStructureChanged( );
   }
}

void PMCamera::setUp( const PMVector& v )
{
   if( v != m_up )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMUpID, m_up );
      m_up = v;
      m_up.resize( 3 );
      setViewStructureChanged( );
   }
}

void PMCamera::setRight( const PMVector& v )
{
   if( v != m_right )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMRightID, m_right );
      m_right = v;
      m_right.resize( 3 );
      setViewStructureChanged( );
   }
}

void PMCamera::setDirection( const PMVector& v )
{
   if( v != m_direction )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMDirectionID, m_direction );
      m_direction = v;
      m_direction.resize( 3 );
      setViewStructureChanged( );
   }
}

void PMCamera::setSky( const PMVector& v )
{
   if( v != m_sky )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMSkyID, m_sky );
      m_sky = v;
      m_sky.resize( 3 );
      setViewStructureChanged( );
   }
}

double PMCamera::aspect( ) const
{
   double d = m_up.abs( );
   if( approxZero( d ) )
      return 1.0;
   return m_right.abs( ) / d;
}

void PMCamera::enableAngle( bool yes )
{
   if( yes != m_angleEnabled )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMAngleEnabledID, m_angleEnabled );
      m_angleEnabled = yes;
      setViewStructureChanged( );
   }
}

void PMCamera::setAngle( double a )
{
   if( a != m_angle )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMAngleID, m_angle );
      m_angle = a;
      setViewStructureChanged( );
   }   
}

void PMCamera::setCameraType( PMCamera::CameraType t )
{
   if( t != m_cameraType )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMCameraTypeID, m_cameraType );
      m_cameraType = t;
      setViewStructureChanged( );
   }
}

void PMCamera::setCylinderType( int t )
{
   if( ( t < 1 ) || ( t > 4 ) )
      kdError( PMArea ) << "Invalid type in PMCylinder::setCylinderType\n";
   else if( t != m_cylinderType )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMCylinderTypeID, m_cylinderType );
      m_cylinderType = t;
      setViewStructureChanged( );
   }
}

void PMCamera::enableFocalBlur( bool yes )
{
   if( yes != m_focalBlurEnabled )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMFocalBlurID, m_focalBlurEnabled );
      m_focalBlurEnabled = yes;
   }
}

void PMCamera::setAperture( double a )
{
   if( a < 0 )
      kdError( PMArea ) << "Aperture < 0 in PMCylinder::setAperture\n";
   else if( a != m_aperture )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMApertureID, m_aperture );
      m_aperture = a;
   }
}

void PMCamera::setBlurSamples( int s )
{
   if( s < 0 )
      kdError( PMArea ) << "Samples < 0 in PMCylinder::setBlutSamples\n";
   else if( s != m_blurSamples )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMBlurSamplesID, m_blurSamples );
      m_blurSamples = s;
   }
}

void PMCamera::setFocalPoint( const PMVector& v )
{
   if( v != m_focalPoint )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMFocalPointID, m_focalPoint );
      m_focalPoint = v;
   }
}

void PMCamera::setConfidence( double c )
{
   if( ( c < 0.0 ) || ( c > 1.0 ) )
      kdError( PMArea ) << "Confidence not in [0.0 1.0] in PMCylinder::setConfidence\n";
   else if( c != m_confidence )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMConfidenceID, m_confidence );
      m_confidence = c;
   }
}

void PMCamera::setVariance( double v )
{
   if( v < 0 )
      kdError( PMArea ) << "Variance < 0 in PMCylinder::setVariance\n";
   else if( v != m_variance )
   {
      if( m_pMemento )
         m_pMemento->addData( PMTCamera, PMVarianceID, m_variance );
      m_variance = v;
   }
}

PMDialogEditBase* PMCamera::editWidget( QWidget* parent ) const
{
   return new PMCameraEdit( parent );
}

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

   for( ; it.current( ); ++it )
   {
      data = it.current( );
      if( data->objectType( ) == PMTCamera )
      {
         switch( data->valueID( ) )
         {
            case PMLocationID:
               setLocation( data->vectorData( ) );
               break;
            case PMLookAtID:
               setLookAt( data->vectorData( ) );
               break;
            case PMUpID:
               setUp( data->vectorData( ) );
               break;
            case PMRightID:
               setRight( data->vectorData( ) );
               break;
            case PMDirectionID:
               setDirection( data->vectorData( ) );
               break;
            case PMSkyID:
               setSky( data->vectorData( ) );
               break;
            case PMAngleEnabledID:
               enableAngle( data->boolData( ) );
               break;
            case PMAngleID:
               setAngle( data->doubleData( ) );
               break;
            case PMCameraTypeID:
               setCameraType( ( CameraType ) data->intData( ) );
               break;
            case PMCylinderTypeID:
               setCylinderType( data->intData( ) );
               break;
            case PMFocalBlurID:
               enableFocalBlur( data->boolData( ) );
               break;
            case PMBlurSamplesID:
               setBlurSamples( data->intData( ) );
               break;
            case PMFocalPointID:
               setFocalPoint( data->vectorData( ) );
               break;
            case PMConfidenceID:
               setConfidence( data->doubleData( ) );
               break;
            case PMVarianceID:
               setVariance( data->doubleData( ) );
               break;
            case PMApertureID:
               setAperture( data->doubleData( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMCamera::restoreMemento\n";
               break;
         }
      }
   }
   Base::restoreMemento( s );
}

void PMCamera::createViewStructure( )
{
   PMVector newUp, newRight, newDirection, tmp;
   double upLen, rightLen;
   
   if( !m_pViewStructure )
   {
      m_pViewStructure = new PMViewStructure( defaultViewStructure( ) );
      m_pViewStructure->points( ).detach( );
   }

   PMPointArray& points = m_pViewStructure->points( );
   calculateLookAtAngle( newRight, newUp, newDirection );

   points[0] = m_lookAt;
   points[1] = m_location;

   upLen = newUp.abs( ); 
   rightLen = newRight.abs( );
   if( rightLen > upLen )
   {
      newUp /= rightLen;
      newRight /= rightLen;
      newDirection /= rightLen;
   }
   else
   {
      newUp /= upLen;
      newRight /= upLen;
      newDirection /= upLen;
   }
   
   newRight /= 2.0;
   newUp /= 2.0;
   tmp = m_location + newDirection;
   points[2] = tmp - newRight + newUp;
   points[3] = tmp - newRight - newUp;
   points[4] = tmp + newRight - newUp;
   points[5] = tmp + newRight + newUp;
//   points[6] = m_location + m_sky;
}

PMViewStructure* PMCamera::defaultViewStructure( ) const
{
   if( !s_pDefaultViewStructure )
   {
      s_pDefaultViewStructure = new PMViewStructure( 6, 9 );
      PMLineArray& lines = s_pDefaultViewStructure->lines( );

      lines[0] = PMLine( 0, 1 );
      lines[1] = PMLine( 1, 2 );
      lines[2] = PMLine( 1, 3 );
      lines[3] = PMLine( 1, 4 );
      lines[4] = PMLine( 1, 5 );
      lines[5] = PMLine( 2, 3 );
      lines[6] = PMLine( 2, 5 );
      lines[7] = PMLine( 3, 4 );
      lines[8] = PMLine( 4, 5 );
//      lines[9] = PMLine( 1, 6 );
   }
   return s_pDefaultViewStructure;
}

void PMCamera::controlPoints( PMControlPointList& list )
{
   PMControlPoint* p;
   p = new PM3DControlPoint( m_location, PMLocationID, i18n( "Location" ) );
   list.append( p );
   list.append( new PM3DControlPoint( m_lookAt, PMLookAtID, i18n( "Look at" ) ) );
}

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

   for( p = list.first( ); p; p = list.next( ) )
   {
      if( p->changed( ) )
      {
         switch( p->id( ) )
         {
            case PMLocationID:
               setLocation( ( ( PM3DControlPoint* ) p )->point( ) );
               break;
            case PMLookAtID:
               setLookAt( ( ( PM3DControlPoint* ) p )->point( ) );
               break;
            default:
               kdError( PMArea ) << "Wrong ID in PMCamera::controlPointsChanged\n";
               break;
         }
      }
   }
}

void PMCamera::calculateLookAtAngle( PMVector& right, PMVector& up, PMVector& direction )
{
   PMVector tmpVector;
   double directionLen, upLen, rightLen, handedness, angle;
   
   angle = m_angle;
   if( m_cameraType == Perspective )
   {
      if( angle < 0.0 )
         angle = angleDefault;
      if( angle >= 180.0 )
         angle = angleDefault;
   }
   else
      angle = 90.0;
   
   directionLen = m_direction.abs( );
   upLen = m_up.abs( );
   rightLen = m_right.abs( );

   // for safety: if vector is null vector, use the default one
   if( approxZero( directionLen ) )
   {
      direction = directionDefault;
      directionLen = direction.abs( );
   }
   else
      direction = m_direction;
   
   if( approxZero( upLen ) )
   {
      up = upDefault;
      upLen = up.abs( );
   }
   else
      up = m_up;

   if( approxZero( rightLen ) )
   {
      right = rightDefault;
      rightLen = right.abs( );
   }
   else
      right = m_right;

   if( m_angleEnabled )
   {
      // calculate angle
      direction /= directionLen;
      directionLen = 0.5 * rightLen / tan( angle * M_PI / 360.0 );
      direction *= directionLen;
   }
   
   // calculate handedness
   tmpVector = PMVector::cross( up, direction );
   handedness = PMVector::dot( tmpVector, right );

   direction = m_lookAt - m_location;
   if( approxZero( direction.abs( ) ) )
   {
      // Camera location and look_at point are the same
      direction = directionDefault;
   }

   // normalize new direction
   direction /= direction.abs( );
       
   tmpVector = right;
   right = PMVector::cross( m_sky, direction );

   if( approxZero( right.abs( ) ) )
      right = tmpVector;

   right /= right.abs( );
   up = PMVector::cross( direction, right );
   direction *= directionLen;

   if( handedness > 0.0 )
      right *= rightLen;
   else
      right *= -rightLen;
   up *= upLen;
}

QString PMCamera::cameraTypeToString( PMCamera::CameraType t )
{
   QString str( "perspective" );
   switch( t )
   {
      case Perspective:
         break;
      case Orthographic:
         str = "orthographic";
         break;
      case FishEye:
         str = "fisheye";
         break;
      case UltraWideAngle:
         str = "ultra_wide_angle";
         break;
      case Omnimax:
         str = "omnimax";
         break;
      case Panoramic:
         str = "panoramic";
         break;
      case Cylinder:
         str = "cylinder";
         break;
   }
   return str;
}

PMCamera::CameraType PMCamera::stringToCameraType( const QString& str )
{
   CameraType t = Perspective;
   
   if( str == "perspective" )
      t = Perspective;
   else if( str == "orthographic" )
      t = Orthographic;
   else if( str == "fisheye" )
      t = FishEye;
   else if( str == "ultra_wide_angle" )
      t = UltraWideAngle;
   else if( str == "omnimax" )
      t = Omnimax;
   else if( str == "panoramic" )
      t = Panoramic;
   else if( str == "cylinder" )
      t = Cylinder;
   else
      kdDebug( PMArea ) << "Unknown camera type\n";

   return t;
}

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