/* ----------------------------------------------------------------------
 * RPly library, read/write PLY files
 * Diego Nehab, Princeton University
 * http://www.cs.princeton.edu/~diego/professional/rply
 *
 * This library is distributed under the MIT License. See notice
 * at the end of this file.
 * ---------------------------------------------------------------------- */
#include <stdio.h>
#include <ctype.h>
#include <assert.h>
#include <string.h>
#include <limits.h>
#include <float.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stddef.h>

#include "rply.h"

/* ----------------------------------------------------------------------
 * Constants 
 * ---------------------------------------------------------------------- */
#define WORDSIZE 256
#define LINESIZE 1024
#define BUFFERSIZE (8*1024)

typedef enum e_ply_io_mode_ {
    PLY_READ, 
    PLY_WRITE
} e_ply_io_mode;

static const char *const ply_storage_mode_list[] = {
    "binary_big_endian", "binary_little_endian", "ascii", NULL
};     /* order matches e_ply_storage_mode enum */

static const char *const ply_type_list[] = {
    "int8", "uint8", "int16", "uint16", 
    "int32", "uint32", "float32", "float64",
    "char", "uchar", "short", "ushort", 
    "int", "uint", "float", "double",
    "list", NULL
};     /* order matches e_ply_type enum */

/* ----------------------------------------------------------------------
 * Property reading callback argument
 *
 * element: name of element being processed
 * property: name of property being processed
 * nelements: number of elements of this kind in file
 * instance_index: index current element of this kind being processed
 * length: number of values in current list (or 1 for scalars)
 * value_index: index of current value int this list (or 0 for scalars)
 * value: value of property
 * pdata/idata: user data defined with ply_set_cb
 *
 * Returns handle to ply file if succesful, NULL otherwise.
 * ---------------------------------------------------------------------- */
typedef struct t_ply_argument_ {
    p_ply_element element;
    long instance_index;
    p_ply_property property;
    long length, value_index;
    double value;
    void *pdata;
    long idata;
} t_ply_argument;

/* ----------------------------------------------------------------------
 * Property information
 *
 * name: name of this property
 * type: type of this property (list or type of scalar value)
 * length_type, value_type: type of list property count and values
 * read_cb: function to be called when this property is called
 *
 * Returns 1 if should continue processing file, 0 if should abort.
 * ---------------------------------------------------------------------- */
typedef struct t_ply_property_ {
    char name[WORDSIZE];
    e_ply_type type, value_type, length_type;
    p_ply_read_cb read_cb;
    void *pdata;
    long idata;
} t_ply_property; 

/* ----------------------------------------------------------------------
 * Element information
 *
 * name: name of this property
 * ninstances: number of elements of this type in file
 * property: property descriptions for this element
 * nproperty: number of properties in this element
 *
 * Returns 1 if should continue processing file, 0 if should abort.
 * ---------------------------------------------------------------------- */
typedef struct t_ply_element_ {
    char name[WORDSIZE];
    long ninstances;
    p_ply_property property;
    long nproperties;
} t_ply_element;

/* ----------------------------------------------------------------------
 * Input/output driver
 *
 * Depending on file mode, different functions are used to read/write 
 * property fields. The drivers make it transparent to read/write in ascii, 
 * big endian or little endian cases.
 * ---------------------------------------------------------------------- */
typedef int (*p_ply_ihandler)(p_ply ply, double *value);
typedef int (*p_ply_ichunk)(p_ply ply, void *anydata, size_t size);
typedef struct t_ply_idriver_ {
    p_ply_ihandler ihandler[16];
    p_ply_ichunk ichunk;
    const char *name;
} t_ply_idriver;
typedef t_ply_idriver *p_ply_idriver;

typedef int (*p_ply_ohandler)(p_ply ply, double value);
typedef int (*p_ply_ochunk)(p_ply ply, void *anydata, size_t size);
typedef struct t_ply_odriver_ {
    p_ply_ohandler ohandler[16];
    p_ply_ochunk ochunk;
    const char *name;
} t_ply_odriver;
typedef t_ply_odriver *p_ply_odriver;

/* ----------------------------------------------------------------------
 * Ply file handle. 
 *
 * io_mode: read or write (from e_ply_io_mode)
 * storage_mode: mode of file associated with handle (from e_ply_storage_mode)
 * element: elements description for this file
 * nelement: number of different elements in file
 * comment: comments for this file
 * ncomments: number of comments in file
 * obj_info: obj_info items for this file
 * nobj_infos: number of obj_info items in file
 * fp: file pointer associated with ply file
 * c: last character read from ply file
 * buffer: last word/chunck of data read from ply file
 * buffer_first, buffer_last: interval of untouched good data in buffer
 * buffer_token: start of parsed token (line or word) in buffer
 * idriver, odriver: input driver used to get property fields from file 
 * argument: storage space for callback arguments
 * welement, wproperty: element/property type being written
 * winstance_index: index of instance of current element being written
 * wvalue_index: index of list property value being written 
 * wlength: number of values in list property being written
 * error_cb: callback for error messages
 * ---------------------------------------------------------------------- */
typedef struct t_ply_ {
    e_ply_io_mode io_mode;
    e_ply_storage_mode storage_mode;
    p_ply_element element;
    long nelements;
    char *comment;
    long ncomments;
    char *obj_info;
    long nobj_infos;
    FILE *fp;
    int c;
    char buffer[BUFFERSIZE];
    size_t buffer_first, buffer_token, buffer_last;
    p_ply_idriver idriver;
    p_ply_odriver odriver;
    t_ply_argument argument;
    long welement, wproperty;
    long winstance_index, wvalue_index, wlength;
    p_ply_error_cb error_cb;
} t_ply;

/* ----------------------------------------------------------------------
 * I/O functions and drivers
 * ---------------------------------------------------------------------- */
static t_ply_idriver ply_idriver_ascii;
static t_ply_idriver ply_idriver_binary;
static t_ply_idriver ply_idriver_binary_reverse;
static t_ply_odriver ply_odriver_ascii;
static t_ply_odriver ply_odriver_binary;
static t_ply_odriver ply_odriver_binary_reverse;

