//
// C++ Implementation: kxedocument
//
// Description:
//
//
// Author: Adam Charytoniuk <achary@poczta.onet.pl>, (C) 2004
//
// Copyright: See COPYING file that comes with this distribution
//
//
#include "kxedocument.h"

#include "kxmleditorfactory.h"
#include "kxeconfiguration.h"
#include "kxenewfilesettings.h"
#include "kxearchiveextssettings.h"
#include "kxeprintsettings.h"
#include "kxetextviewsettings.h"
#include "kxechoosestringdialog.h"
#include "kxeattachdialogbase.h"
#include "kxespecprocinstrdialog.h"
#include "kxefilenewdialog.h"
#include "commands_file.h"


#include <tdefile.h>
#include <ktar.h>
#include <kzip.h>
#include <kfilterdev.h>
#include <tdetempfile.h>
#include <kdebug.h>
#include <tdemessagebox.h>
#include <tdelocale.h>
#include <kcommand.h>
#include <tdeaction.h>
#include <kurl.h>
#include <kurlrequester.h>

#include <tqcombobox.h>
#include <tqbuffer.h>
#include <tqregexp.h>
#include <tqtextcodec.h>
#include <tqlabel.h>
#include <tqcheckbox.h>

KXEDocument::KXEDocument(TQObject *parent, const char *name)
	:TQObject (parent,name),
	TQDomDocument(),
	KXMLGUIClient()
{
	m_bDocIsCompressed = false;
	m_bIsModified = false;
	m_strCompressedTarEntryName = "";
	m_url = "";
		
	//setXMLFile("kxedocument.rc");
}


KXEDocument::~KXEDocument()
{
}


bool KXEDocument::save(const TQString  &strFileName)
{
	if (this->documentElement().isNull() &&
	    KMessageBox::warningContinueCancel(0,
		i18n("Your file doesn't have root element defined. \n\
		Continue saving?"))==KMessageBox::Cancel )
		{
			return false;
		}

	TQString strXML;

	TQTextStream streamXML(&strXML, IO_WriteOnly);
	int iIndent = KXMLEditorFactory::configuration()->textview()->indentSteps();
	((TQDomDocument*)this)->save(streamXML, iIndent);

	TQString strEncoding;
	TQTextCodec *pTextCodec;

  // find encoding info
  if(strXML.left(5) == "<?xml")
     { int iStart, iEnd;
       if((iStart = strXML.find("encoding", 0)) > 0)
       {
         // info about encoding found;
         iStart += 8; // skip encoding

         // search " or ' after encoding
         if((iStart = strXML.find(TQRegExp("[\"']"), iStart)) > 0)
         {
           TQChar ch = strXML[iStart];
           iStart++; // skip ch
           if((iEnd = strXML.find(ch, iStart)) > 0)
           {
             strEncoding = strXML.mid(iStart, iEnd - iStart);
           }
         }
       }
     }

	if(strEncoding.length() <= 0)
    pTextCodec = TQTextCodec::codecForLocale(); // default
  else
    pTextCodec = TQTextCodec::codecForName(strEncoding.latin1());

	if(pTextCodec == 0)
		{ if(KMessageBox::questionYesNo(0, i18n("Codec for encoding %1 not found ! Continue saving ?").arg(strEncoding)) != KMessageBox::Yes)
					return false;
		}

	TQCString strDecoded;
	if(pTextCodec)
		{ strDecoded = pTextCodec->fromUnicode(strXML);
		}

  // save string to file
	if(!m_bDocIsCompressed)
	    { TQFile file(strFileName);
	      if(file.open(IO_WriteOnly) == true)
	        { file.writeBlock(strDecoded, strDecoded.length());
	  				file.flush();
	  				file.close();
	  			}
	  		else
					{ KMessageBox::error(0,
												 i18n("Can't create file %1").arg(strFileName),
												 i18n("Write error !"));
					}
	  	}
	else
	  	{ // obtain file extension -----------------------------------------
				TQString strExtension;
			
				int iPos = strFileName.findRev('.');
			
				if(iPos > 0)
					{ strExtension = strFileName.mid(iPos + 1);
					}
					
				if(strExtension == "svgz")
				{
					KMessageBox::sorry(0,
												 "Saving *.svgz not implemented yet",
												 "sory");
					return false;
				}
				else
				{
					KZip	tarGzFile(strFileName); // New KOffice use KZip instead of KTarGz for storing files
					if(tarGzFile.open(IO_WriteOnly))
						{ tarGzFile.writeFile(m_strCompressedTarEntryName, "user", "group", strDecoded.length(), strDecoded);
							tarGzFile.close();
						}
					else
						{ KMessageBox::error(0,
													i18n("Can't create archive %1").arg(strFileName),
													i18n("Write error !"));
						}
				}
			}

	return true;
}

