/*
 * loader.cpp
 *
 * Copyright (c) 2001, 2002, 2003, 2004 Frerich Raabe <raabe@kde.org>
 *
 * 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. For licensing and distribution details, check the
 * accompanying file 'COPYING'.
 */
#include "loader.h"
#include "document.h"

#include <tdeio/job.h>
#include <tdeprocess.h>
#include <kurl.h>

#include <tqdom.h>
#include <tqbuffer.h>
#include <tqregexp.h>

using namespace RSS;

DataRetriever::DataRetriever()
{
}

DataRetriever::~DataRetriever()
{
}

struct FileRetriever::Private
{
	Private()
		: buffer(NULL),
		  lastError(0)
	{
	}

	~Private()
	{
		delete buffer;
	}

	TQBuffer *buffer;
	int lastError;
};

FileRetriever::FileRetriever()
	: d(new Private)
{
}

FileRetriever::~FileRetriever()
{
	delete d;
}

void FileRetriever::retrieveData(const KURL &url)
{
	if (d->buffer)
		return;
	
	d->buffer = new TQBuffer;
	d->buffer->open(IO_WriteOnly);

	TDEIO::Job *job = TDEIO::get(url, false, false);
	connect(job, TQ_SIGNAL(data(TDEIO::Job *, const TQByteArray &)),
	             TQ_SLOT(slotData(TDEIO::Job *, const TQByteArray &)));
	connect(job, TQ_SIGNAL(result(TDEIO::Job *)), TQ_SLOT(slotResult(TDEIO::Job *)));
	connect(job, TQ_SIGNAL(permanentRedirection(TDEIO::Job *, const KURL &, const KURL &)),
	             TQ_SLOT(slotPermanentRedirection(TDEIO::Job *, const KURL &, const KURL &)));
}

int FileRetriever::errorCode() const
{
	return d->lastError;
}

void FileRetriever::slotData(TDEIO::Job *, const TQByteArray &data)
{
	d->buffer->writeBlock(data.data(), data.size());
}

void FileRetriever::slotResult(TDEIO::Job *job)
{
	TQByteArray data = d->buffer->buffer();
	data.detach();

	delete d->buffer;
	d->buffer = NULL;
	
	d->lastError = job->error();
	emit dataRetrieved(data, d->lastError == 0);
}

void FileRetriever::slotPermanentRedirection(TDEIO::Job *, const KURL &, const KURL &newUrl)
{
	emit permanentRedirection(newUrl);
}

struct OutputRetriever::Private
{
	Private() : process(NULL),
		buffer(NULL),
		lastError(0)
	{
	}

	~Private()
	{
		delete process;
		delete buffer;
	}
	
	KShellProcess *process;
	TQBuffer *buffer;
	int lastError;
};

OutputRetriever::OutputRetriever() :
	d(new Private)
{
}

OutputRetriever::~OutputRetriever()
{
	delete d;
}

void OutputRetriever::retrieveData(const KURL &url)
{
	// Ignore subsequent calls if we didn't finish the previous job yet.
	if (d->buffer || d->process)
		return;
	
	d->buffer = new TQBuffer;
	d->buffer->open(IO_WriteOnly);
	
	d->process = new KShellProcess();
	connect(d->process, TQ_SIGNAL(processExited(TDEProcess *)),
	                    TQ_SLOT(slotExited(TDEProcess *)));
	connect(d->process, TQ_SIGNAL(receivedStdout(TDEProcess *, char *, int)),
	                    TQ_SLOT(slotOutput(TDEProcess *, char *, int)));
	*d->process << url.path();
	d->process->start(TDEProcess::NotifyOnExit, TDEProcess::Stdout);
}

int OutputRetriever::errorCode() const
{
	return d->lastError;
}

void OutputRetriever::slotOutput(TDEProcess *, char *data, int length)
{
	d->buffer->writeBlock(data, length);
}

void OutputRetriever::slotExited(TDEProcess *p)
{
	if (!p->normalExit())
		d->lastError = p->exitStatus();

	TQByteArray data = d->buffer->buffer();
	data.detach();
	
	delete d->buffer;
	d->buffer = NULL;
	
	delete d->process;
	d->process = NULL;
	
	emit dataRetrieved(data, p->normalExit() && p->exitStatus() == 0);
}

struct Loader::Private
{
	Private() : retriever(NULL),
		lastError(0)
	{
	}

	~Private()
	{
		delete retriever;
	}

	DataRetriever *retriever;
	int lastError;
};

Loader *Loader::create()
{
	return new Loader;
}

Loader *Loader::create(TQObject *object, const char *slot)
{
	Loader *loader = create();
	connect(loader, TQ_SIGNAL(loadingComplete(Loader *, Document, Status)),
	        object, slot);
	return loader;
}

Loader::Loader() : d(new Private)
{
}

Loader::~Loader()
{
	delete d;
}

void Loader::loadFrom(const KURL &url, DataRetriever *retriever)
{
	if (d->retriever != NULL)
		return;

	d->retriever = retriever;
	
	connect(d->retriever, TQ_SIGNAL(dataRetrieved(const TQByteArray &, bool)),
	        this, TQ_SLOT(slotRetrieverDone(const TQByteArray &, bool)));

	d->retriever->retrieveData(url);
}

int Loader::errorCode() const
{
	return d->lastError;
}

void Loader::slotRetrieverDone(const TQByteArray &data, bool success)
{
	d->lastError = d->retriever->errorCode();
	
	delete d->retriever;
	d->retriever = NULL;
	
	Document rssDoc;
	Status status = Success;
	
	if (success) {
		TQDomDocument doc;

		/* Some servers insert whitespace before the <?xml...?> declaration.
		 * TQDom doesn't tolerate that (and it's right, that's invalid XML),
		 * so we strip that.
		 */

		const char *charData = data.data();
		int len = data.count();

		while (len && TQChar(*charData).isSpace()) {
			--len;
			++charData;
		}

		/* It seems that some Microsoft-developed code generates UTF-8 files
		 * with the three leading unicode characters 0357, 0273, 0277. For
		 * an example, check http://msdn.microsoft.com/rss.xml
		 */
		if (len > 3 && TQChar(*charData) == TQChar(0357)) {
			len -= 3;
			charData += 3;
		}

		TQByteArray tmpData;
		tmpData.setRawData(charData, len);
		
		if (doc.setContent(tmpData))
			rssDoc = Document(doc);
		else
			status = ParseError;
		
		tmpData.resetRawData(charData, len);
	} else
		status = RetrieveError;

	emit loadingComplete(this, rssDoc, status);

	delete this;
}

#include "loader.moc"
