/*
 *   kdeprintfax - a small fax utility
 *   Copyright (C) 2001  Michael Goffioul
 *
 *   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.
 *
 *   This program 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 General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "faxctrl.h"
#include "kdeprintfax.h"
#include "defcmds.h"

#include <qtextedit.h>
#include <qfile.h>
#include <qregexp.h>
#include <kpushbutton.h>
#include <qlayout.h>
#include <qprinter.h>
#include <qvaluestack.h>

#include <kprocess.h>
#include <kglobal.h>
#include <kconfig.h>
#include <klocale.h>
#include <kdialogbase.h>
#include <kmimetype.h>
#include <kstandarddirs.h>
#include <kapplication.h>
#include <kwin.h>
#include <kemailsettings.h>
#include <kdebug.h>
#include <kstdguiitem.h>

#include <stdlib.h>

#define quote(x) KProcess::quote(x)

FaxCtrl::FaxCtrl(QObject *parent, const char *name)
: QObject(parent, name)
{
	m_process = new KProcess();
	m_process->setUseShell(true);
	connect(m_process, SIGNAL(receivedStdout(KProcess*,char*,int)), SLOT(slotReceivedStdout(KProcess*,char*,int)));
	connect(m_process, SIGNAL(receivedStderr(KProcess*,char*,int)), SLOT(slotReceivedStdout(KProcess*,char*,int)));
	connect(m_process, SIGNAL(processExited(KProcess*)), SLOT(slotProcessExited(KProcess*)));
	connect(this, SIGNAL(faxSent(bool)), SLOT(cleanTempFiles()));
	m_logview = 0;
}

FaxCtrl::~FaxCtrl()
{
	slotCloseLog();
	delete m_process;
}

bool FaxCtrl::send(KdeprintFax *f)
{
	m_command = faxCommand();
	if (m_command.isEmpty())
		return false;
	KConfig	*conf = KGlobal::config();

	// unquote variables (they will be replaced with quoted values later)

        QValueStack<bool> stack;

	bool issinglequote=false;
	bool isdoublequote=false;
	QRegExp re_noquote("(\\$\\(|\\)|\\(|\"|'|\\\\|`|%dev|%server|%page|%res|%user|%from|%email|%number|%name|%comment|%enterprise|%time_(\\w))");
	QRegExp re_singlequote("('|%dev|%server|%page|%res|%user|%from|%email|%number|%name|%comment|%enterprise|%time_(\\w))");
	QRegExp re_doublequote("(\\$\\(|\"|\\\\|`|%dev|%server|%page|%res|%user|%from|%email|%number|%name|%comment|%enterprise|%time_(\\w))");
	for	( int i = re_noquote.search(m_command);
		i != -1;
		i = (issinglequote?re_singlequote.search(m_command,i)
			:isdoublequote?re_doublequote.search(m_command,i)
			:re_noquote.search(m_command,i))
	)
	{
		if (m_command[i]=='(') // (...)
		{
			// assert(isdoublequote == false)
			stack.push(isdoublequote);
			i++;
		}
		else if (m_command[i]=='$') // $(...)
		{
			stack.push(isdoublequote);
			isdoublequote = false;
			i+=2;
		}
		else if (m_command[i]==')') // $(...) or (...)
		{
			if (!stack.isEmpty())
				isdoublequote = stack.pop();
			else
				qWarning("Parse error.");
			i++;
		}
		else if (m_command[i]=='\'')
		{
			issinglequote=!issinglequote;
			i++;
		}
		else if (m_command[i]=='"')
		{
			isdoublequote=!isdoublequote;
			i++;
		}
		else if (m_command[i]=='\\')
			i+=2;
		else if (m_command[i]=='`')
		{
			// Replace all `...` with safer $(...)
			m_command.replace (i, 1, "$(");
			QRegExp re_backticks("(`|\\\\`|\\\\\\\\|\\\\\\$)");
			for (	int i2=re_backticks.search(m_command,i+2);
				i2!=-1;
				i2=re_backticks.search(m_command,i2)
			)
			{
				if (m_command[i2] == '`')
				{
					m_command.replace (i2, 1, ")");
					i2=m_command.length(); // leave loop
				}
				else
				{	// remove backslash and ignore following character
					m_command.remove (i2, 1);
					i2++;
				}
			}
			// Leave i unchanged! We need to process "$("
		}
		else
		{
			QString match, v;

			// get match
			if (issinglequote)
				match=re_singlequote.cap();
			else if (isdoublequote)
				match=re_doublequote.cap();
			else
				match=re_noquote.cap();

			// substitute %variables
			// settings
			if (match=="%dev")
			{
				conf->setGroup("Fax");
				v = quote(conf->readEntry("Device", "modem"));

			}
			else if (match=="%server")
			{
				conf->setGroup( "Fax" );
				v = conf->readEntry("Server");
				if (v.isEmpty())
					v = getenv("FAXSERVER");
				if (v.isEmpty())
					v = QString::fromLatin1("localhost");
				v = quote(v);
			}
			else if (match=="%page")
				v = pageSize();
			else if (match=="%res")
			{
				conf->setGroup( "Fax" );
				if (conf->readEntry("Resolution", "High") == "High")
					v ="";
				else
					v = "-l";
			}
			else if (match=="%user")
			{
				conf->setGroup("Personal");
				v = quote(conf->readEntry("Name", getenv("USER")));
			}
			else if (match=="%from")
			{
				conf->setGroup( "Personal" );
				v = quote(conf->readEntry("Number", ""));
			}
			else if (match=="%email")
			{
				KEMailSettings	e;
				v = quote(e.getSetting(KEMailSettings::EmailAddress));
			}
			// arguments
			else if (match=="%number")
				v = quote(f->number());
			else if (match=="%name")
				v = quote(f->name());
			else if (match=="%comment")
				v = quote(f->comment());
			else if (match=="%enterprise")
				v = quote(f->enterprise());
			else if (match.left(6) == "%time_") // %time_(\w)
			{
				QString	tt = f->time();
				if (tt.isEmpty())
					v = "";
				else
					v = ("-" + match.mid(6) + " " + quote(tt));
			}

			// %variable inside of a quote?
			if (isdoublequote)
				v='"'+v+'"';
			else if (issinglequote)
				v="'"+v+"'";

			m_command.replace (i, match.length(), v);
			i+=v.length();
		}
	}

	m_log = QString::null;
	m_filteredfiles.clear();
	cleanTempFiles();
	m_files = f->files();

	filter();

	return true;
}

void FaxCtrl::slotReceivedStdout(KProcess*, char *buffer, int len)
{
	QCString	str(buffer, len);
	addLog(QString(str));
}

void FaxCtrl::slotProcessExited(KProcess*)
{
	// we exited a process: if there's still entries in m_files, this was a filter
	// process, else this was the fax process
	bool	ok = (m_process->normalExit() && ((m_process->exitStatus() & (m_files.count() > 0 ? 0x1 : 0xFFFFFFFF)) == 0));
	if (ok && m_files.count() > 0)
	{
		// remove first element
		m_files.remove(m_files.begin());
		if (m_files.count() > 0)
			filter();
		else
			sendFax();
	}
	else
		emit faxSent(ok);
}

QString FaxCtrl::faxCommand()
{
	KConfig	*conf = KGlobal::config();
	conf->setGroup("System");
	QString	sys = conf->readEntry("System", "efax");
	QString cmd;
	if (sys == "hylafax")
		cmd = conf->readPathEntry("HylaFax", hylafax_default_cmd);
	else if (sys == "mgetty")
		cmd = conf->readPathEntry("Mgetty", mgetty_default_cmd);
	else
		cmd = conf->readPathEntry("EFax", efax_default_cmd);
	if (cmd.startsWith("%exe_"))
		cmd = defaultCommand(cmd);
	return cmd;
}

QString FaxCtrl::pageSize()
{
	KConfig	*conf = KGlobal::config();
	conf->setGroup("Fax");
	return conf->readEntry("Page", KGlobal::locale()->pageSize() == QPrinter::A4 ? "a4" : "letter");
}

void FaxCtrl::sendFax()
{
	// files
	QString	filestr;
	for (QStringList::ConstIterator it=m_filteredfiles.begin(); it!=m_filteredfiles.end(); ++it)
		filestr += (quote(*it)+" ");
	m_command.replace(QRegExp("%files"), filestr);

	m_process->clearArguments();
	*m_process << m_command;
	addLog(i18n("Sending to fax using: %1\n").arg(m_command));
	if (!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput))
		emit faxSent(false);
	else emit message(i18n("Sending fax..."));
}

void FaxCtrl::filter()
{
	if (m_files.count() > 0)
	{
		QString	mimeType = KMimeType::findByURL(KURL(m_files[0]), 0, true)->name();
		if (mimeType == "application/postscript" || mimeType == "image/tiff")
		{
			emit message(i18n("Skipping %1...").arg(m_files[0]));
			m_filteredfiles.prepend(m_files[0]);
			m_files.remove(m_files.begin());
			filter();
		}
		else
		{
			QString	tmp = locateLocal("tmp","kdeprintfax_") + kapp->randomString(8);
			m_filteredfiles.prepend(tmp);
			m_tempfiles.append(tmp);
			m_process->clearArguments();
			*m_process << locate("data","kdeprintfax/anytops") << "-m" << locate("data","kdeprintfax/faxfilters") 
				<< QString::fromLatin1("--mime=%1").arg(mimeType)
				<< "-p" << pageSize()
				<<  m_files[0] << tmp;
			if (!m_process->start(KProcess::NotifyOnExit, KProcess::AllOutput))
				emit faxSent(false);
			else emit message(i18n("Filtering %1...").arg(m_files[0]));
		}
	}
	else
		sendFax();
}

bool FaxCtrl::abort()
{
	if (m_process->isRunning())
		return m_process->kill();
	else
		return false;
}

void FaxCtrl::viewLog(QWidget *parent)
{
	if (!m_logview)
	{
		QWidget	*topView = new QWidget(0, "LogView", WType_TopLevel|WStyle_DialogBorder|WDestructiveClose);
		m_logview = new QTextEdit(topView);
		m_logview->setReadOnly(true);
		m_logview->setWordWrap(QTextEdit::NoWrap);
		QPushButton	*m_clear = new KPushButton(KStdGuiItem::clear(), topView);
		QPushButton	*m_close = new KPushButton(KStdGuiItem::close(), topView);
		m_close->setDefault(true);
		connect(m_clear, SIGNAL(clicked()), SLOT(slotClearLog()));
		connect(m_close, SIGNAL(clicked()), SLOT(slotCloseLog()));
		connect(m_logview, SIGNAL(destroyed()), SLOT(slotCloseLog()));

		QVBoxLayout	*l0 = new QVBoxLayout(topView, 10, 10);
		l0->addWidget(m_logview);
		QHBoxLayout	*l1 = new QHBoxLayout(0, 0, 10);
		l0->addLayout(l1);
		l1->addStretch(1);
		l1->addWidget(m_clear);
		l1->addWidget(m_close);

		m_logview->setText(m_log);

		topView->resize(450, 350);
		topView->show();
	}
	else
	{
		KWin::setActiveWindow(m_logview->parentWidget()->winId());
	}
}

void FaxCtrl::addLog(const QString& s)
{
	m_log.append(s).append("\n");
	if (m_logview)
		m_logview->append(s);
}

QString FaxCtrl::faxSystem()
{
	KConfig	*conf = KGlobal::config();
	conf->setGroup("System");
	QString	s = conf->readEntry("System", "efax");
	s[0] = s[0].upper();
	return s;
}

void FaxCtrl::cleanTempFiles()
{
	for (QStringList::ConstIterator it=m_tempfiles.begin(); it!=m_tempfiles.end(); ++it)
		QFile::remove(*it);
	m_tempfiles.clear();
}

void FaxCtrl::slotClearLog()
{
	m_log = QString::null;
	if (m_logview)
		m_logview->clear();
}

void FaxCtrl::slotCloseLog()
{
	const QObject	*obj = sender();
	if (m_logview)
	{
		QTextEdit	*view = m_logview;
		m_logview = 0;
		if (obj->inherits("QPushButton"))
			delete view->parentWidget();
kdDebug() << "slotClose()" << endl;
	}
}

#include "faxctrl.moc"