bool KXEDocument::open(const TQString &strFileName)
{
  TQString strCompressedTarEntryName;

  kdDebug() << "KXEDocument::open: opening file " << strFileName << endl;

  // obtain file extension -----------------------------------------
	TQString strExtension;

	int iPos = strFileName.findRev('.');

	if(iPos > 0)
		{ strExtension = strFileName.mid(iPos + 1);
		}

	TQString strTmpfileName;

	if ( KXMLEditorFactory::configuration()->archexts()->extensions().contains(strExtension) )
		{ 
		  
			KTempFile tmp;
			if (tmp.status() != 0)
			{
				kdError() << "Couldn't open temp file" << endl;
				KMessageBox::sorry(0, i18n("Couldn't open temp file !"));
				return false;
			}
		
			tmp.setAutoDelete(false);
			TQFile &fileTemporary = *(tmp.file());
			
						
			if(strExtension == "svgz")
				{ 
					//----------------------- It is gzip compressed file -----------------------
				
					m_strCompressedTarEntryName = strFileName.left(strFileName.length() - 5); // For SVG compressed icons strip extension, e.g. "kate.svgz" has entry "kate" etc
				
					iPos = m_strCompressedTarEntryName.findRev('/');

					if(iPos > 0)
						{ m_strCompressedTarEntryName = m_strCompressedTarEntryName.mid(iPos + 1);
						}
					
				  TQIODevice *pIODevice = KFilterDev::deviceForFile(strFileName, "application/x-gzip");
				  
					if(pIODevice->open( IO_ReadOnly ))
					{
							TQTextStream stream(pIODevice);
							TQString line;
							//int i = 1;
							while ( !stream.atEnd() ) 
							{
									line = stream.readLine(); // line of text excluding '\n'
									//printf( "%3d: %s\n", i++, line.latin1() );
									fileTemporary.writeBlock(line.utf8(), line.utf8().length());
							}
							pIODevice->close();
					}
				}
			else 
				{
					//----------------------- It is zip archive file ---------------------------
				  								
					KZip tarGzFile(strFileName); // new KOffice use KZip instead of KTarGz for storing files
					
					tarGzFile.open(IO_ReadOnly);
					fileTemporary.open(IO_WriteOnly);
		
					const KTarDirectory *root = tarGzFile.directory();
					if(!root)
						{
							return false;
						}
		
					// For KOffice files let user to choose maindoc or documentinfo
					if(strCompressedTarEntryName.length() == 0)
					{ KXEChooseStringDialog dlgChooseString(0, 0, i18n("Choose file").utf8(), i18n("File:").utf8());
						dlgChooseString.m_pComboBox->insertItem("maindoc.xml");
						dlgChooseString.m_pComboBox->insertItem("documentinfo.xml");
	
						if(dlgChooseString.exec() != KXEChooseStringDialog::Accepted)
							{ return false;
							}
						m_strCompressedTarEntryName = dlgChooseString.m_strChoosedText;
					}
					else
					{ 
						m_strCompressedTarEntryName = strCompressedTarEntryName;
					}
							
					const KArchiveEntry *entry = root->entry(m_strCompressedTarEntryName);
		
					if(entry && entry->isFile())
						{ const KArchiveFile *pTarFile = static_cast <const KArchiveFile *> (entry);
		
							TQBuffer buffer(pTarFile->data());
							buffer.open(IO_ReadOnly);
		
							fileTemporary.writeBlock(buffer.buffer(), buffer.size());
						}
					else
						m_strCompressedTarEntryName.truncate(0);
		
					tarGzFile.close();
				}
			
			strTmpfileName = fileTemporary.name();
			fileTemporary.close();
					
			m_bDocIsCompressed = true;
		}
	else
		m_bDocIsCompressed = false;


	// ( 1.) parse the file and fill our document
	TQFile file(m_bDocIsCompressed ? strTmpfileName : strFileName);
	if(! file.open(IO_ReadOnly))
	{
		kdDebug() << "KXEDocument::openFile: Can't open file." << endl;
		return false;
	}

  // auxiliary file for obtaining encoding info
  TQFile fileAux(m_bDocIsCompressed ? strTmpfileName : strFileName);
	if(! fileAux.open(IO_ReadOnly))
	{
		kdDebug() << "KXEDocument::openFile: Can't open file." << endl;
		return false;
	}

	TQTextStream txtStreamLocal( & file );

  // Lookup at XML document encoding -----------------------------------------------
  TQTextStream txtStreamAux( & fileAux );
  TQString strFirstLine = txtStreamAux.readLine();
  fileAux.close();
  int iStart, iEnd;
  if((iStart = strFirstLine.find("encoding", 0)) > 0)
  {
    TQString strEncoding;
    // info about encoding found;
    iStart += 8; // skip encoding

    // search " or ' after encoding
    if((iStart = strFirstLine.find(TQRegExp("[\"']"), iStart)) > 0)
    {
      TQChar ch = strFirstLine[iStart];
      iStart++; // skip ch
      if((iEnd = strFirstLine.find(ch, iStart)) > 0)
      {
        strEncoding = strFirstLine.mid(iStart, iEnd - iStart);

        TQTextCodec *pTextCodec = TQTextCodec::codecForName(strEncoding.latin1());
        if(pTextCodec)
          txtStreamLocal.setCodec(pTextCodec);
        else
        {
          KMessageBox::sorry(0, i18n("Codec for encoding %1 not found ! Using locale encoding for load.").arg(strEncoding));
          txtStreamLocal.setEncoding(TQTextStream::Locale);
        }
      }
    }
  }
  else
  {
    // XML documment dont have info about encoding, set default UTF-8
    txtStreamLocal.setCodec(TQTextCodec::codecForName("UTF-8"));
  }


  //--------------------------------------------------------------------------------
  TQString strFileContents = txtStreamLocal.read();
	file.close();

  if(m_bDocIsCompressed)
    {
      TQDir dir;
      dir.remove(strTmpfileName);
    }

  //-- Set string with XML to TQDomDocument ------------------------------------------
	TQString strErrorMsg;
  int iErrorLine, iErrorColumn;
	TQDomDocument * pNewDoc = new TQDomDocument; // first try with a new document

 	if( ! pNewDoc->setContent(strFileContents, true, &strErrorMsg, &iErrorLine, &iErrorColumn) )
 		{ kdDebug() << "KXEDocument::openFile: Failed parsing the file." << endl;

			KMessageBox::error(0,
												 i18n("%1 in line %2, column %3").arg(strErrorMsg).arg(iErrorLine).arg(iErrorColumn),
												 i18n("Parsing error !"));

			delete pNewDoc; // remove the new document, because it's useless
			return false;
 		}

// The following commented code is performance wise buggy, because the string
// gets parsed a second time. I replaced it with this code.
	// copy the content of the parsed document to this one
	TQDomNode e = pNewDoc->removeChild( pNewDoc->documentElement() );
	TQDomDocument::operator=( *pNewDoc );
	appendChild( e );
// Here comes the "buggy" code.
	//this->setContent(pNewDoc->toString(),true,0,0); // and take the new one
	//delete pNewDoc;    // remove the former document
// To test/see the difference in loading time, you can switch the commented 
// codeblocks above and compare the loading-time-differences measured in 
// KXMLEditorPart::openFile.
//                                             Olaf
// TODO: remove the comments above later

	emit sigOpened();
	
	return true;
}

