/* This file is part of the KDE project
   Copyright (C) 2002, The Karbon Developers

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301, USA.
*/

#include <tqapplication.h>
#include <tqcstring.h>
#include <tqdatetime.h>	// For creation date/time.
#include <tqdom.h>
#include <tqfile.h>
#include <tqstring.h>
#include <tqvaluelist.h>

#include <kdebug.h>
#include <kgenericfactory.h>
#include <KoDocumentInfo.h>
#include <KoFilter.h>
#include <KoFilterChain.h>
#include <KoStore.h>

#include "epsexport.h"
#include "epsexportdlg.h"
#include "vcolor.h"
#include "vcomposite.h"
#include "vdashpattern.h"
#include "vdocument.h"
#include "vfill.h"
#include "vgroup.h"
#include "vlayer.h"
#include "vpath.h"
#include "vsegment.h"
#include "vselection.h"
#include "vstroke.h"
#include "vtext.h"
#include "vcomputeboundingbox.h"

// Define PostScript level1 operators.
static char l1_newpath		= 'N';
static char l1_closepath	= 'C';
static char l1_moveto		= 'm';
static char l1_curveto		= 'c';
static char l1_lineto		= 'l';
static char l1_stroke		= 's';
static char l1_fill			= 'f';
//static char l1_eofill		= 'e';
static char l1_setlinewidth	= 'w';
static char l1_setdash		= 'd';
static char l1_setrgbcolor	= 'r';
static char l1_gsave		= 'S';
static char l1_grestore		= 'R';


class EpsExportFactory : KGenericFactory<EpsExport, KoFilter>
{
public:
	EpsExportFactory( void )
		: KGenericFactory<EpsExport, KoFilter>( "karbonepsexport" )
	{}

protected:
	virtual void setupTranslations( void )
	{
		TDEGlobal::locale()->insertCatalogue( "kofficefilters" );
	}
};


K_EXPORT_COMPONENT_FACTORY( libkarbonepsexport, EpsExportFactory() )


EpsExport::EpsExport( KoFilter*, const char*, const TQStringList& )
	: KoFilter(), m_exportHidden( true )
{
}

KoFilter::ConversionStatus
EpsExport::convert( const TQCString& from, const TQCString& to )
{
	if ( to != "image/x-eps" || from != "application/x-karbon" )
	{
		return KoFilter::NotImplemented;
	}


	KoStoreDevice* storeIn = m_chain->storageFile( "root", KoStore::Read );

	if( !storeIn )
		return KoFilter::StupidError;


	KoFilter::ConversionStatus status = KoFilter::OK;

	// Ask questions about PS level etc.
	EpsExportDlg* dialog = new EpsExportDlg();

	TQApplication::setOverrideCursor( TQt::arrowCursor );

	if( dialog->exec() )
	{
		// Which PostScript level to support?
		m_psLevel = dialog->psLevel() + 1;
		m_exportHidden = dialog->exportHidden();

		TQFile fileOut( m_chain->outputFile() );
		if( !fileOut.open( IO_WriteOnly ) )
		{
			TQApplication::restoreOverrideCursor();
			delete( dialog );

			return KoFilter::StupidError;
		}

		TQDomDocument domIn;
		domIn.setContent( storeIn );
		TQDomElement docNode = domIn.documentElement();

		m_stream = new TQTextStream( &fileOut );

		// Load the document.
		VDocument doc;
		doc.load( docNode );

		// Process the document.
		doc.accept( *this );

		delete m_stream;
		fileOut.close();
	}
	else
	{
		// Dialog cancelled.
		status = KoFilter::UserCancelled;
	}

	TQApplication::restoreOverrideCursor();
	delete( dialog );

	return status;
}