static int ply_read_word(p_ply ply);
static int ply_check_word(p_ply ply);
static int ply_read_line(p_ply ply);
static int ply_check_line(p_ply ply);
static int ply_read_chunk(p_ply ply, void *anybuffer, size_t size);
static int ply_read_chunk_reverse(p_ply ply, void *anybuffer, size_t size);
static int ply_write_chunk(p_ply ply, void *anybuffer, size_t size);
static int ply_write_chunk_reverse(p_ply ply, void *anybuffer, size_t size);
static void ply_reverse(void *anydata, size_t size);

/* ----------------------------------------------------------------------
 * String functions
 * ---------------------------------------------------------------------- */
static int ply_find_string(const char *item, const char* const list[]);
static p_ply_element ply_find_element(p_ply ply, const char *name);
static p_ply_property ply_find_property(p_ply_element element, 
        const char *name);

/* ----------------------------------------------------------------------
 * Header parsing
 * ---------------------------------------------------------------------- */
static int ply_read_header_format(p_ply ply);
static int ply_read_header_comment(p_ply ply);
static int ply_read_header_obj_info(p_ply ply);
static int ply_read_header_property(p_ply ply);
static int ply_read_header_element(p_ply ply);

/* ----------------------------------------------------------------------
 * Error handling
 * ---------------------------------------------------------------------- */
static void ply_error_cb(const char *message);
static void ply_error(p_ply ply, const char *fmt, ...);

/* ----------------------------------------------------------------------
 * Memory allocation and initialization
 * ---------------------------------------------------------------------- */
static void ply_init(p_ply ply);
static void ply_element_init(p_ply_element element);
static void ply_property_init(p_ply_property property);
static p_ply ply_alloc(void);
static p_ply_element ply_grow_element(p_ply ply);
static p_ply_property ply_grow_property(p_ply ply, p_ply_element element);
static void *ply_grow_array(p_ply ply, void **pointer, long *nmemb, long size);

/* ----------------------------------------------------------------------
 * Special functions
 * ---------------------------------------------------------------------- */
static e_ply_storage_mode ply_arch_endian(void);
static int ply_type_check(void); 

/* ----------------------------------------------------------------------
 * Auxiliary read functions
 * ---------------------------------------------------------------------- */
static int ply_read_element(p_ply ply, p_ply_element element, 
        p_ply_argument argument);
static int ply_read_property(p_ply ply, p_ply_element element, 
        p_ply_property property, p_ply_argument argument);
static int ply_read_list_property(p_ply ply, p_ply_element element, 
        p_ply_property property, p_ply_argument argument);
static int ply_read_scalar_property(p_ply ply, p_ply_element element, 
        p_ply_property property, p_ply_argument argument);


/* ----------------------------------------------------------------------
 * Buffer support functions
 * ---------------------------------------------------------------------- */
/* pointers to tokenized word and line in buffer */
#define BWORD(p) (p->buffer + p->buffer_token)
#define BLINE(p) (p->buffer + p->buffer_token)

/* pointer to start of untouched bytes in buffer */
#define BFIRST(p) (p->buffer + p->buffer_first) 

/* number of bytes untouched in buffer */
#define BSIZE(p) (p->buffer_last - p->buffer_first) 

/* consumes data from buffer */
#define BSKIP(p, s) (p->buffer_first += s)

/* refills the buffer */
static int BREFILL(p_ply ply) {
    /* move untouched data to beginning of buffer */
    size_t size = BSIZE(ply);
    memmove(ply->buffer, BFIRST(ply), size);
    ply->buffer_last = size;
    ply->buffer_first = ply->buffer_token = 0;
    /* fill remaining with new data */
    size = fread(ply->buffer+size, 1, BUFFERSIZE-size-1, ply->fp);
    /* place sentinel so we can use str* functions with buffer */
    ply->buffer[BUFFERSIZE-1] = '\0';
    /* check if read failed */
    if (size <= 0) return 0;
    /* increase size to account for new data */
    ply->buffer_last += size;
    return 1;
}

/* ----------------------------------------------------------------------
 * Exported functions
 * ---------------------------------------------------------------------- */
/* ----------------------------------------------------------------------
 * Read support functions
 * ---------------------------------------------------------------------- */
p_ply ply_open(const char *name, p_ply_error_cb error_cb) {
    char magic[5] = "    ";
    FILE *fp = NULL; 
    p_ply ply = NULL;
    if (error_cb == NULL) error_cb = ply_error_cb;
    if (!ply_type_check()) {
        error_cb("Incompatible type system");
        return NULL;
    }
    assert(name);
    fp = fopen(name, "rb");
    if (!fp) {
        error_cb("Unable to open file");
        return NULL;
    }
    if (fread(magic, 1, 4, fp) < 4) {
        error_cb("Error reading from file");
        fclose(fp);
        return NULL;
    }
    if (strcmp(magic, "ply\n")) {
        fclose(fp);
        error_cb("Not a PLY file. Expected magic number 'ply\\n'");
        return NULL;
    }
    ply = ply_alloc();
    if (!ply) {
        error_cb("Out of memory");
        fclose(fp);
        return NULL;
    }
    ply->fp = fp;
    ply->io_mode = PLY_READ;
    ply->error_cb = error_cb;
    return ply;
}

int ply_read_header(p_ply ply) {
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    if (!ply_read_word(ply)) return 0;
    /* parse file format */
    if (!ply_read_header_format(ply)) {
        ply_error(ply, "Invalid file format");
        return 0;
    }
    /* parse elements, comments or obj_infos until the end of header */
    while (strcmp(BWORD(ply), "end_header")) {
        if (!ply_read_header_comment(ply) && 
                !ply_read_header_element(ply) && 
                !ply_read_header_obj_info(ply)) {
            ply_error(ply, "Unexpected token '%s'", BWORD(ply));
            return 0;
        }
    }
    return 1;
}

long ply_set_read_cb(p_ply ply, const char *element_name, 
        const char* property_name, p_ply_read_cb read_cb, 
        void *pdata, long idata) {
    p_ply_element element = NULL; 
    p_ply_property property = NULL;
    assert(ply && element_name && property_name);
    element = ply_find_element(ply, element_name);
    if (!element) return 0;
    property = ply_find_property(element, property_name);
    if (!property) return 0;
    property->read_cb = read_cb;
    property->pdata = pdata;
    property->idata = idata;
    return (int) element->ninstances;
}