void KXEDocument::setModified(bool value)
{
	m_bIsModified = value;
	emit sigModified(value);
}

void KXEDocument::setURL(KURL url)
{
	m_url = url;
	emit sigURLChanged(url);
}

void KXEDocument::updateNodeCreated(const TQDomNode & node)
{
	emit sigNodeCreated(node);
	setModified();
}

void KXEDocument::updateNodeDeleted(const TQDomNode & node)
{
	emit sigNodeDeleted(node);
	setModified();
}

void KXEDocument::updateNodeChanged( const TQDomElement & domElement )
{
	emit sigNodeChanged(domElement);
	setModified();
}

void KXEDocument::updateNodeChanged( const TQDomCharacterData & node )
{
	emit sigNodeChanged(node);
	setModified();
}

void KXEDocument::updateNodeChanged( const TQDomProcessingInstruction &domProcInstr )
{
	emit sigNodeChanged(domProcInstr);
	setModified();
}

void KXEDocument::updateNodeMoved( const TQDomNode & node )
{
	emit sigNodeMoved(node);
	setModified();
}

void KXEDocument::attachStylesheet(const KURL& stylesheet)
{
	setSpecProcInstr("xml-stylesheet",TQString("type = 'text/xsl' href = '")+stylesheet.url()+"' ");
}

