#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <grass/raster3d.h>
#include "raster3d_intern.h"

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

static int Rast3d_readIndex(RASTER3D_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) {
        Rast3d_error("Rast3d_readIndex: can't position file");
        return 0;
    }

    indexLength = indexLast - map->indexOffset;

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

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

    if (tmp == NULL) {
        Rast3d_error("Rast3d_readIndex: error in Rast3d_malloc");
        return 0;
    }

    /* The size of the tile index array in the map file */
    if (indexLength == map->indexLongNbytes * map->nTiles) {
        if (read(map->data_fd, tmp, indexLength) != indexLength) {
            Rast3d_error("Rast3d_readIndex: can't read file");
            Rast3d_free(tmp);
            return 0;
        }
    }
    else
        /* ATTENTION: RLE encoded reading is only supported for backward
         * compatibility */
        if (indexLength <
            map->indexLongNbytes * map->nTiles) { /* RLE encoded? */

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

                /*->index large enough? */
                tmp2 = Rast3d_malloc(indexLength);
                if (tmp2 == NULL) {
                    Rast3d_error("Rast3d_readIndex: error in Rast3d_malloc");
                    Rast3d_free(tmp);
                    return 0;
                }
            }
            else /* YES */
                tmp2 = (unsigned char *)map->index;

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

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

            if (indexLength > (int)sizeof(long) * map->nTiles)
                Rast3d_free(tmp2);
        } /* END RLE */

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

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

    Rast3d_free(tmp);

    return 1;
}

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

int Rast3d_flush_index(RASTER3D_Map *map)
{
    int 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) {
        Rast3d_error("Rast3d_flush_index: can't rewind file");
        return 0;
    }

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

    tmp = Rast3d_malloc(sizeof(long) * map->nTiles);
    if (tmp == NULL) {
        Rast3d_error("Rast3d_flush_index: error in Rast3d_malloc");
        return 0;
    }

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

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

    indexLength = map->nTiles * sizeof(long);
    if (write(map->data_fd, tmp, indexLength) != indexLength) {
        Rast3d_error("Rast3d_flush_index: can't write file");
        Rast3d_free(tmp);
        return 0;
    }

    Rast3d_free(tmp);
    if (!Rast3d_readIndex(map)) {
        Rast3d_error("Rast3d_flush_index: error in Rast3d_readIndex");
        return 0;
    }

    return 1;
}

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

static long *cmpIndex;

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

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

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

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

int Rast3d_init_index(RASTER3D_Map *map, int hasIndex)
{
    int tile;
    int i0, i1, i2, i3, i4, i5, nofElts;
    long offset;
    long *offsetP;

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

    if ((map->index == NULL) || (map->tileLength == NULL)) {
        Rast3d_error("Rast3d_init_index: error in Rast3d_malloc");
        return 0;
    }

    if (map->operation == RASTER3D_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 = Rast3d_compute_clipped_tile_dimensions(
                map, tile, &i0, &i1, &i2, &i3, &i4, &i5);
            map->tileLength[tile] = nofElts * map->numLengthExtern;
            offset += nofElts;
        }
        return 1;
    }

    if (!Rast3d_readIndex(map)) {
        Rast3d_error("Rast3d_init_index: error in Rast3d_readIndex");
        return 0;
    }

    offsetP = Rast3d_malloc(sizeof(long) * map->nTiles);
    if (offsetP == NULL) {
        Rast3d_error("Rast3d_init_index: error in Rast3d_malloc");
        return 0;
    }

    for (tile = 0; tile < map->nTiles; tile++)
        offsetP[tile] = tile;
    cmpIndex = map->index;
    qsort(offsetP, map->nTiles, sizeof(long), 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]];

    Rast3d_free(offsetP);

    return 1;
}