int ply_read(p_ply ply) {
    long i;
    p_ply_argument argument;
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    argument = &ply->argument;
    /* for each element type */
    for (i = 0; i < ply->nelements; i++) {
        p_ply_element element = &ply->element[i];
        argument->element = element;
        if (!ply_read_element(ply, element, argument))
            return 0;
    }
    return 1;
}

/* ----------------------------------------------------------------------
 * Write support functions
 * ---------------------------------------------------------------------- */
p_ply ply_create(const char *name, e_ply_storage_mode storage_mode, 
        p_ply_error_cb error_cb) {
    FILE *fp = NULL;
    p_ply ply = NULL;
    if (error_cb == NULL) error_cb = ply_error_cb;
    if (!ply_type_check()) {
        error_cb("Incompatible type system");
        return NULL;
    }
    assert(name && storage_mode <= PLY_DEFAULT);
    fp = fopen(name, "wb");
    if (!fp) {
        error_cb("Unable to create file");
        return NULL;
    }
    ply = ply_alloc();
    if (!ply) {
        fclose(fp);
        error_cb("Out of memory");
        return NULL;
    }
    ply->io_mode = PLY_WRITE;
    if (storage_mode == PLY_DEFAULT) storage_mode = ply_arch_endian();
    if (storage_mode == PLY_ASCII) ply->odriver = &ply_odriver_ascii;
    else if (storage_mode == ply_arch_endian()) 
        ply->odriver = &ply_odriver_binary;
    else ply->odriver = &ply_odriver_binary_reverse;
    ply->storage_mode = storage_mode;
    ply->fp = fp;
    ply->error_cb = error_cb;
    return ply;
}

int ply_add_element(p_ply ply, const char *name, long ninstances) {
    p_ply_element element = NULL;
    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);
    assert(name && strlen(name) < WORDSIZE && ninstances >= 0);
    if (strlen(name) >= WORDSIZE || ninstances < 0) {
        ply_error(ply, "Invalid arguments");
        return 0;
    }
    element = ply_grow_element(ply);
    if (!element) return 0;
    strcpy(element->name, name);
    element->ninstances = ninstances;
    return 1;
}

int ply_add_scalar_property(p_ply ply, const char *name, e_ply_type type) {
    p_ply_element element = NULL;
    p_ply_property property = NULL;
    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);
    assert(name && strlen(name) < WORDSIZE);
    assert(type < PLY_LIST);
    if (strlen(name) >= WORDSIZE || type >= PLY_LIST) {
        ply_error(ply, "Invalid arguments");
        return 0;
    }
    element = &ply->element[ply->nelements-1];
    property = ply_grow_property(ply, element);
    if (!property) return 0;
    strcpy(property->name, name);
    property->type = type;
    return 1;
}

int ply_add_list_property(p_ply ply, const char *name, 
        e_ply_type length_type, e_ply_type value_type) {
    p_ply_element element = NULL;
    p_ply_property property = NULL;
    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);
    assert(name && strlen(name) < WORDSIZE);
    if (strlen(name) >= WORDSIZE) {
        ply_error(ply, "Invalid arguments");
        return 0;
    }
    assert(length_type < PLY_LIST);
    assert(value_type < PLY_LIST);
    if (length_type >= PLY_LIST || value_type >= PLY_LIST) {
        ply_error(ply, "Invalid arguments");
        return 0;
    }
    element = &ply->element[ply->nelements-1];
    property = ply_grow_property(ply, element);
    if (!property) return 0;
    strcpy(property->name, name);
    property->type = PLY_LIST;
    property->length_type = length_type;
    property->value_type = value_type;
    return 1;
}

int ply_add_property(p_ply ply, const char *name, e_ply_type type,
        e_ply_type length_type, e_ply_type value_type) {
    if (type == PLY_LIST) 
        return ply_add_list_property(ply, name, length_type, value_type);
    else 
        return ply_add_scalar_property(ply, name, type);
}

int ply_add_comment(p_ply ply, const char *comment) {
    char *new_comment = NULL;
    assert(ply && comment && strlen(comment) < LINESIZE);
    if (!comment || strlen(comment) >= LINESIZE) {
        ply_error(ply, "Invalid arguments");
        return 0;
    }
    new_comment = (char *) ply_grow_array(ply, (void **) &ply->comment,
            &ply->ncomments, LINESIZE);
    if (!new_comment) return 0;
    strcpy(new_comment, comment);
    return 1;
}

int ply_add_obj_info(p_ply ply, const char *obj_info) {
    char *new_obj_info = NULL;
    assert(ply && obj_info && strlen(obj_info) < LINESIZE);
    if (!obj_info || strlen(obj_info) >= LINESIZE) {
        ply_error(ply, "Invalid arguments");
        return 0;
    }
    new_obj_info = (char *) ply_grow_array(ply, (void **) &ply->obj_info,
            &ply->nobj_infos, LINESIZE);
    if (!new_obj_info) return 0;
    strcpy(new_obj_info, obj_info);
    return 1;
}

int ply_write_header(p_ply ply) {
    long i, j;
    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);
    assert(ply->element || ply->nelements == 0); 
    assert(!ply->element || ply->nelements > 0); 
    if (fprintf(ply->fp, "ply\nformat %s 1.0\n", 
                ply_storage_mode_list[ply->storage_mode]) <= 0) goto error;
    for (i = 0; i < ply->ncomments; i++)
        if (fprintf(ply->fp, "comment %s\n", ply->comment + LINESIZE*i) <= 0)
            goto error;
    for (i = 0; i < ply->nobj_infos; i++)
        if (fprintf(ply->fp, "obj_info %s\n", ply->obj_info + LINESIZE*i) <= 0)
            goto error;
    for (i = 0; i < ply->nelements; i++) {
        p_ply_element element = &ply->element[i];
        assert(element->property || element->nproperties == 0); 
        assert(!element->property || element->nproperties > 0); 
        if (fprintf(ply->fp, "element %s %ld\n", element->name, 
                    element->ninstances) <= 0) goto error;
        for (j = 0; j < element->nproperties; j++) {
            p_ply_property property = &element->property[j];
            if (property->type == PLY_LIST) {
                if (fprintf(ply->fp, "property list %s %s %s\n", 
                            ply_type_list[property->length_type],
                            ply_type_list[property->value_type],
                            property->name) <= 0) goto error;
            } else {
                if (fprintf(ply->fp, "property %s %s\n", 
                            ply_type_list[property->type],
                            property->name) <= 0) goto error;
            }
        }
    }
    return fprintf(ply->fp, "end_header\n") > 0;