void KXEDocument::detachStylesheet()
{
	removeSpecProcInstr("xml-stylesheet");
}

void KXEDocument::attachSchema(const KURL& schema)
{
	TQDomElement domElement = documentElement();
	if (!domElement.isNull())
	{
		domElement.setAttributeNS(SCHEMA_NAMESPACE,
								SCHEMA_ATTRIBUTE_XSI,
								schema.url());
		// refresh views
		updateNodeChanged(domElement);
		setModified();
	}
}

void KXEDocument::detachSchema()
{
	TQDomElement domElement = this->documentElement();
	if (!domElement.isNull())
	{
		domElement.removeAttributeNS(SCHEMA_NAMESPACE,SCHEMA_ATTRIBUTE);
		// refresh views
		updateNodeChanged(domElement);
		setModified();
	}
}

void KXEDocument::setSpecProcInstr(const TQString& target, const TQString& data)
{
	// removing old one
	removeSpecProcInstr(target);
	// create new one
	if (!data.isEmpty())
	{
		TQDomProcessingInstruction domProcInstr = this->createProcessingInstruction(target,data);

		TQDomNode node = getSpecProcInstr("xml");
		if (!node.isNull())
			// if there is already xml instruction, then put that one below it
			this->insertAfter(domProcInstr,node);
		else
			// otherwise put it always on the top
			this->insertBefore(domProcInstr,this->firstChild());

		updateNodeCreated(domProcInstr);
	}
	setModified();
}

void KXEDocument::removeSpecProcInstr(const TQString &target)
{
	TQDomNode domNode = getSpecProcInstr(target);
	if (!domNode.isNull())
	{
		updateNodeDeleted(domNode);
		((TQDomDocument*)this)->removeChild(domNode);
		setModified();
	}
}

TQDomNode KXEDocument::getSpecProcInstr(const TQString& target)
{
	TQDomNode result;
	TQDomNodeList domNodeList = this->childNodes();
	for (uint i=0;i<domNodeList.count();i++)
		if (domNodeList.item(i).isProcessingInstruction())
		{
			TQDomProcessingInstruction domProcInstr = domNodeList.item(i).toProcessingInstruction();
			if (domProcInstr.target()==target)
				return domNodeList.item(i);
		}
	return result;
}

