/***************************************************************************
 *   Copyright (C) 2003 by S�astien Laot                                 *
 *   slaout@linux62.org                                                    *
 *                                                                         *
 *   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.,                                       *
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.             *
 ***************************************************************************/

#include <kurl.h>
#include <tdeglobal.h>
#include <kstandarddirs.h>
#include <ksimpleconfig.h>
#include <tqpainter.h>
#include <tqdir.h>
#include <tqimage.h>

#include <iostream>

#include "backgroundmanager.h"

/** class BackgroundEntry: */

BackgroundEntry::BackgroundEntry(const TQString &location)
{
	this->location = location;
	name           = KURL(location).fileName();
	tiled          = false;
	pixmap         = 0;
	preview        = 0;
	customersCount = 0;
}

BackgroundEntry::~BackgroundEntry()
{
	delete pixmap;
	delete preview;
}

/** class OpaqueBackgroundEntry: */

OpaqueBackgroundEntry::OpaqueBackgroundEntry(const TQString &name, const TQColor &color)
{
	this->name     = name;
	this->color    = color;
	pixmap         = 0;
	customersCount = 0;
}

OpaqueBackgroundEntry::~OpaqueBackgroundEntry()
{
	delete pixmap;
}

/** class BackgroundManager: */

BackgroundManager::BackgroundManager()
{
///	std::cout << "BackgroundManager: Found the following background images in  ";
	TQStringList directories = TDEGlobal::dirs()->resourceDirs("data"); // eg. { "/home/seb/.trinity/share/apps/", "/usr/share/apps/" }
	// For each folder:
	for (TQStringList::Iterator it = directories.begin(); it != directories.end(); ++it) {
		// For each file in those directories:
		TQDir dir(*it + "basket/backgrounds/", /*nameFilder=*/"*.png", /*sortSpec=*/TQDir::Name | TQDir::IgnoreCase, /*filterSpec=*/TQDir::Files | TQDir::NoSymLinks);
///		std::cout << *it + "basket/backgrounds/  ";
		TQStringList files = dir.entryList();
		for (TQStringList::Iterator it2 = files.begin(); it2 != files.end(); ++it2) // TODO: If an image name is present in two folders?
			addImage(*it + "basket/backgrounds/" + *it2);
	}

///	std::cout << ":" << std::endl;
///	for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
///		std::cout << "* " << (*it)->location << "  [ref: " << (*it)->name << "]" << std::endl;

	connect( &m_garbageTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(doGarbage()) );
}

BackgroundManager::~BackgroundManager()
{
}

void BackgroundManager::addImage(const TQString &fullPath)
{
	m_backgroundsList.append(new BackgroundEntry(fullPath));
}

BackgroundEntry* BackgroundManager::backgroundEntryFor(const TQString &image)
{
	for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
		if ((*it)->name == image)
			return *it;
	return 0;
}

OpaqueBackgroundEntry* BackgroundManager::opaqueBackgroundEntryFor(const TQString &image, const TQColor &color)
{
	for (OpaqueBackgroundsList::Iterator it = m_opaqueBackgroundsList.begin(); it != m_opaqueBackgroundsList.end(); ++it)
		if ((*it)->name == image && (*it)->color == color)
			return *it;
	return 0;
}

bool BackgroundManager::subscribe(const TQString &image)
{
	BackgroundEntry *entry = backgroundEntryFor(image);
	if (entry) {
		// If it's the first time something subscribe to this image:
		if (!entry->pixmap) {
			// Try to load the pixmap:
			entry->pixmap = new TQPixmap(entry->location);
			// Try to figure out if it's a tiled background image or not (default to NO):
			KSimpleConfig config(entry->location + ".config", /*readOnly=*/true);
			config.setGroup("BasKet Background Image Configuration");
			entry->tiled = config.readBoolEntry("tiled", false);
		}
		// Return if the image loading has failed:
		if (entry->pixmap->isNull()) {
///			std::cout << "BackgroundManager: Failed to load " << entry->location << std::endl;
			return false;
		}
		// Success: effectively subscribe:
		++entry->customersCount;
		return true;
	} else {
		// Don't exist: subscription failed:
///		std::cout << "BackgroundManager: Requested unexisting image: " << image << std::endl;
		return false;
	}
}