error:
    ply_error(ply, "Error writing to file");
    return 0;
}

int ply_write(p_ply ply, double value) {
    p_ply_element element = NULL;
    p_ply_property property = NULL;
    int type = -1;
    int breakafter = 0;
    if (ply->welement > ply->nelements) return 0;
    element = &ply->element[ply->welement];
    if (ply->wproperty > element->nproperties) return 0;
    property = &element->property[ply->wproperty];
    if (property->type == PLY_LIST) {
        if (ply->wvalue_index == 0) {
            type = property->length_type;
            ply->wlength = (long) value;
        } else type = property->value_type;
    } else {
        type = property->type;
        ply->wlength = 0;
    }
    if (!ply->odriver->ohandler[type](ply, value)) {
        ply_error(ply, "Failed writing %s of %s %d (%s: %s)", 
                    property->name, element->name, 
                    ply->winstance_index, 
                    ply->odriver->name, ply_type_list[type]);
        return 0;
    }
    ply->wvalue_index++;
    if (ply->wvalue_index > ply->wlength) {
        ply->wvalue_index = 0;
        ply->wproperty++;
    }
    if (ply->wproperty >= element->nproperties) {
        ply->wproperty = 0;
        ply->winstance_index++;
        if (ply->storage_mode == PLY_ASCII) breakafter = 1;
    }
    if (ply->winstance_index >= element->ninstances) {
        ply->winstance_index = 0;
        ply->welement++;
    }
    return !breakafter || putc('\n', ply->fp) > 0;
}

int ply_close(p_ply ply) {
    long i;
    assert(ply && ply->fp);
    assert(ply->element || ply->nelements == 0);
    assert(!ply->element || ply->nelements > 0);
    /* write last chunk to file */
    if (ply->io_mode == PLY_WRITE && 
      fwrite(ply->buffer, 1, ply->buffer_last, ply->fp) < ply->buffer_last) {
        ply_error(ply, "Error closing up");
        return 0;
    }
    fclose(ply->fp);
    /* free all memory used by handle */
    if (ply->element) {
        for (i = 0; i < ply->nelements; i++) {
            p_ply_element element = &ply->element[i];
            if (element->property) free(element->property);
        }
        free(ply->element);
    }
    if (ply->obj_info) free(ply->obj_info);
    if (ply->comment) free(ply->comment);
    free(ply);
    return 1;
}

/* ----------------------------------------------------------------------
 * Query support functions
 * ---------------------------------------------------------------------- */
p_ply_element ply_get_next_element(p_ply ply, 
        p_ply_element last) {
    assert(ply);
    if (!last) return ply->element;
    last++;
    if (last < ply->element + ply->nelements) return last;
    else return NULL;
}

int ply_get_element_info(p_ply_element element, const char** name,
        long *ninstances) {
    assert(element);
    if (name) *name = element->name;
    if (ninstances) *ninstances = (long) element->ninstances;
    return 1;
}

p_ply_property ply_get_next_property(p_ply_element element, 
        p_ply_property last) {
    assert(element);
    if (!last) return element->property;
    last++;
    if (last < element->property + element->nproperties) return last;
    else return NULL;
}

int ply_get_property_info(p_ply_property property, const char** name,
        e_ply_type *type, e_ply_type *length_type, e_ply_type *value_type) {
    assert(property);
    if (name) *name = property->name;
    if (type) *type = property->type;
    if (length_type) *length_type = property->length_type;
    if (value_type) *value_type = property->value_type;
    return 1;

}

const char *ply_get_next_comment(p_ply ply, const char *last) {
    assert(ply);
    if (!last) return ply->comment; 
    last += LINESIZE;
    if (last < ply->comment + LINESIZE*ply->ncomments) return last;
    else return NULL;
}

const char *ply_get_next_obj_info(p_ply ply, const char *last) {
    assert(ply);
    if (!last) return ply->obj_info; 
    last += LINESIZE;
    if (last < ply->obj_info + LINESIZE*ply->nobj_infos) return last;
    else return NULL;
}

/* ----------------------------------------------------------------------
 * Callback argument support functions 
 * ---------------------------------------------------------------------- */
int ply_get_argument_element(p_ply_argument argument, 
        p_ply_element *element, long *instance_index) {
    assert(argument);
    if (!argument) return 0;
    if (element) *element = argument->element;
    if (instance_index) *instance_index = argument->instance_index;
    return 1;
}

int ply_get_argument_property(p_ply_argument argument, 
        p_ply_property *property, long *length, long *value_index) {
    assert(argument);
    if (!argument) return 0;
    if (property) *property = argument->property;
    if (length) *length = argument->length;
    if (value_index) *value_index = argument->value_index;
    return 1;
}

int ply_get_argument_user_data(p_ply_argument argument, void **pdata, 
        long *idata) {
    assert(argument);
    if (!argument) return 0;
    if (pdata) *pdata = argument->pdata;
    if (idata) *idata = argument->idata;
    return 1;
}

double ply_get_argument_value(p_ply_argument argument) {
    assert(argument);
    if (!argument) return 0.0;
    return argument->value;
}

/* ----------------------------------------------------------------------
 * Internal functions
 * ---------------------------------------------------------------------- */
static int ply_read_list_property(p_ply ply, p_ply_element element, 
        p_ply_property property, p_ply_argument argument) {
    int l;
    p_ply_read_cb read_cb = property->read_cb;
    p_ply_ihandler *driver = ply->idriver->ihandler; 
    /* get list length */
    p_ply_ihandler handler = driver[property->length_type];
    double length;
    if (!handler(ply, &length)) {
        ply_error(ply, "Error reading '%s' of '%s' number %d",
                property->name, element->name, argument->instance_index);
        return 0;
    }
    /* invoke callback to pass length in value field */
    argument->length = (long) length;
    argument->value_index = -1;
    argument->value = length;
    if (read_cb && !read_cb(argument)) {
        ply_error(ply, "Aborted by user");
        return 0;
    }
    /* read list values */
    handler = driver[property->value_type];
    /* for each value in list */
    for (l = 0; l < (long) length; l++) {
        /* read value from file */
        argument->value_index = l;
        if (!handler(ply, &argument->value)) {
            ply_error(ply, "Error reading value number %d of '%s' of "
                    "'%s' number %d", l+1, property->name, 
                    element->name, argument->instance_index);
            return 0;
        }
        /* invoke callback to pass value */
        if (read_cb && !read_cb(argument)) {
            ply_error(ply, "Aborted by user");
            return 0;
        }
    }
    return 1;
}

