/* This file is part of the KDE project
   Copyright (C) 2005 Jaroslaw Staniek <js@iidea.pl>

   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 "kexiblobbuffer.h"

#include <assert.h>

#include <tqfile.h>
#include <tqfileinfo.h>
#include <tqbuffer.h>

#include <kdebug.h>
#include <kstaticdeleter.h>
#include <kimageio.h>

#include <kexidb/connection.h>

static KStaticDeleter<KexiBLOBBuffer> m_bufferDeleter;
static KexiBLOBBuffer* m_buffer = 0;

//-----------------

class KexiBLOBBuffer::Private
{
	public:
		Private()
		 : maxId(0)
		 , inMemoryItems(1009)
		 , storedItems(1009)
		 , itemsByURL(1009)
		{
		}
		Id_t maxId; //!< Used to compute maximal recently used identifier for unstored BLOB
//! @todo will be changed to TQHash<quint64, Item>
		TQIntDict<Item> inMemoryItems; //!< for unstored BLOBs
		TQIntDict<Item> storedItems; //!< for stored items
		TQDict<Item> itemsByURL;
		TQGuardedPtr<KexiDB::Connection> conn;
};

//-----------------

KexiBLOBBuffer::Handle::Handle(Item* item)
 : m_item(item)
{
	if (m_item)
		m_item->refs++;
}

KexiBLOBBuffer::Handle::Handle(const Handle& handle)
{
	*this = handle;
}

KexiBLOBBuffer::Handle::Handle()
 : m_item(0)
{
}

KexiBLOBBuffer::Handle::~Handle()
{
	if (m_item) {
		m_item->refs--;
		if (m_item->refs<=0)
			KexiBLOBBuffer::self()->removeItem(m_item->id, m_item->stored);
	}
}

KexiBLOBBuffer::Handle& KexiBLOBBuffer::Handle::operator=(const Handle& handle)
{
	m_item = handle.m_item;
	if (m_item)
		m_item->refs++;
	return *this;
}

void KexiBLOBBuffer::Handle::setStoredWidthID(KexiBLOBBuffer::Id_t id)
{
	if (!m_item)
		return;
	if (m_item->stored) {
		kdWarning() << "KexiBLOBBuffer::Handle::setStoredWidthID(): object for id=" << id 
			<< " is aleady stored" << endl;
		return;
	}

	KexiBLOBBuffer::self()->takeItem(m_item);
	m_item->id = id; //new id
	m_item->stored = true;
//! @todo What about other handles for this item? 
//! @todo They were assuming it's unstored item, but it's stored now....
	KexiBLOBBuffer::self()->insertItem(m_item);
}

//-----------------

KexiBLOBBuffer::Item::Item(const TQByteArray& data, KexiBLOBBuffer::Id_t ident, bool _stored,
	const TQString& _name, const TQString& _caption, const TQString& _mimeType, 
	Id_t _folderId, const TQPixmap& pixmap)
 : name(_name), caption(_caption), mimeType(_mimeType), refs(0), 
 id(ident), folderId(_folderId), stored(_stored),
 m_pixmapLoaded(new bool(false)/*workaround for pixmap() const*/)
{
	if (pixmap.isNull())
		m_pixmap = new TQPixmap();
	else
		m_pixmap = new TQPixmap(pixmap);

	if (data.isEmpty())
		m_data = new TQByteArray();
	else
		m_data = new TQByteArray(data);
}

KexiBLOBBuffer::Item::~Item()
{
	kexipluginsdbg << "KexiBLOBBuffer::Item::~Item()" << endl;
	delete m_pixmap;
	m_pixmap = 0;
	delete m_data;
	m_data = 0;
	delete m_pixmapLoaded;
}

TQPixmap KexiBLOBBuffer::Item::pixmap() const
{
	if (!*m_pixmapLoaded && m_pixmap->isNull() && !m_data->isEmpty()) {
		TQString type( KImageIO::typeForMime(mimeType) );
		if (!KImageIO::canRead( type ) || !m_pixmap->loadFromData(*m_data, type.latin1())) {
			//! @todo inform about error?
		}
		*m_pixmapLoaded = true;
	}
	return *m_pixmap;
}

TQByteArray KexiBLOBBuffer::Item::data() const
{
	if (!m_data->isEmpty())
		return *m_data;

	if (m_data->isEmpty() && m_pixmap->isNull())
		return TQByteArray();
	
	if (m_data->isEmpty() && !m_pixmap->isNull()) {
		//convert pixmap to byte array
		//(do it only on demand)
		TQBuffer buffer( *m_data );
		buffer.open( IO_WriteOnly );
		m_pixmap->save( &buffer, mimeType.isEmpty() ? (const char*)"PNG"/*! @todo default? */ : mimeType.latin1() );
	}
	return *m_data;
}

//-----------------

KexiBLOBBuffer::KexiBLOBBuffer()
 : TQObject()
 , d(new Private())
{
	Q_ASSERT(!m_buffer);
	d->inMemoryItems.setAutoDelete(true);
	d->storedItems.setAutoDelete(true);
}

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

