#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <grass/G3d.h>

/*---------------------------------------------------------------------------*/

static void
retileNocache(void *map, const char *nameOut, int tileX, int tileY, int tileZ)
{
    void *map2;
    int x, y, z, saveType, nx, ny, nz;
    int typeIntern;
    void *data;
    int tileXsave, tileYsave, tileZsave;
    G3D_Region region;

    saveType = G3d_getFileType();
    G3d_setFileType(G3d_fileTypeMap(map));
    G3d_getTileDimension(&tileXsave, &tileYsave, &tileZsave);
    G3d_setTileDimension(tileX, tileY, tileZ);
    typeIntern = G3d_tileTypeMap(map);
    G3d_getRegionStructMap(map, &region);

    map2 = G3d_openCellNew(nameOut, typeIntern, G3D_NO_CACHE, &region);

    if (map2 == NULL)
	G3d_fatalError("G3d_retile: error in G3d_openCellNew");

    G3d_setFileType(saveType);
    G3d_setTileDimension(tileXsave, tileYsave, tileZsave);

    data = G3d_allocTiles(map2, 1);
    if (data == NULL)
	G3d_fatalError("G3d_retile: error in G3d_allocTiles");

    G3d_getNofTilesMap(map2, &nx, &ny, &nz);

    for (z = 0; z < nz; z++)
	for (y = 0; y < ny; y++)
	    for (x = 0; x < nx; x++) {

		G3d_getBlock(map, x * tileX, y * tileY, z * tileZ,
			     tileX, tileY, tileZ, data, typeIntern);
		if (!G3d_writeTile
		    (map2, G3d_tile2tileIndex(map2, x, y, z), data,
		     typeIntern))
		    G3d_fatalError
			("G3d_retileNocache: error in G3d_writeTile");
	    }

    G3d_freeTiles(data);
    G3d_closeCell(map2);
}

/*---------------------------------------------------------------------------*/


/*!
 * \brief 
 *
 * Makes a copy of <em>map</em> with name <em>nameOut</em> which has
 * tile dimensions <em>tileX</em>, <em>tileY</em>, <em>tileZ</em>.
 * The source code can be found in <em>retile.c</em>.
 *
 *  \param map
 *  \param nameOut
 *  \param tileX
 *  \param tileY
 *  \param tileZ
 *  \return void
 */

void
G3d_retile(void *map, const char *nameOut, int tileX, int tileY, int tileZ)
{
    void *map2;
    double value;
    int x, y, z, saveType;
    int rows, cols, depths, typeIntern;
    int xTile, yTile, zTile;
    int xOffs, yOffs, zOffs, prev;
    int tileXsave, tileYsave, tileZsave;
    G3D_Region region;

    if (!G3d_tileUseCacheMap(map)) {
	retileNocache(map, nameOut, tileX, tileY, tileZ);
	return;
    }

    saveType = G3d_getFileType();
    G3d_setFileType(G3d_fileTypeMap(map));
    G3d_getTileDimension(&tileXsave, &tileYsave, &tileZsave);
    G3d_setTileDimension(tileX, tileY, tileZ);

    typeIntern = G3d_tileTypeMap(map);
    G3d_getRegionStructMap(map, &region);

    map2 =
	G3d_openCellNew(nameOut, typeIntern, G3D_USE_CACHE_DEFAULT, &region);
    if (map2 == NULL)
	G3d_fatalError("G3d_retile: error in G3d_openCellNew");

    G3d_setFileType(saveType);
    G3d_setTileDimension(tileXsave, tileYsave, tileZsave);

    G3d_coord2tileCoord(map2, 0, 0, 0,
			&xTile, &yTile, &zTile, &xOffs, &yOffs, &zOffs);

    prev = zTile;

    x = 0;
    y = 0;

    G3d_getCoordsMap(map, &rows, &cols, &depths);

    for (z = 0; z < depths; z++) {
	G3d_coord2tileCoord(map2, x, y, z, &xTile, &yTile, &zTile,
			    &xOffs, &yOffs, &zOffs);
	if (zTile > prev) {
	    if (!G3d_flushAllTiles(map2))
		G3d_fatalError("G3d_retile: error in G3d_flushAllTiles");
	    prev++;
	}

	for (y = 0; y < rows; y++)
	    for (x = 0; x < cols; x++) {

		G3d_getValueRegion(map, x, y, z, &value, typeIntern);
		if (!G3d_putValue(map2, x, y, z, &value, typeIntern))
		    G3d_fatalError("G3d_retile: error in G3d_putValue");
	    }
    }

    if (!G3d_flushAllTiles(map2))
	G3d_fatalError("G3d_retile: error in G3d_flushAllTiles");
    if (!G3d_closeCell(map2))
	G3d_fatalError("G3d_retile: error in G3d_closeCell");
}
