// shputil.c -- contains routines and datatypes for reading the SHP format
//
// Written by Eric C. Peterson, licensed under UoI/NCSA.
// Revision history:
//      + 2009 May 13 : Clean up, public release on mw2.jjaro.net
//      + 2006 Oct 27 : Initial version written

#include <stdlib.h>
#include <stdio.h>

// macros

#define isOdd(A)    ((A) % 2)
#define isEven(A)   (!isOdd(A))

// public datatypes

typedef struct {
    unsigned char r, g, b, a;
} RGBA;

typedef struct {
    short xbound, ybound;
    short xorigin, yorigin;
    long xmin, ymin, xmax, ymax;
    int bitmap_size;
    char *bitmap;
} SHP_IMAGE;

typedef struct {
    int number_of_shapes;
    SHP_IMAGE *shapes;
} SHP_OBJECT;

// private datatypes

typedef struct {
    long shape; // offset from beginning of file to this shape
    long zero;  // always seems to be zero
} SHP_OFFSET;

typedef struct {
    char version[4]; // four-character string containing version information
    long shapes_in_file; // number of shapes in this file
    SHP_OFFSET offsets[]; // a table of offsets within the file
} SHP_HEADER;

// the token data type (also private)
//
// if the token member is zero, then this token is an end token for the line
// if the token member is one, then this token is a skip token.  the argument
//      is then the number of pixels we should skip over.
// if the token number is of form 2n, then this token is a run token, and
//      n is the number of pixels in the run and the argument is the pixel
//      color (index into the palette).
// if the token number is of form 2n+1, then this token is a string token,
//      and n is the number of pixels in the string and the argument is a
//      string of bytes that are indices into the palette
typedef struct {
    Uchar token;
    Uchar argument;
} SHP_TOKEN;

typedef struct {
    short ybound;    // the x dimension of the image
    short xbound;    // the y dimension of the image
    short yorigin;   // the "hot spot" of the image -- translations are done
    short xorigin;   // about this point.
    long  xmin;       // the left-most   pixel coorindate in the shape
    long  ymin;       // the top-most    pixel coordinate in the shape
    long  xmax;       // the right-most  pixel coordinate in the shape
    long  ymax;       // the bottom-most pixel coodinrate in the shape
    SHP_TOKEN data[]; // then finally all the packet data
} SHP_SHAPE;

// SHPs expand to 256-paletted bitmaps, so put the palette here for us to use
RGBA palette[256];

