#include "mapview.h"
#include "mapview_tile_cache_p.h"
#include "mapview_tile_downloader_p.h"
#include "qad_locations.h"


MapViewTileCache::MapViewTileCache(MapView * p): QThread() {
	qRegisterMetaType<MapViewTypes::TileIndex>();
	qRegisterMetaType<MapViewTypes::TilePixmap>();
	parent = p;
	setCacheRoot(QAD::userPath(QAD::ltCache, "mapview") + "/");
	setAdditionalCacheSize(64);
	start();
}


MapViewTileCache::~MapViewTileCache() {
	requestInterruption();
	cond.wakeAll();
	wait();
}


void MapViewTileCache::tileDownloaded(MapViewTypes::TileIndex index, const QPixmap & pixmap) {
	auto * tile  = new MapViewTypes::TilePixmap();
	tile->index  = index;
	tile->pixmap = pixmap;
	saveTile(tile);
}


MapViewTypes::TilePixmap MapViewTileCache::getTile(MapViewTypes::TileIndex index, QRectF & rect_src) {
	MapViewTypes::TilePixmap ret = getTileFromCache(index);
	if (ret.isEmpty() || ret.obsolete) {
		if (!is_offline) parent->downloader->queueTile(index);
	}
	if (ret.isEmpty()) {
		QVector<QPointF> offsets;
		while (index.z >= 0) {
			index.z--;
			offsets << QPointF(index.x % 2, index.y % 2);
			index.x /= 2;
			index.y /= 2;
			ret = getTileFromCache(index);
			if (!ret.isEmpty()) {
				for (int i = offsets.size() - 1; i >= 0; --i) {
					rect_src.setSize(rect_src.size() / 2.);
					rect_src.translate(offsets[i].x() * rect_src.width(), offsets[i].y() * rect_src.height());
				}
				return ret;
			}
		}
	} else
		rect_src = ret.pixmap.rect();
	return ret;
}


void MapViewTileCache::clearCache() {
	tile_cache.clear();
}


bool MapViewTileCache::isTileFileExists(MapViewTypes::TileIndex index) const {
	QString hashdir = index.cacheDir();
	if (!cache_dir.exists(hashdir)) return false;
	QString hashname = hashdir + "/" + index.hashName();
	return cache_dir.exists(hashname);
}


void MapViewTileCache::setCacheRoot(const QString & p) {
	cache_root = p;
	tileProviderChanged();
}


void MapViewTileCache::tileProviderChanged() {
	cache_dir.setPath(cache_root);
	auto * provider = parent->downloader->provider;
	if (provider) {
		cache_dir.setPath(cache_root + "/" + provider->cacheDir());
	}
	qDebug() << "[MapViewTileCache] Cache dir" << cache_dir.absolutePath();
	if (!cache_dir.exists()) cache_dir.mkpath(".");
}


void MapViewTileCache::setOfflineMode(bool yes) {
	is_offline = yes;
}


MapViewTypes::TilePixmap MapViewTileCache::getTileFromCache(MapViewTypes::TileIndex index) {
	MapViewTypes::TilePixmap ret;
	ret.index   = index;
	auto * tile = tile_cache[index.hash()];
	if (tile) {
		ret.pixmap = tile->pixmap;
	} else {
		QString hashdir = index.cacheDir();
		if (cache_dir.exists(hashdir)) {
			QString hashname = hashdir + "/" + index.hashName();
			if (cache_dir.exists(hashname)) {
				QString fname = cache_dir.absoluteFilePath(hashname);
				ret.pixmap.load(fname, "png");
				tile         = new MapViewTypes::TilePixmap();
				tile->index  = index;
				tile->pixmap = ret.pixmap;
				if (secs_obsolete > 0) {
					int secs_old = QFileInfo(fname).lastModified().secsTo(QDateTime::currentDateTime());
					ret.obsolete = secs_old >= secs_obsolete;
				}
				tile_cache.insert(tile->index.hash(), tile, tile->pixmapBytes());
				return ret;
			}
		}
	}
	return ret;
}


void MapViewTileCache::updateCacheSize() {
	tile_cache.setMaxCost(fixed_size_b + add_size_b);
}


void MapViewTileCache::saveTile(MapViewTypes::TilePixmap * tile) {
	tile_cache.insert(tile->index.hash(), tile, tile->pixmapBytes());
	emit tileReady(*tile);
	cond_mutex.lock();
	if (!queue.contains(*tile)) {
		queue.enqueue(*tile);
		cond.wakeOne();
	}
	cond_mutex.unlock();
}


void MapViewTileCache::writeToDisk(const MapViewTypes::TilePixmap & tile) {
	QString hashdir = tile.index.cacheDir();
	if (!cache_dir.exists(hashdir)) cache_dir.mkdir(hashdir);
	QFile f(cache_dir.absoluteFilePath(hashdir + "/" + tile.index.hashName()));
	if (!f.open(QIODevice::ReadWrite)) return;
	f.resize(0);
	tile.pixmap.save(&f, "png");
}


void MapViewTileCache::run() {
	while (!isInterruptionRequested()) {
		MapViewTypes::TilePixmap tile;
		cond_mutex.lock();
		if (queue.isEmpty()) {
			cond.wait(&cond_mutex);
			cond_mutex.unlock();
		} else {
			tile = queue.dequeue();
			cond_mutex.unlock();
			writeToDisk(tile);
		}
	}
}