bool BackgroundManager::subscribe(const TQString &image, const TQColor &color)
{
	BackgroundEntry *backgroundEntry = backgroundEntryFor(image);

	// First, if the image doesn't exist, isn't subscribed, or failed to load then we don't go further:
	if (!backgroundEntry || !backgroundEntry->pixmap || backgroundEntry->pixmap->isNull()) {
///		std::cout << "BackgroundManager: Requested an unexisting or unsubscribed image: (" << image << "," << color.name() << ")..." << std::endl;
		return false;
	}

	OpaqueBackgroundEntry *opaqueBackgroundEntry = opaqueBackgroundEntryFor(image, color);

	// If this couple is requested for the first time or it haven't been subscribed for a long time enough, create it:
	if (!opaqueBackgroundEntry) {
///		std::cout << "BackgroundManager: Computing (" << image << "," << color.name() << ")..." << std::endl;
		opaqueBackgroundEntry = new OpaqueBackgroundEntry(image, color);
		opaqueBackgroundEntry->pixmap = new TQPixmap(backgroundEntry->pixmap->size());
		opaqueBackgroundEntry->pixmap->fill(color);
		TQPainter painter(opaqueBackgroundEntry->pixmap);
		painter.drawPixmap(0, 0, *(backgroundEntry->pixmap));
		painter.end();
		m_opaqueBackgroundsList.append(opaqueBackgroundEntry);
	}

	// We are now sure the entry exist, do the subscription:
	++opaqueBackgroundEntry->customersCount;
	return true;
}

void BackgroundManager::unsubscribe(const TQString &image)
{
	BackgroundEntry *entry = backgroundEntryFor(image);

	if (!entry) {
///		std::cout << "BackgroundManager: Wanted to unsuscribe a not subscribed image: " << image << std::endl;
		return;
	}

	--entry->customersCount;
	if (entry->customersCount <= 0)
		requestDelayedGarbage();
}

void BackgroundManager::unsubscribe(const TQString &image, const TQColor &color)
{
	OpaqueBackgroundEntry *entry = opaqueBackgroundEntryFor(image, color);

	if (!entry) {
///		std::cout << "BackgroundManager: Wanted to unsuscribe a not subscribed colored image: (" << image << "," << color.name() << ")" << std::endl;
		return;
	}

	--entry->customersCount;
	if (entry->customersCount <= 0)
		requestDelayedGarbage();
}

TQPixmap* BackgroundManager::pixmap(const TQString &image)
{
	BackgroundEntry *entry = backgroundEntryFor(image);

	if (!entry || !entry->pixmap || entry->pixmap->isNull()) {
///		std::cout << "BackgroundManager: Requested an unexisting or unsubscribed image: " << image << std::endl;
		return 0;
	}

	return entry->pixmap;
}

TQPixmap* BackgroundManager::opaquePixmap(const TQString &image, const TQColor &color)
{
	OpaqueBackgroundEntry *entry = opaqueBackgroundEntryFor(image, color);

	if (!entry || !entry->pixmap || entry->pixmap->isNull()) {
///		std::cout << "BackgroundManager: Requested an unexisting or unsubscribed colored image: (" << image << "," << color.name() << ")" << std::endl;
		return 0;
	}

	return entry->pixmap;
}

bool BackgroundManager::tiled(const TQString &image)
{
	BackgroundEntry *entry = backgroundEntryFor(image);

	if (!entry || !entry->pixmap || entry->pixmap->isNull()) {
///		std::cout << "BackgroundManager: Requested an unexisting or unsubscribed image: " << image << std::endl;
		return false;
	}

	return entry->tiled;
}