void
EpsExport::visitVDocument( VDocument& document )
{
	// calculate the documents bounding box
	VComputeBoundingBox bbox( ! m_exportHidden );
	document.accept( bbox );
	const KoRect &rect = bbox.boundingRect();

	// Print a header.
	*m_stream <<
		"%!PS-Adobe-3.0 EPSF-3.0\n"
		"%%BoundingBox: " <<
		// Round down.
			tqRound( rect.left()   - 0.5 ) << " " <<
			tqRound( rect.top()    - 0.5 ) << " " <<
		// Round up.
			tqRound( rect.right()  + 0.5 ) << " " <<
			tqRound( rect.bottom() + 0.5 ) << "\n" <<
		"%%HiResBoundingBox: " <<
			rect.left()   << " " <<
			rect.top()    << " " <<
			rect.right()  << " " <<
			rect.bottom() << "\n"
		"%%Creator: Karbon14 EPS Exportfilter 0.5"
	<< endl;

	// Process document info.
	KoStoreDevice* storeIn;
	storeIn = m_chain->storageFile( "documentinfo.xml", KoStore::Read );

	if( storeIn )
	{
		TQDomDocument domIn;
		domIn.setContent( storeIn );

		KoDocumentInfo docInfo;
		docInfo.load( domIn );

		KoDocumentInfoAuthor* authorPage =
			static_cast<KoDocumentInfoAuthor*>( docInfo.page( "author" ) );

		// Get creation date/time = "now".
		TQDateTime now( TQDateTime::currentDateTime() );

		*m_stream <<
			"%%CreationDate: (" << now.toString( TQt::LocalDate ) << ")\n"
			"%%For: (" << authorPage->fullName() << ") (" << authorPage->company() << ")\n"
			"%%Title: (" << docInfo.title() << ")"
		<< endl;
	}


	// Print operator definitions.
	*m_stream <<
		"\n"
		"/" << l1_newpath		<< " {newpath} def\n"
		"/" << l1_closepath		<< " {closepath} def\n"
		"/" << l1_moveto		<< " {moveto} def\n"
		"/" << l1_curveto		<< " {curveto} def\n"
		"/" << l1_lineto		<< " {lineto} def\n"
		"/" << l1_stroke		<< " {stroke} def\n"
		"/" << l1_fill			<< " {fill} def\n"
		"/" << l1_setlinewidth	<< " {setlinewidth} def\n"
		"/" << l1_setdash		<< " {setdash} def\n"
		"/" << l1_setrgbcolor	<< " {setrgbcolor} def\n"
		"/" << l1_gsave			<< " {gsave} def\n"
		"/" << l1_grestore		<< " {grestore} def\n"
	<< endl;

	// Export layers.
	VVisitor::visitVDocument( document );

	// Finished.
	*m_stream <<
		"%%EOF"
	<< endl;
}

void
EpsExport::visitVGroup( VGroup& group )
{
	VObjectListIterator itr( group.objects() );

	for( ; itr.current(); ++itr )
	{
		// do not export hidden child objects
		if( ! m_exportHidden && ! isVisible( itr.current() ) )
			continue;
		itr.current()->accept( *this );
	}
}

void
EpsExport::visitVLayer( VLayer& layer )
{
	// do not export hidden layers
	if( ! m_exportHidden && ! isVisible( &layer ) )
		return;

	VObjectListIterator itr( layer.objects() );

	for( ; itr.current(); ++itr )
	{
		// do not export hidden objects
		if( ! m_exportHidden && ! isVisible( itr.current() ) )
			continue;
		itr.current()->accept( *this );
	}
}

void
EpsExport::visitVPath( VPath& composite )
{
	*m_stream << l1_newpath << "\n";

	VVisitor::visitVPath( composite );

	getFill( *composite.fill() );
	getStroke( *composite.stroke() );

	*m_stream << endl;
}

