#include <mapnik/map.hpp>
#include <mapnik/datasource_cache.hpp>
#include <mapnik/agg_renderer.hpp>
#include <mapnik/filter_factory.hpp>
#include <mapnik/color_factory.hpp>
#include <mapnik/image_util.hpp>
#include <mapnik/load_map.hpp>
#include <mapnik/image_util.hpp>

#include <iostream>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <syslog.h>
#include <unistd.h>
#include <pthread.h>

using namespace mapnik;
#define DEG_TO_RAD (M_PI/180)
#define RAD_TO_DEG (180/M_PI)

#define SIZE 256
#define RENDER_SIZE (SIZE)

class SphericalProjection
{
    double *Ac, *Bc, *Cc, *zc;

    public:
        SphericalProjection(int levels=18) {
            Ac = new double[levels];
            Bc = new double[levels];
            Cc = new double[levels];
            zc = new double[levels];
            int d, c = SIZE;
            for (d=0; d<levels; d++) {
                int e = c/2;
                Bc[d] = c/360.0;
                Cc[d] = c/(2 * M_PI);
                zc[d] = e;
                Ac[d] = c;
                c *=2;
            }
        }

        void fromLLtoPixel(double &x, double &y, int zoom) {
            double d = zc[zoom];
            double f = minmax(sin(DEG_TO_RAD * y),-0.9999,0.9999);
            x = round(d + x * Bc[zoom]);
            y = round(d + 0.5*log((1+f)/(1-f))*-Cc[zoom]);
        }
        void fromPixelToLL(double &x, double &y, int zoom) {
            double e = zc[zoom];
            double g = (y - e)/-Cc[zoom];
            x = (x - e)/Bc[zoom];
            y = RAD_TO_DEG * ( 2 * atan(exp(g)) - 0.5 * M_PI);
        }

    private:
        double minmax(double a, double b, double c)
        {
            #define MIN(x,y) ((x)<(y)?(x):(y))
            #define MAX(x,y) ((x)>(y)?(x):(y))
            a = MAX(a,b);
            a = MIN(a,c);
            return a;
        }
};

/* Build parent directories for the specified file name
 * Note: the part following the trailing / is ignored
 * e.g. mkdirp("/a/b/foo.png") == shell mkdir -p /a/b */
static int mkdirp(const char *path) {
    struct stat s;
    char tmp[PATH_MAX];
    char *p;

    strncpy(tmp, path, sizeof(tmp));

    // Look for parent directory
    p = strrchr(tmp, '/');
    if (!p)
        return 0;

    *p = '\0';

    if (!stat(tmp, &s))
        return !S_ISDIR(s.st_mode);
    *p = '/';
    // Walk up the path making sure each element is a directory
    p = tmp;
    if (!*p)
        return 0;
    p++; // Ignore leading /
    while (*p) {
        if (*p == '/') {
            *p = '\0';
            if (!stat(tmp, &s)) {
                if (!S_ISDIR(s.st_mode)) {
                    fprintf(stderr, "Error, is not a directory: %s\n", tmp);
                    return 1;
                }
            } else if (mkdir(tmp, 0777)) {
                    perror(tmp);
                    return 1;
                }
            *p = '/';
        }
        p++;
    }
    return 0;
}

static void xyz_to_path(char *path, size_t len, const char *tile_dir,
		int x, int y, int z)
{
    snprintf(path, len, "%s/%d/%d/%d.png", tile_dir, z, x, y);
    return;
}

static void load_fonts(const char *font_dir, int recurse)
{
    DIR *fonts = opendir(font_dir);
    struct dirent *entry;
    char path[PATH_MAX]; // FIXME: Eats lots of stack space when recursive

    if (!fonts) {
        syslog(LOG_CRIT, "Unable to open font directory: %s", font_dir);
        return;
    }

    while ((entry = readdir(fonts))) {
        struct stat b;
        char *p;

        if (!strcmp(entry->d_name, ".") || !strcmp(entry->d_name, ".."))
            continue;
        snprintf(path, sizeof(path), "%s/%s", font_dir, entry->d_name);
        if (stat(path, &b))
            continue;
        if (S_ISDIR(b.st_mode)) {
            if (recurse)
                load_fonts(path, recurse);
            continue;
        }
        p = strrchr(path, '.');
        if (p && !strcmp(p, ".ttf")) {
            syslog(LOG_DEBUG, "DEBUG: Loading font: %s", path);
            freetype_engine::register_font(path);
        }
    }
    closedir(fonts);
}