KexiBLOBBuffer::Handle KexiBLOBBuffer::insertPixmap(const KURL& url)
{
	if (url.isEmpty() )
		return KexiBLOBBuffer::Handle();
	if (!url.isValid()) {
		kexipluginswarn << "::insertPixmap: INVALID URL '" << url << "'" << endl;
		return KexiBLOBBuffer::Handle();
	}
//! @todo what about searching by filename only and then compare data?
	Item * item = d->itemsByURL.find(url.prettyURL());
	if (item)
		return KexiBLOBBuffer::Handle(item);

	TQString fileName = url.isLocalFile() ? url.path() : url.prettyURL();
//! @todo download the file if remote, then set fileName properly
	TQFile f(fileName);
	if (!f.open(IO_ReadOnly)) {
		//! @todo err msg
		return KexiBLOBBuffer::Handle();
	}
	TQString mimeType( KImageIO::mimeType( fileName ) );

	TQByteArray data( f.readAll() );
	if (f.status()!=IO_Ok) {
		//! @todo err msg
		return KexiBLOBBuffer::Handle();
	}
	TQFileInfo fi(url.fileName());
	TQString caption(fi.baseName().replace('_', " ").simplifyWhiteSpace());

	item = new Item(data, ++d->maxId, /*!stored*/false, url.fileName(), caption, mimeType);
	insertItem(item);

	//cache
	item->prettyURL = url.prettyURL();
	d->itemsByURL.replace(url.prettyURL(), item);
	return KexiBLOBBuffer::Handle(item);
}

KexiBLOBBuffer::Handle KexiBLOBBuffer::insertObject(const TQByteArray& data, 
	const TQString& name, const TQString& caption, const TQString& mimeType, KexiBLOBBuffer::Id_t identifier)
{
	KexiBLOBBuffer::Id_t newIdentifier;
	if (identifier>0)
		newIdentifier = identifier;
	else
		newIdentifier = ++d->maxId;

	Item *item = new Item(data, newIdentifier, identifier>0, name, caption, mimeType);
	insertItem( item );
	return KexiBLOBBuffer::Handle(item);
}

KexiBLOBBuffer::Handle KexiBLOBBuffer::insertPixmap(const TQPixmap& pixmap)
{
	if (pixmap.isNull())
		return KexiBLOBBuffer::Handle();

	Item * item = new Item(
		TQByteArray(), //(pixmap will be converted to byte array on demand)
		++d->maxId, 
		false, //not stored
		TQString(), 
		TQString(), 
		"image/png", //!< @todo OK? What about jpegs?
		0, //folder id
		pixmap);

	insertItem(item);
	return KexiBLOBBuffer::Handle(item);
}

KexiBLOBBuffer::Handle KexiBLOBBuffer::objectForId(Id_t id, bool stored)
{
	if (id<=0)
		return KexiBLOBBuffer::Handle();
	if (stored) {
		Item *item = d->storedItems.find(id);
		if (item || !d->conn)
			return KexiBLOBBuffer::Handle(item);
		//retrieve stored BLOB:

//#if 0
		assert(d->conn);
		KexiDB::TableSchema *blobsTable = d->conn->tableSchema("kexi__blobs"); 
		if (!blobsTable) {
		//! @todo err msg
			return KexiBLOBBuffer::Handle();
		}
/*		TQStringList where;
		where << "o_id";
		KexiDB::PreparedStatement::Ptr st = d->conn->prepareStatement(
			KexiDB::PreparedStatement::SelectStatement, *blobsTable, where);*/
//! @todo use PreparedStatement
		KexiDB::QuerySchema schema;
		schema.addField( blobsTable->field("o_data") );
		schema.addField( blobsTable->field("o_name") );
		schema.addField( blobsTable->field("o_caption") );
		schema.addField( blobsTable->field("o_mime") );
		schema.addField( blobsTable->field("o_folder_id") );
		schema.addToWhereExpression(blobsTable->field("o_id"), TQVariant((TQ_LLONG)id));

		KexiDB::RowData rowData;
		tristate res = d->conn->querySingleRecord(
			schema,
//			TQString::fromLatin1("SELECT o_data, o_name, o_caption, o_mime FROM kexi__blobs where o_id=")
//			+TQString::number(id), 
			rowData); 
		if (res!=true || rowData.size()<4) {
		//! @todo err msg
			kdWarning() << "KexiBLOBBuffer::objectForId("<<id<<","<<stored
			<<"): res!=true || rowData.size()<4; res=="<<res.toString()<<" rowData.size()=="<<rowData.size()<< endl;
			return KexiBLOBBuffer::Handle();
		}

		item = new Item(
			rowData[0].toByteArray(),
			id, 
			true, //stored
			rowData[1].toString(),
			rowData[2].toString(),
			rowData[3].toString(),
			(Id_t)rowData[4].toInt()
		);

		insertItem(item);
		return KexiBLOBBuffer::Handle(item);
//#endif
	}
	else
		return KexiBLOBBuffer::Handle(d->inMemoryItems.find(id));
}

KexiBLOBBuffer::Handle KexiBLOBBuffer::objectForId(Id_t id)
{
	KexiBLOBBuffer::Handle h(objectForId(id, false/*!stored*/));
	if (h)
		return h;
	return objectForId(id, true/*stored*/);
}

void KexiBLOBBuffer::removeItem(Id_t id, bool stored)
{
	Item *item;
	if (stored)
		item = d->storedItems.take(id);
	else
		item = d->inMemoryItems.take(id);

	if (item && !item->prettyURL.isEmpty()) {
		d->itemsByURL.remove(item->prettyURL);
	}
	delete item;
}

void KexiBLOBBuffer::takeItem(Item *item)
{
	assert(item);
	if (item->stored)
		d->storedItems.take(item->id);
	else
		d->inMemoryItems.take(item->id);
}

void KexiBLOBBuffer::insertItem(Item *item)
{
	assert(item);
	if (item->stored)
		d->storedItems.insert(item->id, item);
	else
		d->inMemoryItems.insert(item->id, item);
}

void KexiBLOBBuffer::setConnection(KexiDB::Connection *conn)
{
	KexiBLOBBuffer::self()->d->conn = conn;
}

KexiBLOBBuffer* KexiBLOBBuffer::self()
{
	if(!m_buffer) {
		m_bufferDeleter.setObject( m_buffer, new KexiBLOBBuffer() );
	}
	return m_buffer;
}

#include "kexiblobbuffer.moc"