void
EpsExport::visitVSubpath( VSubpath& path )
{
	// Export segments.
	VSubpathIterator itr( path );

	for( ; itr.current(); ++itr )
	{
		VSegment *segment = itr.current();
		if ( segment->isCurve() ) {
		    *m_stream <<
			itr.current()->point( 0 ).x() << " " <<
			itr.current()->point( 0 ).y() << " " <<
			itr.current()->point( 1 ).x() << " " <<
			itr.current()->point( 1 ).y() << " " <<
			itr.current()->knot().x() << " " <<
			itr.current()->knot().y() << " " <<
			l1_curveto << "\n";
		} else if ( segment->isLine() ) {
		    *m_stream <<
			itr.current()->knot().x() << " " <<
			itr.current()->knot().y() << " " <<
			l1_lineto << "\n";
		} else if ( segment->isBegin() ) {
		    *m_stream <<
			itr.current()->knot().x() << " " <<
			itr.current()->knot().y() << " " <<
			l1_moveto << "\n";
		}
	}

	if( path.isClosed() )
		*m_stream << l1_closepath << "\n";
}

void
EpsExport::visitVText( VText& text )
{
	// TODO: currently we only export the glyphs if available.

	// Export the glyphs (= VPaths).
	VPathListIterator itr( text.glyphs() );

	for( ; itr.current(); ++itr )
	{
		visit( *itr.current() );
	}
}

void
EpsExport::getStroke( const VStroke& stroke )
{
	// Solid stroke.
	if( stroke.type() == VStroke::solid )
	{
		// Dash pattern.
		*m_stream << "[";

		const TQValueList<float>&
			array( stroke.dashPattern().array() );

		TQValueListConstIterator<float> itr = array.begin();
		for( ; itr != array.end(); ++itr )
			 *m_stream << *itr << " ";

		*m_stream <<
			"] " << stroke.dashPattern().offset() <<
			" "  << l1_setdash << " ";

		getColor( stroke.color() );

		// "setlinewidth", "stroke".
		*m_stream <<
			" " << stroke.lineWidth() <<
			" " << l1_setlinewidth <<
			" " << l1_stroke << "\n";
	}
	else if( stroke.type() == VStroke::grad )
	{
		if( m_psLevel == 3 )
		{

		}
	}
}