void KXEDocument::newFile()
{
	switch ( KXMLEditorFactory::configuration()->newfile()->newFileCreaBehav() )
	{
		case KXENewFileSettings::CreateEmptyFile:
			break;         // nothing to do in this case

		case KXENewFileSettings::CreateWithAssistance:
		{

			KXEFileNewDialog dlg( 0L);
			dlg.fillDialog( KXMLEditorFactory::configuration()->newfile()->dfltVersion(),
			                KXMLEditorFactory::configuration()->newfile()->dfltEncoding() );
			if( dlg.exec() )
			{                    // if the dialog has been accepted (OK pressed)
				setSpecProcInstr( "xml", dlg.getData() );

				// if the dialog shouldn't be shown anymore, the settings have to be changed
				if ( dlg.m_pDontShowAgain->isChecked() )
					KXMLEditorFactory::configuration()->newfile()->setNewFileCreaBehav( KXENewFileSettings::UseDefaults, instance()->config() );
			}

			break;
		}

		case KXENewFileSettings::UseDefaults:
			setSpecProcInstr( "xml", TQString( "version='%1' encoding='%2'" ).arg(KXMLEditorFactory::configuration()->newfile()->dfltVersion()).arg(KXMLEditorFactory::configuration()->newfile()->dfltEncoding()) );
			break;
	}
	emit sigOpened();
	setModified();
}

//------------- SLOTS, called from Part --------------------------------

KCommand * KXEDocument::actDetachStylesheet()
{
	TQDomNode domNode = getSpecProcInstr("xml-stylesheet");
	if (!domNode.isNull())
	{
		KCommand *pCmd = new KXEStylesheetDetachCommand(this,domNode.toProcessingInstruction().data());
		return pCmd;
	}
	return 0L;
}

KCommand * KXEDocument::actAttachStylesheet()
{
	KXEAttachDialogBase dlg;
	dlg.Label->setText(i18n("Stylesheet URL:"));
	if (dlg.exec())
	{
		TQDomNode domNode = getSpecProcInstr("xml-stylesheet");
		TQString data = "";
		if (!domNode.isNull())
			data = domNode.toProcessingInstruction().data();
		KCommand *pCmd = new KXEStylesheetAttachCommand(this,data,dlg.attachURI->url());
		return pCmd;
	}
	return 0L;
}

KCommand * KXEDocument::actDetachSchema()
{
	if (!documentElement().isNull())		// just for sure...
	{
		KCommand *pCmd = new KXESchemaDetachCommand(this,
													documentElement().attributeNS(SCHEMA_NAMESPACE,
																			SCHEMA_ATTRIBUTE,"")
													);
		return pCmd;
	}
	return 0L;
}

KCommand * KXEDocument::actAttachSchema()
{
	KXEAttachDialogBase dlg;
	dlg.Label->setText(i18n("Schema URL:"));
	if (dlg.exec())
	{
		if (!documentElement().isNull())		// just for sure...
		{
			KCommand *pCmd = new KXESchemaAttachCommand(this,dlg.attachURI->url(),
													documentElement().attributeNS(SCHEMA_NAMESPACE,SCHEMA_ATTRIBUTE,""));
			return pCmd;
		}
	}
	return 0L;
}

// Instert or edit special processing instruction <?xml ... ?>
KCommand * KXEDocument::actVersionEncoding()
{
	TQDomNode node = getSpecProcInstr("xml");
	KXESpecProcInstrDialog dlg;

	if(!node.isNull())
		dlg.fillDialog(node.toProcessingInstruction().data());
	else
		dlg.fillDialog( KXMLEditorFactory::configuration()->newfile()->dfltVersion(),
		                KXMLEditorFactory::configuration()->newfile()->dfltEncoding() );

	if(dlg.exec())
	{
		TQString strOldData = "";
		if (!node.isNull())
			strOldData = node.toProcessingInstruction().data();
		KCommand *pCmd = new KXEVersionEncodingCommand(this,strOldData,dlg.getData());
		return pCmd;
	}
	return 0L;
}

#include "kxedocument.moc"
