/* This file is part of the KDE project
   Copyright (C) 2001, 2002, 2003 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 <tqdom.h>
#include <tqfileinfo.h>
#include <tqpainter.h>
#include <tqpaintdevicemetrics.h>

#include <tdeconfig.h>
#include <kdebug.h>
#include <tdelocale.h>
#include <tdetempfile.h>
#include <KoTemplateChooseDia.h>
#include <KoStoreDevice.h>
#include <KoOasisStyles.h>
#include <KoOasisLoadingContext.h>
#include <KoXmlWriter.h>
#include <KoXmlNS.h>
#include <KoDom.h>
#include <KoOasisSettings.h>
#include <KoMainWindow.h>

#include "karbon_factory.h"
#include "karbon_part.h"
#include "karbon_part_iface.h"
#include "karbon_view.h"
#include "vcommand.h"
#include "vglobal.h"
#include "vpainter.h"
#include "vpainterfactory.h"
#include "vselection.h"
#include "vcanvas.h"
#include "vlayer.h"
#include "vdocumentdocker.h"
#include "vtoolcontroller.h"
#include "KoApplication.h"
#include "vtool.h"
#include "commands/vtransformcmd.h"

// Make sure an appropriate DTD is available in www/koffice/DTD if changing this value
// static const char * CURRENT_DTD_VERSION = "1.2";

KarbonPart::KarbonPart( TQWidget* parentWidget, const char* widgetName,
						TQObject* parent, const char* name, bool singleViewMode )
		: KoDocument( parentWidget, widgetName, parent, name, singleViewMode )
{
	setInstance( KarbonFactory::instance(), false );
	setTemplateType( "karbon_template" );
	m_bShowStatusBar = true;
	dcop = 0L;

	m_commandHistory = new VCommandHistory( this );
	connect( m_commandHistory, TQT_SIGNAL( documentRestored() ), this, TQT_SLOT( slotDocumentRestored() ) );
	connect( m_commandHistory, TQT_SIGNAL( commandExecuted( VCommand * ) ), this, TQT_SLOT( slotCommandExecuted( VCommand * ) ) );

	initConfig();

	m_merge = false;

	m_maxRecentFiles = 10;

	//if( name )
        dcopObject();

	// set as default paper
	m_pageLayout.format = KoPageFormat::defaultFormat();
	m_pageLayout.orientation = PG_PORTRAIT;
	m_pageLayout.ptWidth = MM_TO_POINT( KoPageFormat::width( m_pageLayout.format, m_pageLayout.orientation ) );
	m_pageLayout.ptHeight = MM_TO_POINT( KoPageFormat::height( m_pageLayout.format, m_pageLayout.orientation ) );
	m_doc.setWidth( m_pageLayout.ptWidth );
	m_doc.setHeight( m_pageLayout.ptHeight );
	// enable selection drawing
	m_doc.selection()->showHandle();
	m_doc.selection()->setSelectObjects();
	m_doc.selection()->setState( VObject::selected );
	m_doc.selection()->selectNodes();
}

KarbonPart::~KarbonPart()
{
	// delete the command-history:
	delete m_commandHistory;
	delete dcop;
}

DCOPObject* KarbonPart::dcopObject()
{
	if( !dcop )
		dcop = new KarbonPartIface( this );

	return dcop;
}

void
KarbonPart::setPageLayout( KoPageLayout& layout, KoUnit::Unit _unit )
{
	m_pageLayout = layout;
	m_doc.setUnit( _unit );
	m_doc.setWidth( m_pageLayout.ptWidth );
	m_doc.setHeight( m_pageLayout.ptHeight );
}

bool
KarbonPart::initDoc(InitDocFlags flags, TQWidget* parentWidget)
{
        if (flags==KoDocument::InitDocEmpty)
        {
                return true;
        }
	TQString file;
	KoTemplateChooseDia::ReturnType result;

        KoTemplateChooseDia::DialogType dlgtype;
        if (flags != KoDocument::InitDocFileNew)
            dlgtype = KoTemplateChooseDia::Everything;
        else
            dlgtype = KoTemplateChooseDia::OnlyTemplates;

	result = KoTemplateChooseDia::choose( KarbonFactory::instance(), file, dlgtype, "karbon_template", parentWidget );

	if( result == KoTemplateChooseDia::Template )
	{
		resetURL();
		bool ok = loadNativeFormat( file );
		if ( !ok )
			showLoadingErrorDialog();
		setEmpty();
		return ok;
	}
	else if( result == KoTemplateChooseDia::Empty )
	{
		return true;
	}
	else if( result == KoTemplateChooseDia::File )
	{
		KURL url( file );
		return openURL( url );
	}

	return false;
}

KoView*
KarbonPart::createViewInstance( TQWidget* parent, const char* name )
{
	KarbonView *result = new KarbonView( this, parent, name );
	return result;
}

void
KarbonPart::removeView( KoView *view )
{
	kdDebug(38000) << "KarbonPart::removeView" << endl;
	KoDocument::removeView( view );
}

double getAttribute(TQDomElement &element, const char *attributeName, double defaultValue)
{
	TQString value;
	if ( ( value = element.attribute( attributeName ) ) != TQString() )
		return value.toDouble();
	else
		return defaultValue;
}

int getAttribute(TQDomElement &element, const char *attributeName, int defaultValue)
{
	TQString value;
	if ( ( value = element.attribute( attributeName ) ) != TQString() )
		return value.toInt();
	else
		return defaultValue;
}

bool
KarbonPart::loadXML( TQIODevice*, const TQDomDocument& document )
{
	bool success = false;

	TQDomElement doc = document.documentElement();

	if( m_merge )
	{
		m_doc.loadDocumentContent( doc );
		return true;
	}

	success = m_doc.loadXML( doc );

	//m_pageLayout = KoPageLayout::standardLayout();

	// <PAPER>
	TQDomElement paper = doc.namedItem( "PAPER" ).toElement();
	if ( !paper.isNull() )
	{
		m_pageLayout.format = static_cast<KoFormat>( getAttribute( paper, "format", 0 ) );
		m_pageLayout.orientation = static_cast<KoOrientation>( getAttribute( paper, "orientation", 0 ) );

		if( m_pageLayout.format == PG_CUSTOM )
		{
			m_pageLayout.ptWidth	= m_doc.width();
			m_pageLayout.ptHeight	= m_doc.height();
		}
		else
		{
			m_pageLayout.ptWidth = getAttribute( paper, "width", 0.0 );
			m_pageLayout.ptHeight = getAttribute( paper, "height", 0.0 );
		}
	}
	else
	{
		m_pageLayout.ptWidth = getAttribute( doc, "width", 595.277);
		m_pageLayout.ptHeight = getAttribute( doc, "height", 841.891 );
	}

	kdDebug() << " ptWidth=" << m_pageLayout.ptWidth << endl;
	kdDebug() << " ptHeight=" << m_pageLayout.ptHeight << endl;
        TQDomElement borders = paper.namedItem( "PAPERBORDERS" ).toElement();
        if( !borders.isNull() )
       	{
            if( borders.hasAttribute( "ptLeft" ) )
                m_pageLayout.ptLeft = borders.attribute( "ptLeft" ).toDouble();
            if( borders.hasAttribute( "ptTop" ) )
                m_pageLayout.ptTop = borders.attribute( "ptTop" ).toDouble();
            if( borders.hasAttribute( "ptRight" ) )
                m_pageLayout.ptRight = borders.attribute( "ptRight" ).toDouble();
            if( borders.hasAttribute( "ptBottom" ) )
                m_pageLayout.ptBottom = borders.attribute( "ptBottom" ).toDouble();
	}

	setUnit( m_doc.unit() );

	return success;
}

TQDomDocument
KarbonPart::saveXML()
{
	TQDomDocument doc = m_doc.saveXML();
	TQDomElement me = doc.documentElement();
	TQDomElement paper = doc.createElement( "PAPER" );
	me.appendChild( paper );
	paper.setAttribute( "format", static_cast<int>( m_pageLayout.format ) );
	paper.setAttribute( "pages", pageCount() );
	paper.setAttribute( "width", m_pageLayout.ptWidth );
	paper.setAttribute( "height", m_pageLayout.ptHeight );
	paper.setAttribute( "orientation", static_cast<int>( m_pageLayout.orientation ) );

	TQDomElement paperBorders = doc.createElement( "PAPERBORDERS" );
	paperBorders.setAttribute( "ptLeft", m_pageLayout.ptLeft );
	paperBorders.setAttribute( "ptTop", m_pageLayout.ptTop );
	paperBorders.setAttribute( "ptRight", m_pageLayout.ptRight );
	paperBorders.setAttribute( "ptBottom", m_pageLayout.ptBottom );
	paper.appendChild(paperBorders);

	return doc;
}

bool
KarbonPart::loadOasis( const TQDomDocument &doc, KoOasisStyles &styles, const TQDomDocument &settings, KoStore *store )
{
	kdDebug(38000) << "Start loading OASIS document..." << doc.toString() << endl;

	TQDomElement contents = doc.documentElement();
	kdDebug(38000) << "Start loading OASIS document..." << contents.text() << endl;
	kdDebug(38000) << "Start loading OASIS contents..." << contents.lastChild().localName() << endl;
	kdDebug(38000) << "Start loading OASIS contents..." << contents.lastChild().namespaceURI() << endl;
	kdDebug(38000) << "Start loading OASIS contents..." << contents.lastChild().isElement() << endl;
	TQDomElement body( KoDom::namedItemNS( contents, KoXmlNS::office, "body" ) );
	kdDebug(38000) << "Start loading OASIS document..." << body.text() << endl;
	if( body.isNull() )
	{
		kdDebug(38000) << "No office:body found!" << endl;
		setErrorMessage( i18n( "Invalid OASIS document. No office:body tag found." ) );
		return false;
	}

	body = KoDom::namedItemNS( body, KoXmlNS::office, "drawing");
	if(body.isNull())
	{
		kdDebug(38000) << "No office:drawing found!" << endl;
		setErrorMessage( i18n( "Invalid OASIS document. No office:drawing tag found." ) );
		return false;
	}

	TQDomElement page( KoDom::namedItemNS( body, KoXmlNS::draw, "page" ) );
	if(page.isNull())
	{
		kdDebug(38000) << "No office:drawing found!" << endl;
		setErrorMessage( i18n( "Invalid OASIS document. No draw:page tag found." ) );
		return false;
	}

	TQString masterPageName = "Standard"; // use default layout as fallback
	TQDomElement *master = styles.masterPages()[ masterPageName ];
	if ( !master ) //last test...
		master = styles.masterPages()[ "Default" ];
	// last resort, use the first found master page style
	if ( ! master )
	{
		TQDictIterator<TQDomElement> it( styles.masterPages() );
		master = it.current();
	}
	Q_ASSERT( master );
	const TQDomElement *style = master ? styles.findStyle( master->attributeNS( KoXmlNS::style, "page-layout-name", TQString() ) ) : 0;
	if( style )
	{
		m_pageLayout.loadOasis( *style );
		m_doc.setWidth( m_pageLayout.ptWidth );
		m_doc.setHeight( m_pageLayout.ptHeight );
	}
	else
		return false;

	KoOasisLoadingContext context( this, styles, store );
	m_doc.loadOasis( page, context );
	// do y-mirroring here
	TQWMatrix mat;
	mat.scale( 1, -1 );
	mat.translate( 0, -m_doc.height() );
	VTransformCmd trafo( 0L, mat );
	trafo.visit( m_doc );

	loadOasisSettings( settings );

	return true;
}

void
KarbonPart::loadOasisSettings( const TQDomDocument&settingsDoc )
{
    if ( settingsDoc.isNull() )
        return ; // not an error if some file doesn't have settings.xml
    KoOasisSettings settings( settingsDoc );
    KoOasisSettings::Items viewSettings = settings.itemSet( "view-settings" );
    if ( !viewSettings.isNull() )
    {
        setUnit(KoUnit::unit(viewSettings.parseConfigItemString("unit")));
        // FIXME: add other config here.
    }
}


bool
KarbonPart::saveOasis( KoStore *store, KoXmlWriter *manifestWriter )
{
    if( !store->open( "content.xml" ) )
        return false;

    KoStoreDevice storeDev( store );
    KoXmlWriter* docWriter = createOasisXmlWriter( &storeDev, "office:document-content" );
    KoGenStyles mainStyles;

    KoGenStyle pageLayout = m_pageLayout.saveOasis();
    TQString layoutName = mainStyles.lookup( pageLayout, "PL" );
    KoGenStyle masterPage( KoGenStyle::STYLE_MASTER );
    masterPage.addAttribute( "style:page-layout-name", layoutName );
    mainStyles.lookup( masterPage, "Default", KoGenStyles::DontForceNumbering );

    KTempFile contentTmpFile;
    contentTmpFile.setAutoDelete( true );
    TQFile* tmpFile = contentTmpFile.file();
    KoXmlWriter contentTmpWriter( TQT_TQIODEVICE(tmpFile), 1 );

    contentTmpWriter.startElement( "office:body" );
    contentTmpWriter.startElement( "office:drawing" );

    m_doc.saveOasis( store, &contentTmpWriter, mainStyles ); // Save contents

    contentTmpWriter.endElement(); // office:drawing
    contentTmpWriter.endElement(); // office:body

    docWriter->startElement( "office:automatic-styles" );

    TQValueList<KoGenStyles::NamedStyle> styles = mainStyles.styles( VDocument::STYLE_GRAPHICAUTO );
    TQValueList<KoGenStyles::NamedStyle>::const_iterator it = styles.begin();
    for( ; it != styles.end() ; ++it )
        (*it).style->writeStyle( docWriter, mainStyles, "style:style", (*it).name , "style:graphic-properties"  );

    docWriter->endElement(); // office:automatic-styles

    // And now we can copy over the contents from the tempfile to the real one
    tmpFile->close();
    docWriter->addCompleteElement( TQT_TQIODEVICE(tmpFile) );
    contentTmpFile.close();

    docWriter->endElement(); // Root element
    docWriter->endDocument();
    delete docWriter;

    if( !store->close() )
        return false;

    manifestWriter->addManifestEntry( "content.xml", "text/xml" );

    if( !store->open( "styles.xml" ) )
        return false;

    KoXmlWriter* styleWriter = createOasisXmlWriter( &storeDev, "office:document-styles" );

    styleWriter->startElement( "office:styles" );

    styles = mainStyles.styles( VDocument::STYLE_LINEAR_GRADIENT );
    it = styles.begin();
    for( ; it != styles.end() ; ++it )
        (*it).style->writeStyle( styleWriter, mainStyles, "svg:linearGradient", (*it).name, 0, true,  true /*add draw:name*/);

    styles = mainStyles.styles( VDocument::STYLE_RADIAL_GRADIENT );
    it = styles.begin();
    for( ; it != styles.end() ; ++it )
        (*it).style->writeStyle( styleWriter, mainStyles, "svg:radialGradient", (*it).name, 0, true,  true /*add draw:name*/);

    styleWriter->endElement(); // office:styles

    styleWriter->startElement( "office:automatic-styles" );

    TQValueList<KoGenStyles::NamedStyle> styleList = mainStyles.styles( KoGenStyle::STYLE_PAGELAYOUT );
    it = styleList.begin();

    for( ; it != styleList.end(); ++it )
        (*it).style->writeStyle( styleWriter, mainStyles, "style:page-layout", (*it).name, "style:page-layout-properties" );

    styleWriter->endElement(); // office:automatic-styles

    styles = mainStyles.styles( KoGenStyle::STYLE_MASTER );
    it = styles.begin();
    styleWriter->startElement("office:master-styles");

    for( ; it != styles.end(); ++it)
        (*it).style->writeStyle( styleWriter, mainStyles, "style:master-page", (*it).name, "");

    styleWriter->endElement(); // office:master-styles

    styleWriter->endElement(); // Root element
    styleWriter->endDocument();
    delete styleWriter;

    if( !store->close() )
        return false;

    manifestWriter->addManifestEntry( "styles.xml", "text/xml" );


    if(!store->open("settings.xml"))
        return false;


    KoXmlWriter& settingsWriter = *createOasisXmlWriter(&storeDev, "office:document-settings");
    settingsWriter.startElement("office:settings");
    settingsWriter.startElement("config:config-item-set");
    settingsWriter.addAttribute("config:name", "view-settings");

    KoUnit::saveOasis(&settingsWriter, unit());
    saveOasisSettings( settingsWriter );

    settingsWriter.endElement(); // config:config-item-set
    settingsWriter.endElement(); // office:settings
    settingsWriter.endElement(); // Root element
    settingsWriter.endDocument();
    delete &settingsWriter;


    if(!store->close())
        return false;

    manifestWriter->addManifestEntry("settings.xml", "text/xml");

    setModified( false );
    return true;
}

