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

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

static int G3d_readIndex(G3D_Map * map)
{
    unsigned char *tmp, *tmp2;
    int dummy1, dummy2, indexLength, tileIndex;
    long indexLast;

    indexLast = lseek(map->data_fd, (long)0, SEEK_END);
    if (indexLast == -1) {
	G3d_error("G3d_readIndex: can't position file");
	return 0;
    }

    indexLength = indexLast - map->indexOffset;

    if (lseek(map->data_fd, map->indexOffset, SEEK_SET) == -1) {
	G3d_error("G3d_readIndex: can't position file");
	return 0;
    }

    tmp = G3d_malloc(map->indexLongNbytes * map->nTiles);

    if (tmp == NULL) {
	G3d_error("G3d_readIndex: error in G3d_malloc");
	return 0;
    }

    if (indexLength < map->indexLongNbytes * map->nTiles) {	/* RLE encoded? */

	if (indexLength > sizeof(long) * map->nTiles) {

						     /*->index large enough? */
	    tmp2 = G3d_malloc(indexLength);
	    if (tmp2 == NULL) {
		G3d_error("G3d_readIndex: error in G3d_malloc");
		return 0;
	    }
	}
	else			/* YES */
	    tmp2 = (unsigned char *)map->index;

	if (read(map->data_fd, tmp2, indexLength) != indexLength) {
	    G3d_error("G3d_readIndex: can't read file");
	    return 0;
	}

	G_rle_decode(tmp2, tmp, map->indexLongNbytes * map->nTiles, 1,
		     &dummy1, &dummy2);

	if (indexLength > sizeof(long) * map->nTiles)
	    G3d_free(tmp2);
    }
    else /* NO RLE */ if (read(map->data_fd, tmp, indexLength) != indexLength) {
	G3d_error("G3d_readIndex: can't read file");
	return 0;
    }

    G3d_longDecode(tmp, map->index, map->nTiles, map->indexLongNbytes);

    for (tileIndex = 0; tileIndex < map->nTiles; tileIndex++)
	if (map->index[tileIndex] == 0)
	    map->index[tileIndex] = -1;

    G3d_free(tmp);

    return 1;
}

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

int G3d_flushIndex(G3D_Map * map)
{
    int sizeCompressed, indexLength, tileIndex;
    unsigned char *tmp;
    long ldummy;

    if (!map->hasIndex)
	return 1;

    map->indexOffset = lseek(map->data_fd, (long)0, SEEK_END);
    if (map->indexOffset == -1) {
	G3d_error("G3d_flushIndex: can't rewind file");
	return 0;
    }

    map->indexNbytesUsed = G3d_longEncode(&(map->indexOffset),
					  (unsigned char *)&ldummy, 1);

    tmp = G3d_malloc(sizeof(long) * map->nTiles);
    if (tmp == NULL) {
	G3d_error("G3d_flushIndex: error in G3d_malloc");
	return 0;
    }

    for (tileIndex = 0; tileIndex < map->nTiles; tileIndex++)
	if (map->index[tileIndex] == -1)
	    map->index[tileIndex] = 0;

    (void)G3d_longEncode(map->index, tmp, map->nTiles);

    sizeCompressed = G_rle_count_only(tmp, sizeof(long) * map->nTiles, 1);

    if (sizeCompressed >= map->nTiles * sizeof(long)) {
	indexLength = map->nTiles * sizeof(long);
	if (write(map->data_fd, tmp, indexLength) != indexLength) {
	    G3d_error("G3d_flushIndex: can't write file");
	    return 0;
	}
    }
    else {
	indexLength = sizeCompressed;
	G_rle_encode(tmp, (char *)map->index, sizeof(long) * map->nTiles, 1);
	if (write(map->data_fd, map->index, sizeCompressed) != sizeCompressed) {
	    G3d_error("G3d_flushIndex: can't write file");
	    return 0;
	}
    }

    G3d_free(tmp);
    if (!G3d_readIndex(map)) {
	G3d_error("G3d_flushIndex: error in G3d_readIndex");
	return 0;
    }

    return 1;
}

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

static long *cmpIndex;

static int indexSortCompare(const void *a, const void *b)
{
    long offset1, offset2;

    offset1 = cmpIndex[*((const int *)a)];
    offset2 = cmpIndex[*((const int *)b)];

    if (offset1 > offset2)
	return 1;
    if (offset1 < offset2)
	return -1;
    return 0;
}

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

int G3d_initIndex(G3D_Map * map, int hasIndex)
{
    int tile;
    int i0, i1, i2, i3, i4, i5, offset, nofElts;
    int *offsetP;

    map->hasIndex = hasIndex;
    map->index = G3d_malloc(sizeof(long) * map->nTiles);
    map->tileLength = G3d_malloc(sizeof(int) * map->nTiles);

    if ((map->index == NULL) || (map->tileLength == NULL)) {
	G3d_error("G3d_initIndex: error in G3d_malloc");
	return 0;
    }

    if (map->operation == G3D_WRITE_DATA) {
	for (tile = 0; tile < map->nTiles; tile++)
	    map->index[tile] = -1;
	return 1;
    }

    if (!map->hasIndex) {
	offset = 0;
	for (tile = 0; tile < map->nTiles; tile++) {
	    map->index[tile] = offset * map->numLengthExtern + map->offset;
	    nofElts = G3d_computeClippedTileDimensions
		(map, tile, &i0, &i1, &i2, &i3, &i4, &i5);
	    map->tileLength[tile] = nofElts * map->numLengthExtern;
	    offset += nofElts;
	}
	return 1;
    }

    if (!G3d_readIndex(map)) {
	G3d_error("G3d_initIndex: error in G3d_readIndex");
	return 0;
    }

    offsetP = G3d_malloc(sizeof(int) * map->nTiles);
    if (offsetP == NULL) {
	G3d_error("G3d_initIndex: error in G3d_malloc");
	return 0;
    }

    for (tile = 0; tile < map->nTiles; tile++)
	offsetP[tile] = tile;
    cmpIndex = map->index;
    qsort(offsetP, map->nTiles, sizeof(int), indexSortCompare);

    for (tile = 0; tile < map->nTiles - 1; tile++) {
	if (map->index[offsetP[tile]] == -1) {
	    map->tileLength[offsetP[tile]] = 0;
	    continue;
	}

	map->tileLength[offsetP[tile]] = map->index[offsetP[tile + 1]] -
	    map->index[offsetP[tile]];
    }

    if (map->index[offsetP[map->nTiles - 1]] == -1)
	map->tileLength[offsetP[map->nTiles - 1]] = 0;
    else
	map->tileLength[offsetP[map->nTiles - 1]] =
	    map->indexOffset - map->index[offsetP[map->nTiles - 1]];

    G3d_free(offsetP);

    return 1;
}