static SphericalProjection *tiling;

static int render(Map &m, const char *tile_dir,
		projection &prj, int x, int y, int z)
{
    char filename[PATH_MAX];
    char tmp[PATH_MAX];
    double p0x = x * SIZE;
    double p0y = (y + 1) * SIZE;
    double p1x = (x + 1) * SIZE;
    double p1y = y * SIZE;

    tiling->fromPixelToLL(p0x, p0y, z);
    tiling->fromPixelToLL(p1x, p1y, z);

    prj.forward(p0x, p0y);
    prj.forward(p1x, p1y);

    Envelope<double> bbox(p0x, p0y, p1x, p1y);
//  bbox.width(bbox.width() * 2);
//  bbox.height(bbox.height() * 2);
    m.resize(SIZE, SIZE);
    m.zoomToBox(bbox);
    m.set_buffer_size(128);

    Image32 buf(RENDER_SIZE, RENDER_SIZE);
    agg_renderer<Image32> ren(m, buf);
    ren.apply();

    xyz_to_path(filename, sizeof(filename), tile_dir, x, y, z);
    if (mkdirp(filename))
        return 0;
    snprintf(tmp, sizeof(tmp), "%s.tmp", filename);

    image_view<ImageData32> vw(0, 0, SIZE, SIZE, buf.data());
    std::cout << "Render " << z << " " << x << " " << y << " " << filename << "\n";
    save_to_file(vw, tmp, "png256");
    if (rename(tmp, filename)) {
        perror(tmp);
        return 0;
    }

    return 1;
}

int main(int argc, char *argv[])
{
	int zoom = 10;
	int z2 = 11;
	double lat = -0.567364021723;
	double lon = 38.8437697286;
#define HOME "/home/balrog"
	const char *mapfile = HOME "/wp/db/render/osm.xml";
	const char *tiledir = HOME "/wp/db/render/tiles/";
	const char *plugdir = "/usr/local/lib/mapnik/input/";
	const char *fontdir = "/usr/local/lib/mapnik/fonts";

	datasource_cache::instance()->register_datasources(plugdir);
	load_fonts(fontdir, 1);

	SphericalProjection(zoom + 1).fromLLtoPixel(lat, lon, zoom);

	int x, y;
	int x0 = lat / SIZE;
	int y0 = lon / SIZE;
	int x1 = x + 1;
	int y1 = y + 1;

	if (argc == 7) {
		zoom = strtol(argv[1], NULL, 0);
		z2 = strtol(argv[2], NULL, 0);
		x0 = strtol(argv[3], NULL, 0);
		y0 = strtol(argv[4], NULL, 0);
		x1 = strtol(argv[5], NULL, 0);
		y1 = strtol(argv[6], NULL, 0);
	}

	Map map(RENDER_SIZE, RENDER_SIZE);
	load_map(map, mapfile, true);

	projection prj(map.srs());
	for (; zoom < z2; zoom ++) {
		printf("abc ---> z %i (%i - %i)\n", zoom, zoom, z2);////
		tiling = new SphericalProjection(zoom + 1);
		for (x = x0; x < x1; x ++)
			for (y = y0; y < y1; y ++)
				if (!render(map, tiledir, prj, x, y, zoom))
					std::cout << "failed" << std::endl;
		delete tiling;
		x0 *= 2;
		y0 *= 2;
		x1 *= 2;
		y1 *= 2;
	}
#if 0
	/* Change the following for different bounding boxes and zoom levels */
	bbox = (21.00895, 52.22843, 21.02149, 52.23651)
	render_tiles(bbox, mapfile, tile_dir, 17, 18, "World")
#endif
}