static int ply_read_scalar_property(p_ply ply, p_ply_element element, 
        p_ply_property property, p_ply_argument argument) {
    p_ply_read_cb read_cb = property->read_cb;
    p_ply_ihandler *driver = ply->idriver->ihandler; 
    p_ply_ihandler handler = driver[property->type];
    argument->length = 1;
    argument->value_index = 0;
    if (!handler(ply, &argument->value)) {
        ply_error(ply, "Error reading '%s' of '%s' number %d",
                property->name, element->name, argument->instance_index);
        return 0;
    }
    if (read_cb && !read_cb(argument)) {
        ply_error(ply, "Aborted by user");
        return 0;
    }
    return 1;
}

static int ply_read_property(p_ply ply, p_ply_element element, 
        p_ply_property property, p_ply_argument argument) {
    if (property->type == PLY_LIST) 
        return ply_read_list_property(ply, element, property, argument);
    else 
        return ply_read_scalar_property(ply, element, property, argument);
}

static int ply_read_element(p_ply ply, p_ply_element element, 
        p_ply_argument argument) {
    long j, k;
    /* for each element of this type */
    for (j = 0; j < element->ninstances; j++) {
        argument->instance_index = j;
        /* for each property */
        for (k = 0; k < element->nproperties; k++) {
            p_ply_property property = &element->property[k];
            argument->property = property;
            argument->pdata = property->pdata;
            argument->idata = property->idata;
            if (!ply_read_property(ply, element, property, argument))
                return 0;
        }
    }
    return 1;
}

static int ply_find_string(const char *item, const char* const list[]) {
    int i;
    assert(item && list);
    for (i = 0; list[i]; i++) 
        if (!strcmp(list[i], item)) return i;
    return -1;
}

static p_ply_element ply_find_element(p_ply ply, const char *name) {
    p_ply_element element;
    int i, nelements;
    assert(ply && name); 
    element = ply->element;
    nelements = ply->nelements;
    assert(element || nelements == 0); 
    assert(!element || nelements > 0); 
    for (i = 0; i < nelements; i++) 
        if (!strcmp(element[i].name, name)) return &element[i];
    return NULL;
}

static p_ply_property ply_find_property(p_ply_element element, 
        const char *name) {
    p_ply_property property;
    int i, nproperties;
    assert(element && name); 
    property = element->property;
    nproperties = element->nproperties;
    assert(property || nproperties == 0); 
    assert(!property || nproperties > 0); 
    for (i = 0; i < nproperties; i++) 
        if (!strcmp(property[i].name, name)) return &property[i];
    return NULL;
}

static int ply_check_word(p_ply ply) {
    if (strlen(BLINE(ply)) >= WORDSIZE) {
        ply_error(ply, "Word too long");
        return 0;
    }
    return 1;
}

static int ply_read_word(p_ply ply) {
    size_t t = 0;
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    /* skip leading blanks */
    while (1) {
        t = strspn(BFIRST(ply), " \n\r\t");
        /* check if all buffer was made of blanks */
        if (t >= BSIZE(ply)) {
            if (!BREFILL(ply)) {
                ply_error(ply, "Unexpected end of file");
                return 0;
            }
        } else break; 
    } 
    BSKIP(ply, t); 
    /* look for a space after the current word */
    t = strcspn(BFIRST(ply), " \n\r\t");
    /* if we didn't reach the end of the buffer, we are done */
    if (t < BSIZE(ply)) {
        ply->buffer_token = ply->buffer_first;
        BSKIP(ply, t);
        *BFIRST(ply) = '\0';
        BSKIP(ply, 1);
        return ply_check_word(ply);
    }
    /* otherwise, try to refill buffer */
    if (!BREFILL(ply)) {
        ply_error(ply, "Unexpected end of file");
        return 0;
    }
    /* keep looking from where we left */
    t += strcspn(BFIRST(ply) + t, " \n\r\t");
    /* check if the token is too large for our buffer */
    if (t >= BSIZE(ply)) {
        ply_error(ply, "Token too large");
        return 0;
    }
    /* we are done */
    ply->buffer_token = ply->buffer_first;
    BSKIP(ply, t);
    *BFIRST(ply) = '\0';
    BSKIP(ply, 1);
    return ply_check_word(ply);
}

static int ply_check_line(p_ply ply) {
    if (strlen(BLINE(ply)) >= LINESIZE) {
        ply_error(ply, "Line too long");
        return 0;
    }
    return 1;
}

static int ply_read_line(p_ply ply) {
    const char *end = NULL;
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    /* look for a end of line */
    end = strchr(BFIRST(ply), '\n');
    /* if we didn't reach the end of the buffer, we are done */
    if (end) {
        ply->buffer_token = ply->buffer_first;
        BSKIP(ply, end - BFIRST(ply));
        *BFIRST(ply) = '\0';
        BSKIP(ply, 1);
        return ply_check_line(ply);
    } else {
        end = ply->buffer + BSIZE(ply); 
        /* otherwise, try to refill buffer */
        if (!BREFILL(ply)) {
            ply_error(ply, "Unexpected end of file");
            return 0;
        }
    }
    /* keep looking from where we left */
    end = strchr(end, '\n');
    /* check if the token is too large for our buffer */
    if (!end) {
        ply_error(ply, "Token too large");
        return 0;
    }
    /* we are done */
    ply->buffer_token = ply->buffer_first;
    BSKIP(ply, end - BFIRST(ply));
    *BFIRST(ply) = '\0';
    BSKIP(ply, 1);
    return ply_check_line(ply);
}