void
KarbonPart::saveOasisSettings( KoXmlWriter &/*settingsWriter*/ )
{
    //todo
}

void
KarbonPart::insertObject( VObject* object )
{
	// don't repaint here explicitly. some commands might want to insert many
	// objects.
	m_doc.append( object );
	setModified( true );
}

void
KarbonPart::addCommand( VCommand* cmd, bool repaint )
{
	m_commandHistory->addCommand( cmd );
	setModified( true );

	if( repaint )
		repaintAllViews();
}

void
KarbonPart::slotDocumentRestored()
{
	setModified( false );
}

void
KarbonPart::slotCommandExecuted( VCommand *command )
{
	setModified( true );
}

void
KarbonPart::clearHistory()
{
	m_commandHistory->clear();
}

void
KarbonPart::repaintAllViews( bool repaint )
{
	TQPtrListIterator<KoView> itr( views() );

	for( ; itr.current() ; ++itr )
		static_cast<KarbonView*>( itr.current() )->canvasWidget()->repaintAll( repaint );
}

void
KarbonPart::repaintAllViews( const KoRect &rect )
{
	TQPtrListIterator<KoView> itr( views() );

	for( ; itr.current() ; ++itr )
		static_cast<KarbonView*>( itr.current() )->canvasWidget()->repaintAll( rect );
}