bool BackgroundManager::exists(const TQString &image)
{
	for (BackgroundsList::iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
		if ((*it)->name == image)
			return true;
	return false;
}

TQStringList BackgroundManager::imageNames()
{
	TQStringList list;
	for (BackgroundsList::iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it)
		list.append((*it)->name);
	return list;
}

TQPixmap* BackgroundManager::preview(const TQString &image)
{
	static const int    MAX_WIDTH  = 100;
	static const int    MAX_HEIGHT = 75;
	static const TQColor PREVIEW_BG = TQt::white;

	BackgroundEntry *entry = backgroundEntryFor(image);

	if (!entry) {
///		std::cout << "BackgroundManager: Requested the preview of an unexisting image: " << image << std::endl;
		return false;
	}

	// The easiest way: already computed:
	if (entry->preview)
		return entry->preview;

	// Then, try to load the preview from file:
	TQString previewPath = TDEGlobal::dirs()->findResource("data", "basket/backgrounds/previews/" + entry->name);
	TQPixmap *previewPixmap = new TQPixmap(previewPath);
	// Success:
	if (!previewPixmap->isNull()) {
///		std::cout << "BackgroundManager: Loaded image preview for " << entry->location << " from file " << previewPath << std::endl;
		entry->preview = previewPixmap;
		return entry->preview;
	}

	// We failed? Then construct it:
	// Note: if a preview is requested, it's because the user is currently choosing an image.
	// Since we need that image to create the preview, we keep the image in memory.
	// Then, it will already be loaded when user press [OK] in the background image chooser.
	// BUT we also delay a garbage because we don't want EVERY images to be loaded if the user use only a few of them, of course:

	// Already used? Good: we don't have to load it...
	if (!entry->pixmap) {
		// Note: it's a code duplication from BackgroundManager::subscribe(const TQString &image),
		// Because, as we are loading the pixmap we ALSO need to know if it's a tile or not, in case that image will soon be used (and not destroyed by the garbager):
		entry->pixmap = new TQPixmap(entry->location);
		// Try to figure out if it's a tiled background image or not (default to NO):
		KSimpleConfig config(entry->location + ".config", /*readOnly=*/true);
		config.setGroup("BasKet Background Image Configuration");
		entry->tiled = config.readBoolEntry("tiled", false);
	}

	// The image cannot be loaded, we failed:
	if (entry->pixmap->isNull())
		return 0;

	// Good that we are still alive: entry->pixmap contains the pixmap to rescale down for the preview:
	// Compute new size:
	int width  = entry->pixmap->width();
	int height = entry->pixmap->height();
	if (width > MAX_WIDTH) {
		height = height * MAX_WIDTH / width;
		width  = MAX_WIDTH;
	}
	if (height > MAX_HEIGHT) {
		width  = width * MAX_HEIGHT / height;
		height = MAX_HEIGHT;
	}
	// And create the resulting pixmap:
	TQPixmap *result = new TQPixmap(width, height);
	result->fill(PREVIEW_BG);
	TQImage imageToScale = entry->pixmap->convertToImage();
	TQPixmap pmScaled;
	pmScaled.convertFromImage(imageToScale.smoothScale(width, height));
	TQPainter painter(result);
	painter.drawPixmap(0, 0, pmScaled);
	painter.end();

	// Saving it to file for later:
	TQString folder = TDEGlobal::dirs()->saveLocation("data", "basket/backgrounds/previews/");
	result->save(folder + entry->name, "PNG");

	// Ouf! That's done:
	entry->preview = result;
	requestDelayedGarbage();
	return entry->preview;
}

TQString BackgroundManager::pathForImageName(const TQString &image)
{
	BackgroundEntry *entry = backgroundEntryFor(image);
	if (entry == 0)
		return "";
	else
		return entry->location;
}

TQString BackgroundManager::previewPathForImageName(const TQString &image)
{
	BackgroundEntry *entry = backgroundEntryFor(image);
	if (entry == 0)
		return "";
	else {
		TQString previewPath = TDEGlobal::dirs()->findResource("data", "basket/backgrounds/previews/" + entry->name);
		TQDir dir;
		if (!dir.exists(previewPath))
			return "";
		else
			return previewPath;
	}
}

void BackgroundManager::requestDelayedGarbage()
{
	static const int DELAY = 60/*seconds*/;

	if (!m_garbageTimer.isActive())
		m_garbageTimer.start(DELAY * 1000/*ms*/, /*singleShot=*/true);
}

void BackgroundManager::doGarbage()
{
///	std::cout << "BackgroundManager: Doing garbage..." << std::endl;

///	std::cout << "BackgroundManager: Images:" << std::endl;
	for (BackgroundsList::Iterator it = m_backgroundsList.begin(); it != m_backgroundsList.end(); ++it) {
		BackgroundEntry *entry = *it;
///		std::cout << "* " << entry->name << ": used " << entry->customersCount << " times";
		if (entry->customersCount <= 0 && entry->pixmap) {
///			std::cout << " [Deleted cached pixmap]";
			delete entry->pixmap;
			entry->pixmap = 0;
		}
///		std::cout << std::endl;
	}

///	std::cout << "BackgroundManager: Opaque Cached Images:" << std::endl;
	for (OpaqueBackgroundsList::Iterator it = m_opaqueBackgroundsList.begin(); it != m_opaqueBackgroundsList.end(); ) {
		OpaqueBackgroundEntry *entry = *it;
///		std::cout << "* " << entry->name << "," << entry->color.name() << ": used " << entry->customersCount << " times";
		if (entry->customersCount <= 0) {
///			std::cout << " [Deleted entry]";
			delete entry->pixmap;
			entry->pixmap = 0;
			it = m_opaqueBackgroundsList.remove(it);
		} else
			++it;
///		std::cout << std::endl;
	}
}

#include "backgroundmanager.moc"