static int ply_read_chunk(p_ply ply, void *anybuffer, size_t size) {
    char *buffer = (char *) anybuffer;
    size_t i = 0;
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    assert(ply->buffer_first <= ply->buffer_last);
    while (i < size) {
        if (ply->buffer_first < ply->buffer_last) {
            buffer[i] = ply->buffer[ply->buffer_first];
            ply->buffer_first++;
            i++;
        } else {
            ply->buffer_first = 0;
            ply->buffer_last = fread(ply->buffer, 1, BUFFERSIZE, ply->fp);
            if (ply->buffer_last <= 0) return 0;
        }
    }
    return 1;
}

static int ply_write_chunk(p_ply ply, void *anybuffer, size_t size) {
    char *buffer = (char *) anybuffer;
    size_t i = 0;
    assert(ply && ply->fp && ply->io_mode == PLY_WRITE);
    assert(ply->buffer_last <= BUFFERSIZE);
    while (i < size) {
        if (ply->buffer_last < BUFFERSIZE) {
            ply->buffer[ply->buffer_last] = buffer[i];
            ply->buffer_last++;
            i++;
        } else {
            ply->buffer_last = 0;
            if (fwrite(ply->buffer, 1, BUFFERSIZE, ply->fp) < BUFFERSIZE)
                return 0;
        }
    }
    return 1;
}

static int ply_write_chunk_reverse(p_ply ply, void *anybuffer, size_t size) {
    int ret = 0;
    ply_reverse(anybuffer, size);
    ret = ply_write_chunk(ply, anybuffer, size);
    ply_reverse(anybuffer, size);
    return ret;
}

static int ply_read_chunk_reverse(p_ply ply, void *anybuffer, size_t size) {
    if (!ply_read_chunk(ply, anybuffer, size)) return 0;
    ply_reverse(anybuffer, size);
    return 1;
}

static void ply_reverse(void *anydata, size_t size) {
    char *data = (char *) anydata;
    char temp;
    size_t i;
    for (i = 0; i < size/2; i++) {
        temp = data[i];
        data[i] = data[size-i-1];
        data[size-i-1] = temp;
    }
}

static void ply_init(p_ply ply) {
    ply->c = ' ';
    ply->element = NULL;
    ply->nelements = 0;
    ply->comment = NULL;
    ply->ncomments = 0;
    ply->obj_info = NULL;
    ply->nobj_infos = 0;
    ply->idriver = NULL;
    ply->odriver = NULL;
    ply->buffer[0] = '\0';
    ply->buffer_first = ply->buffer_last = ply->buffer_token = 0;
    ply->welement = 0;
    ply->wproperty = 0;
    ply->winstance_index = 0;
    ply->wlength = 0;
    ply->wvalue_index = 0;
}

static void ply_element_init(p_ply_element element) {
    element->name[0] = '\0';
    element->ninstances = 0;
    element->property = NULL;
    element->nproperties = 0; 
}

static void ply_property_init(p_ply_property property) {
    property->name[0] = '\0';
    property->type = -1;
    property->length_type = -1;
    property->value_type = -1;
    property->read_cb = (p_ply_read_cb) NULL;
    property->pdata = NULL;
    property->idata = 0;
}

static p_ply ply_alloc(void) {
    p_ply ply = (p_ply) malloc(sizeof(t_ply));
    if (!ply) return NULL;
    ply_init(ply);
    return ply;
}

static void *ply_grow_array(p_ply ply, void **pointer, 
        long *nmemb, long size) {
    void *temp = *pointer;
    long count = *nmemb + 1;
    if (!temp) temp = malloc(count*size);
    else temp = realloc(temp, count*size);
    if (!temp) {
        ply_error(ply, "Out of memory");
        return NULL;
    }
    *pointer = temp;
    *nmemb = count;
    return (char *) temp + (count-1) * size;
}

static p_ply_element ply_grow_element(p_ply ply) {
    p_ply_element element = NULL;
    assert(ply); 
    assert(ply->element || ply->nelements == 0); 
    assert(!ply->element || ply->nelements > 0); 
    element = (p_ply_element) ply_grow_array(ply, (void **) &ply->element, 
            &ply->nelements, sizeof(t_ply_element));
    if (!element) return NULL;
    ply_element_init(element);
    return element; 
}

static p_ply_property ply_grow_property(p_ply ply, p_ply_element element) {
    p_ply_property property = NULL;
    assert(ply);
    assert(element);
    assert(element->property || element->nproperties == 0);
    assert(!element->property || element->nproperties > 0);
    property = (p_ply_property) ply_grow_array(ply, 
            (void **) &element->property, 
            &element->nproperties, sizeof(t_ply_property));
    if (!property) return NULL;
    ply_property_init(property);
    return property;
}

static int ply_read_header_format(p_ply ply) {
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    if (strcmp(BWORD(ply), "format")) return 0;
    if (!ply_read_word(ply)) return 0;
    ply->storage_mode = ply_find_string(BWORD(ply), ply_storage_mode_list);
    if (ply->storage_mode == (e_ply_storage_mode) (-1)) return 0;
    if (ply->storage_mode == PLY_ASCII) ply->idriver = &ply_idriver_ascii;
    else if (ply->storage_mode == ply_arch_endian()) 
        ply->idriver = &ply_idriver_binary;
    else ply->idriver = &ply_idriver_binary_reverse;
    if (!ply_read_word(ply)) return 0;
    if (strcmp(BWORD(ply), "1.0")) return 0;
    if (!ply_read_word(ply)) return 0;
    return 1;
}

static int ply_read_header_comment(p_ply ply) {
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    if (strcmp(BWORD(ply), "comment")) return 0;
    if (!ply_read_line(ply)) return 0;
    if (!ply_add_comment(ply, BLINE(ply))) return 0;
    if (!ply_read_word(ply)) return 0;
    return 1;
}

static int ply_read_header_obj_info(p_ply ply) {
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    if (strcmp(BWORD(ply), "obj_info")) return 0;
    if (!ply_read_line(ply)) return 0;
    if (!ply_add_obj_info(ply, BLINE(ply))) return 0;
    if (!ply_read_word(ply)) return 0;
    return 1;
}