// this publicly accessible routine takes an SHP file's data in memory and
// allocates and produces an SHP_OBJECT with all the appropriate SHPs parsed
// and converted into OpenGL-readable bitmaps.
SHP_OBJECT* OpenSHPObject(void* memory) {
    SHP_HEADER* file_in_memory = memory;
    SHP_OBJECT* object_to_return = NULL;
    int i;

    // if we were passed a bad pointer, bail
    if (memory == NULL)
        return NULL;

    // check the version string - should be 1.10
    if (!((file_in_memory->version[0] == '1') &&
          (file_in_memory->version[1] == '.') &&
          (file_in_memory->version[2] == '1') &&
          (file_in_memory->version[3] == '0')))
        return NULL;

    // apart from the magic number and the null pointer check, we're going to
    // have to assume that this is a legal file, so we should alloc the memory
    // required for our brand new SHP_OBJECT and then start pumping it with data
    if ((object_to_return = calloc(1, sizeof(SHP_OBJECT))) == NULL)
        return NULL; // fail!

    // set the number of shapes that we have to load
    object_to_return->number_of_shapes = file_in_memory->shapes_in_file;

    // allocate enough shape objects to hold them, bail if we fail to do so
    if ((object_to_return->shapes = calloc(file_in_memory->shapes_in_file,
                                           sizeof(SHP_IMAGE))) == NULL) {
        free(object_to_return);
        return NULL;
    }

    // for each shape in the file (with index i...)
    for (i = 0; i < object_to_return->number_of_shapes; i++) {
        // make a couple variables for the parser below
        unsigned char *pos_in_file;
        int cur_line;
        RGBA *pos_in_mem;
        // make a variable for shorthand
        SHP_IMAGE* current_image = &(object_to_return->shapes[i]);
        SHP_SHAPE* current_shape = (SHP_SHAPE*)((char*)file_in_memory +
                                            file_in_memory->offsets[i].shape);

        // initialize our image object with all the appropriate header
        // information from the shape entry in the SHP file
        current_image->xbound  = current_shape->xbound;
        current_image->ybound  = current_shape->ybound;
        current_image->xorigin = current_shape->xorigin;
        current_image->yorigin = current_shape->yorigin;
        current_image->xmin    = current_shape->xmin;
        current_image->ymin    = current_shape->ymin;
        current_image->xmax    = current_shape->xmax;
        current_image->ymax    = current_shape->ymax;

        // now initialize the size of our bitmap, then allocate it.
        // TODO: check for failure
        current_image->bitmap_size =   (current_image->xbound + 1) *
                                       (current_image->ybound + 1) *
                                       sizeof(RGBA);
        current_image->bitmap = calloc((current_image->xbound + 1) *
                                       (current_image->ybound + 1),
                                       sizeof(RGBA));

        // set up some variables for the token parser
        pos_in_file = (unsigned char*)(&(current_shape->data));
        pos_in_mem  = (RGBA*)current_image->bitmap;
        cur_line    = current_shape->ybound + 1;

        while (cur_line) {
            // tokens can be of four types, and we will test for each below
            if (*pos_in_file == 0) {
                // if the token is a zero, then this is the end of the line,
                // and we should decrement cur_line by one and forward the token
                // marker by one.
                //
                // TODO: note that whole empty lines within the shape are
                // communicated with a singular end token. not sure how to check
                // that yet. :/
                int offset;

                // prepare the offset jump
                offset = (int)(current_shape->ybound) + 2 - (int)(cur_line);
                offset *= (int)(current_shape->xbound) + 1;

                // forward to the next line
                pos_in_file++;
                pos_in_mem = (RGBA*)(current_image->bitmap) + offset;
                cur_line--;
            } else if (*pos_in_file == 1) {
                // if the token is a one, then this is a skip token.  the
                // byte that follows the skip token is the argument byte,
                // and we skip that many pixels ahead (implicitly filling with
                // transparent values as we go).
                pos_in_file++;              // we're done with the token byte
                pos_in_mem += *pos_in_file; // this is how far we have to skip
                pos_in_file++;              // and now we're done with this byte
            } else if (isEven(*pos_in_file)) {
                // if the token is non-zero and even, then this is a run token.
                // run tokens have a pixel count and a pixel color associated
                // with them.  we write that palette color to the bitmap count
                // times, which allows for RLE compression within the SHP file.
                int count, pixel;

                count = *pos_in_file / 2;   // the token contains how many
                                            // pixels we have to write, and
                                            // save that info.
                pos_in_file++;              // -- next byte --
                pixel = *pos_in_file;       // the argument character is what
                                            // pixel we have to write.  save
                                            // that info.
                pos_in_file++;              // -- next byte --

                // loop count times, storing the color to memory each time
                while (count--) {
                    *pos_in_mem = palette[pixel];
                    pos_in_mem++;
                }

            } else if (isOdd(*pos_in_file)) {
                // if the token is not one but is odd, then this is a string
                // token.  we simply expand the count number of bytes that
                // follow the initial token identifier into RGBA values and
                // then write them sequentially to the bitmap.

                int count = (*pos_in_file - 1) / 2; // save the count to write
                pos_in_file++;                      // advance the file marker

                for ( ; count--; ) {
                    // grab the color out of the file and then out of the
                    // palette, store to memory,
                    *pos_in_mem = palette[*pos_in_file];
                    // then advance the markers.
                    pos_in_file++;
                    pos_in_mem++;
                }

            } else {
                // we shouldn't ever get here.
                printf("ERROR LOADING SHP FILE.\nCURRENT TOKEN: %d",
                       *pos_in_file);
            }
        }
    }

    return object_to_return;
}

SHP_OBJECT* CloseSHPObject(SHP_OBJECT* shpfile) {
    int i;

    if (shpfile == NULL)
        return NULL;

    if (shpfile->shapes != NULL) {
        for (i = 0; i < shpfile->number_of_shapes; i++)
            if (shpfile->shapes[i].bitmap)
                free(shpfile->shapes[i].bitmap);

        free(shpfile->shapes);
    }

    free(shpfile);

    return NULL;
}