void
EpsExport::getFill( const VFill& fill )
{
	// Solid fill.
	if( fill.type() == VFill::solid )
	{
		// "gsave".
		*m_stream << l1_gsave << " ";

		// "setrgbcolor".
		getColor( fill.color() );

		// "fill", "grestore".
		*m_stream << " " << l1_fill << " " << l1_grestore << "\n";
	}
	// Gradient.
	else if( fill.type() == VFill::grad )
	{
		if( m_psLevel == 3 )
		{
			// "gsave".
			*m_stream << l1_gsave << " ";

			VGradient grad = fill.gradient();
			TQPtrVector<VColorStop> ramp = grad.colorStops();
			if( ramp.size() < 2 )
			{
				if( ramp.size() == 1 )
					getColor( ramp[0]->color );
			}
			if( ramp.size() > 2 || ramp.size() == 2 && ramp[0]->midPoint != 0.5 )
			{
				// Gradient with more than two colors or asymmetrical midpoint.
				for( uint i = 1;i < ramp.size();i++ )
				{
					char name[15];
					sprintf( name, "Function%d", 2 * i - 1 );

					VColorStop stop1 = *ramp[i - 1];
					VColorStop stop2 = *ramp[i];
					VColor mid;
					mid.set( 0.5 * ( stop1.color[0] + stop2.color[0] ), 0.5 * ( stop1.color[1] + stop2.color[1] ), 0.5 * ( stop1.color[2] + stop2.color[2] ) );
					*m_stream << "/" << name << " 7 dict def " << name << " begin\n" << "\t/FunctionType 2 def\n"
								<< "\t/Domain [ 0 1 ] def\n" << "\t/C0 [ " << stop1.color[0] << " " << stop1.color[1] << " "
								<< stop1.color[2] << " ] def\n" << "\t/C1 [ " << mid[0] << " " << mid[1] << " "
								<< mid[2] << " ] def\n" << "\t/N 1 def\n" << "end\n";

					sprintf( name, "Function%d", 2 * i );

					*m_stream << "/" << name << " 7 dict def " << name << " begin\n" << "\t/FunctionType 2 def\n" << "\t/Domain [ 0 1 ] def\n"
								<< "\t/C0 [ " << mid[0] << " " << mid[1] << " " << mid[2] << " ] def\n" << "\t/C1 [ " << stop2.color[0] << " "
								<< stop2.color[1] << " " << stop2.color[2] << " ] def\n" << "\t/N 1 def\n" << "end\n";
				}
			}
			if( grad.type() == VGradient::linear )
				*m_stream << "clip newpath\n" << "/DeviceRGB setcolorspace\n" << "<<\n" << "\t/ShadingType 2\n" << "\t/ColorSpace /DeviceRGB\n" << "\t/Coords [ "
							<< grad.origin().x() << " " << grad.origin().y() << " " << grad.vector().x() << " " << grad.vector().y() << " ]\n\t/Extend[ true true ]\n" << "\t/Function <<\n";
			else if( grad.type() == VGradient::radial )
			{
				double r = sqrt( pow( grad.vector().x() - grad.origin().x(), 2 ) + pow( grad.vector().y() - grad.origin().y(), 2 ) );
				*m_stream << "clip newpath\n" << "/DeviceRGB setcolorspace\n" << "<<\n" << "\t/ShadingType 3\n" << "\t/ColorSpace /DeviceRGB\n" << "\t/Coords [ "
			                << grad.origin().x() << " " << grad.origin().y() << " 0.0 " << grad.origin().x() << " " << grad.origin().y()
			                << " " << r << "]\n\t\t/Extend [ false true ]\n" << "\t/Function <<\n";
			}
			if( ramp.size() == 2 && ramp[0]->midPoint == 0.5 )
			{
				// Gradient with only two colors and symmetrical midpoint.
				VColorStop stop1 = *ramp[0];
				VColorStop stop2 = *ramp[1];
				*m_stream << "\t\t/FunctionType 2\n" << "\t\t/C0 [ " << stop1.color[0] << " " << stop1.color[1] << " " << stop1.color[2]
						  << " ]\n" << "\t\t/C1 [ " << stop2.color[0] << " " << stop2.color[1] << " " << stop2.color[2] << " ]\n" << "\t\t/N 1\n";
			}
			else if( ramp.size() > 2 || ramp.size() == 2 && ramp[0]->midPoint != 0.5 )
			{
				// Gradient with more than two colors or asymmetrical midpoint.
				*m_stream << "\t\t/FunctionType 3\n" << "\t\t/Functions [ ";
				for( uint i = 1; i < ( 2 * ramp.size() - 1 );i++ )
					*m_stream << "Function" << i << " ";
				*m_stream << "]\n" << "\t\t/Bounds [";
				for( uint i = 0;i < ramp.size() - 1;i++ )
				{
					VColorStop stop = *ramp[i];
					if( i > 0 )
						*m_stream << " " << stop.rampPoint;
					*m_stream << " " << ( stop.rampPoint + ( ramp[i + 1]->rampPoint - stop.rampPoint ) * stop.midPoint );
				}
				*m_stream << " ]\n" << "\t\t/Encode [ ";
				for( uint i = 0;i < 2 * ramp.size() - 2;i++ )
					*m_stream << "0 1 ";
				*m_stream << "]\n";
			}
			*m_stream << "\t\t/Domain [ " << ramp[0]->rampPoint << " "
					<< ramp[ramp.size() - 1]->rampPoint << " ]\n" << "\t>>\n" << ">>\n";
			// "shfill", "grestore".
			*m_stream << " shfill " << l1_grestore << "\n";
		}
	}
}

void
EpsExport::getColor( const VColor& color )
{
	VColor copy( color );
	copy.setColorSpace( VColor::rgb );

	*m_stream <<
		copy[0] << " " <<
		copy[1] << " " <<
		copy[2] << " " << l1_setrgbcolor;
}

bool
EpsExport::isVisible( const VObject* object ) const
{
	return object->state() != VObject::hidden && object->state() != VObject::hidden_locked;
}

#include "epsexport.moc"