static int ply_read_header_property(p_ply ply) {
    p_ply_element element = NULL;
    p_ply_property property = NULL;
    /* make sure it is a property */
    if (strcmp(BWORD(ply), "property")) return 0;
    element = &ply->element[ply->nelements-1];
    property = ply_grow_property(ply, element);
    if (!property) return 0;
    /* get property type */
    if (!ply_read_word(ply)) return 0;
    property->type = ply_find_string(BWORD(ply), ply_type_list);
    if (property->type == (e_ply_type) (-1)) return 0;
    if (property->type == PLY_LIST) {
        /* if it's a list, we need the base types */
        if (!ply_read_word(ply)) return 0;
        property->length_type = ply_find_string(BWORD(ply), ply_type_list);
        if (property->length_type == (e_ply_type) (-1)) return 0;
        if (!ply_read_word(ply)) return 0;
        property->value_type = ply_find_string(BWORD(ply), ply_type_list);
        if (property->value_type == (e_ply_type) (-1)) return 0;
    }
    /* get property name */
    if (!ply_read_word(ply)) return 0;
    strcpy(property->name, BWORD(ply));
    if (!ply_read_word(ply)) return 0;
    return 1;
}

static int ply_read_header_element(p_ply ply) {
    p_ply_element element = NULL;
    long dummy;
    assert(ply && ply->fp && ply->io_mode == PLY_READ);
    if (strcmp(BWORD(ply), "element")) return 0;
    /* allocate room for new element */
    element = ply_grow_element(ply);
    if (!element) return 0;
    /* get element name */
    if (!ply_read_word(ply)) return 0;
    strcpy(element->name, BWORD(ply));
    /* get number of elements of this type */
    if (!ply_read_word(ply)) return 0;
    if (sscanf(BWORD(ply), "%ld", &dummy) != 1) {
        ply_error(ply, "Expected number got '%s'", BWORD(ply));
        return 0;
    }
    element->ninstances = dummy;
    /* get all properties for this element */
    if (!ply_read_word(ply)) return 0;
    while (ply_read_header_property(ply) || 
        ply_read_header_comment(ply) || ply_read_header_obj_info(ply))
        /* do nothing */;
    return 1;
}

static void ply_error_cb(const char *message) {
    fprintf(stderr, "RPly: %s\n", message);
}

static void ply_error(p_ply ply, const char *fmt, ...) {
    char buffer[1024];
    va_list ap;
    va_start(ap, fmt);
    vsprintf(buffer, fmt, ap);
    va_end(ap);
    ply->error_cb(buffer);
}

static e_ply_storage_mode ply_arch_endian(void) {
    unsigned long i = 1;
    unsigned char *s = (unsigned char *) &i;
    if (*s == 1) return PLY_LITTLE_ENDIAN;
    else return PLY_BIG_ENDIAN;
}

static int ply_type_check(void) {
    assert(sizeof(char) == 1);
    assert(sizeof(unsigned char) == 1);
    assert(sizeof(short) == 2);
    assert(sizeof(unsigned short) == 2);
    assert(sizeof(int) == 4);
    assert(sizeof(unsigned int) == 4);
    assert(sizeof(float) == 4);
    assert(sizeof(double) == 8);
    if (sizeof(char) != 1) return 0;
    if (sizeof(unsigned char) != 1) return 0;
    if (sizeof(short) != 2) return 0;
    if (sizeof(unsigned short) != 2) return 0;
    if (sizeof(int) != 4) return 0;
    if (sizeof(unsigned int) != 4) return 0;
    if (sizeof(float) != 4) return 0;
    if (sizeof(double) != 8) return 0;
    return 1;
}

/* ----------------------------------------------------------------------
 * Output handlers
 * ---------------------------------------------------------------------- */
static int oascii_int8(p_ply ply, double value) {
    if (value > CHAR_MAX || value < CHAR_MIN) return 0;
    return fprintf(ply->fp, "%d ", (char) value) > 0;
}

static int oascii_uint8(p_ply ply, double value) {
    if (value > UCHAR_MAX || value < 0) return 0;
    return fprintf(ply->fp, "%d ", (unsigned char) value) > 0;
}

static int oascii_int16(p_ply ply, double value) {
    if (value > SHRT_MAX || value < SHRT_MIN) return 0;
    return fprintf(ply->fp, "%d ", (short) value) > 0;
}

static int oascii_uint16(p_ply ply, double value) {
    if (value > USHRT_MAX || value < 0) return 0;
    return fprintf(ply->fp, "%d ", (unsigned short) value) > 0;
}

static int oascii_int32(p_ply ply, double value) {
    if (value > INT_MAX || value < INT_MIN) return 0;
    return fprintf(ply->fp, "%d ", (int) value) > 0;
}

static int oascii_uint32(p_ply ply, double value) {
    if (value > UINT_MAX || value < 0) return 0;
    return fprintf(ply->fp, "%d ", (unsigned int) value) > 0;
}

static int oascii_float32(p_ply ply, double value) {
    if (value < -FLT_MAX || value > FLT_MAX) return 0;
    return fprintf(ply->fp, "%g ", (float) value) > 0;
}

static int oascii_float64(p_ply ply, double value) {
    if (value < -DBL_MAX || value > DBL_MAX) return 0;
    return fprintf(ply->fp, "%g ", value) > 0;
}

static int obinary_int8(p_ply ply, double value) {
    char int8 = (char) value;
    if (value > CHAR_MAX || value < CHAR_MIN) return 0;
    return ply->odriver->ochunk(ply, &int8, sizeof(int8));
}

static int obinary_uint8(p_ply ply, double value) {
    unsigned char uint8 = (unsigned char) value;
    if (value > UCHAR_MAX || value < 0) return 0;
    return ply->odriver->ochunk(ply, &uint8, sizeof(uint8)); 
}

static int obinary_int16(p_ply ply, double value) {
    short int16 = (short) value;
    if (value > SHRT_MAX || value < SHRT_MIN) return 0;
    return ply->odriver->ochunk(ply, &int16, sizeof(int16));
}

static int obinary_uint16(p_ply ply, double value) {
    unsigned short uint16 = (unsigned short) value;
    if (value > USHRT_MAX || value < 0) return 0;
    return ply->odriver->ochunk(ply, &uint16, sizeof(uint16)); 
}

static int obinary_int32(p_ply ply, double value) {
    int int32 = (int) value;
    if (value > INT_MAX || value < INT_MIN) return 0;
    return ply->odriver->ochunk(ply, &int32, sizeof(int32));
}