void
KarbonPart::paintContent( TQPainter& painter, const TQRect& rect,
						  bool /*transparent*/, double /*zoomX*/, double /*zoomY*/ )
{
	kdDebug(38000) << "**** part->paintContent()" << endl;

	KoRect r = KoRect::fromTQRect( rect );
	double zoomFactorX = double( r.width() ) / double( document().width() );
	double zoomFactorY = double( r.height() ) / double( document().height() );
	double zoomFactor = kMin( zoomFactorX, zoomFactorY );

	painter.eraseRect( rect );
	VPainterFactory *painterFactory = new VPainterFactory;
	//TQPaintDeviceMetrics metrics( painter.device() );
	painterFactory->setPainter( painter.device(), rect.width(), rect.height() );
	VPainter *p = painterFactory->painter();
	//VPainter *p = new VKoPainter( painter.device() );
	p->begin();
	p->setZoomFactor( zoomFactor );
	kdDebug(38000) << "painter.worldMatrix().dx() : " << painter.worldMatrix().dx() << endl;
	kdDebug(38000) << "painter.worldMatrix().dy() : " << painter.worldMatrix().dy() << endl;
	kdDebug(38000) << "rect.x() : "<< rect.x() << endl;
	kdDebug(38000) << "rect.y() : "<< rect.y() << endl;
	kdDebug(38000) << "rect.width() : "<< rect.width() << endl;
	kdDebug(38000) << "rect.height() : "<< rect.height() << endl;
	r = document().boundingBox();
	TQWMatrix mat = painter.worldMatrix();
	mat.scale( 1, -1 );
	mat.translate( 0, -r.height() * zoomFactor );
	p->setWorldMatrix( mat );

	m_doc.selection()->clear();
	TQPtrListIterator<VLayer> itr( m_doc.layers() );

	for( ; itr.current(); ++itr )
	{
		itr.current()->draw( p, &r );
	}

	p->end();
	delete painterFactory;
}

void
KarbonPart::setShowStatusBar( bool b )
{
	m_bShowStatusBar = b;
}

void
KarbonPart::reorganizeGUI()
{
	TQPtrListIterator<KoView> itr( views() );

	for( ; itr.current(); ++itr )
	{
		static_cast<KarbonView*>( itr.current() )->reorganizeGUI();
	}
}

void
KarbonPart::setUndoRedoLimit( int undos )
{
	m_commandHistory->setUndoLimit( undos );
	m_commandHistory->setRedoLimit( undos );
}

void
KarbonPart::initConfig()
{
	TDEConfig* config = KarbonPart::instance()->config();

	if( config->hasGroup( "Interface" ) )
	{
		config->setGroup( "Interface" );
		setAutoSave( config->readNumEntry( "AutoSave", defaultAutoSave() / 60 ) * 60 );
		m_maxRecentFiles = config->readNumEntry( "NbRecentFile", 10 );
		setShowStatusBar( config->readBoolEntry( "ShowStatusBar" , true ) );
		setBackupFile( config->readNumEntry( "BackupFile", true ) );
		m_doc.saveAsPath( config->readBoolEntry( "SaveAsPath", true ) );
	}
	int undos = 30;
	if( config->hasGroup( "Misc" ) )
	{
		config->setGroup( "Misc" );
		undos = config->readNumEntry( "UndoRedo", -1 );
		TQString defaultUnit = "cm";

		if( TDEGlobal::locale()->measureSystem() == TDELocale::Imperial )
			defaultUnit = "in";

		setUnit( KoUnit::unit( config->readEntry( "Units", defaultUnit ) ) );
		m_doc.setUnit( unit() );
	}
	if( undos != -1 )
		setUndoRedoLimit( undos );
}

bool
KarbonPart::mergeNativeFormat( const TQString &file )
{
	m_merge = true;
	bool result = loadNativeFormat( file );
	if ( !result )
		showLoadingErrorDialog();
	m_merge = false;
	return result;
}

void
KarbonPart::addShell( KoMainWindow *shell )
{
	connect( shell, TQT_SIGNAL( documentSaved() ), m_commandHistory, TQT_SLOT( documentSaved() ) );
	KoDocument::addShell( shell );
}


void
KarbonPart::slotUnitChanged( KoUnit::Unit /*unit*/ )
{
#if 0
	// VDocument has its own storage of the unit...
	m_doc.setUnit( unit );
	if( m_toolController->activeTool() )
		m_toolController->activeTool()->refreshUnit();
#endif
}

#include "karbon_part.moc"