static int obinary_uint32(p_ply ply, double value) {
    unsigned int uint32 = (unsigned int) value;
    if (value > UINT_MAX || value < 0) return 0;
    return ply->odriver->ochunk(ply, &uint32, sizeof(uint32));
}

static int obinary_float32(p_ply ply, double value) {
    float float32 = (float) value;
    if (value > FLT_MAX || value < -FLT_MAX) return 0;
    return ply->odriver->ochunk(ply, &float32, sizeof(float32));
}

static int obinary_float64(p_ply ply, double value) {
    return ply->odriver->ochunk(ply, &value, sizeof(value)); 
}

/* ----------------------------------------------------------------------
 * Input  handlers
 * ---------------------------------------------------------------------- */
static int iascii_int8(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtol(BWORD(ply), &end, 10);
    if (*end || *value > CHAR_MAX || *value < CHAR_MIN) return 0;
    return 1;
}

static int iascii_uint8(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtol(BWORD(ply), &end, 10);
    if (*end || *value > UCHAR_MAX || *value < 0) return 0;
    return 1;
}

static int iascii_int16(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtol(BWORD(ply), &end, 10);
    if (*end || *value > SHRT_MAX || *value < SHRT_MIN) return 0;
    return 1;
}

static int iascii_uint16(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtol(BWORD(ply), &end, 10);
    if (*end || *value > USHRT_MAX || *value < 0) return 0;
    return 1;
}

static int iascii_int32(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtol(BWORD(ply), &end, 10);
    if (*end || *value > INT_MAX || *value < INT_MIN) return 0;
    return 1;
}

static int iascii_uint32(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtol(BWORD(ply), &end, 10);
    if (*end || *value < 0) return 0;
    return 1;
}

static int iascii_float32(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtod(BWORD(ply), &end);
    if (*end || *value < -FLT_MAX || *value > FLT_MAX) return 0;
    return 1;
}

static int iascii_float64(p_ply ply, double *value) {
    char *end;
    if (!ply_read_word(ply)) return 0;
    *value = strtod(BWORD(ply), &end);
    if (*end || *value < -DBL_MAX || *value > DBL_MAX) return 0;
    return 1;
}

static int ibinary_int8(p_ply ply, double *value) {
    char int8;
    if (!ply->idriver->ichunk(ply, &int8, 1)) return 0;
    *value = int8;
    return 1;
}

static int ibinary_uint8(p_ply ply, double *value) {
    unsigned char uint8;
    if (!ply->idriver->ichunk(ply, &uint8, 1)) return 0;
    *value = uint8;
    return 1;
}

static int ibinary_int16(p_ply ply, double *value) {
    short int16;
    if (!ply->idriver->ichunk(ply, &int16, sizeof(int16))) return 0;
    *value = int16;
    return 1;
}

static int ibinary_uint16(p_ply ply, double *value) {
    unsigned short uint16;
    if (!ply->idriver->ichunk(ply, &uint16, sizeof(uint16))) return 0;
    *value = uint16;
    return 1;
}

static int ibinary_int32(p_ply ply, double *value) {
    int int32;
    if (!ply->idriver->ichunk(ply, &int32, sizeof(int32))) return 0;
    *value = int32;
    return 1;
}

static int ibinary_uint32(p_ply ply, double *value) {
    unsigned int uint32;
    if (!ply->idriver->ichunk(ply, &uint32, sizeof(uint32))) return 0;
    *value = uint32;
    return 1;
}

static int ibinary_float32(p_ply ply, double *value) {
    float float32;
    if (!ply->idriver->ichunk(ply, &float32, sizeof(float32))) return 0;
    *value = float32;
    ply_reverse(&float32, sizeof(float32));
    return 1;
}

static int ibinary_float64(p_ply ply, double *value) {
    return ply->idriver->ichunk(ply, value, sizeof(double));
}

/* ----------------------------------------------------------------------
 * Constants
 * ---------------------------------------------------------------------- */
static t_ply_idriver ply_idriver_ascii = {
    {   iascii_int8, iascii_uint8, iascii_int16, iascii_uint16,
        iascii_int32, iascii_uint32, iascii_float32, iascii_float64,
        iascii_int8, iascii_uint8, iascii_int16, iascii_uint16,
        iascii_int32, iascii_uint32, iascii_float32, iascii_float64
    }, /* order matches e_ply_type enum */
    NULL,
    "ascii input"
};

static t_ply_idriver ply_idriver_binary = {
    {   ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,
        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64,
        ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,
        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64
    }, /* order matches e_ply_type enum */
    ply_read_chunk,
    "binary input"
};

static t_ply_idriver ply_idriver_binary_reverse = {
    {   ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,
        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64,
        ibinary_int8, ibinary_uint8, ibinary_int16, ibinary_uint16,
        ibinary_int32, ibinary_uint32, ibinary_float32, ibinary_float64
    }, /* order matches e_ply_type enum */
    ply_read_chunk_reverse,
    "reverse binary input"
};

static t_ply_odriver ply_odriver_ascii = {
    {   oascii_int8, oascii_uint8, oascii_int16, oascii_uint16,
        oascii_int32, oascii_uint32, oascii_float32, oascii_float64,
        oascii_int8, oascii_uint8, oascii_int16, oascii_uint16,
        oascii_int32, oascii_uint32, oascii_float32, oascii_float64
    }, /* order matches e_ply_type enum */
    NULL,
    "ascii output"
};

static t_ply_odriver ply_odriver_binary = {
    {   obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,
        obinary_int32, obinary_uint32, obinary_float32, obinary_float64,
        obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,
        obinary_int32, obinary_uint32, obinary_float32, obinary_float64
    }, /* order matches e_ply_type enum */
    ply_write_chunk,
    "binary output"
};

static t_ply_odriver ply_odriver_binary_reverse = {
    {   obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,
        obinary_int32, obinary_uint32, obinary_float32, obinary_float64,
        obinary_int8, obinary_uint8, obinary_int16, obinary_uint16,
        obinary_int32, obinary_uint32, obinary_float32, obinary_float64
    }, /* order matches e_ply_type enum */
    ply_write_chunk_reverse,
    "reverse binary output"
};

/* ----------------------------------------------------------------------
 * Copyright (C) 2003 Diego Nehab.  All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 * ---------------------------------------------------------------------- */
