/*
 * Copyright © 2011 Intel Corporation
 *
 * 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 (including the next
 * paragraph) 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.
 */

/**
 * \file piglit-vbo.cpp
 *
 * This file adds the facility for specifying vertex data to piglit
 * tests using a columnar text format, for example:
 *
 *   \verbatim
 *   vertex/float/3 foo/uint/1 bar/int/2
 *   0.0 0.0 0.0    10         0 0 # comment
 *   0.0 1.0 0.0     5         1 1
 *   1.0 1.0 0.0     0         0 1
 *   \endverbatim
 *
 * The format consists of a row of column headers followed by any
 * number of rows of data.  Each column header has the form
 * "ATTRNAME/TYPE/COUNT", where ATTRNAME is the name of the vertex
 * attribute to be bound to this column, TYPE is the type of data that
 * follows ("float", "int", or "uint"), and COUNT is the vector length
 * of the data (e.g. "3" for vec3 data).
 *
 * The data follows the column headers in space-separated form.  "#"
 * can be used for comments, as in shell scripts.
 *
 * To process textual vertex data, call the function
 * setup_vbo_from_text(), passing the int identifying the linked
 * program, and the string containing the vertex data.  The return
 * value is the number of rows of vertex data found.
 *
 * If an error occurs, setup_vbo_from_text() will print out a
 * description of the error and exit with PIGLIT_FAIL.
 *
 * For the example above, the call to setup_vbo_from_text() is roughly
 * equivalent to the following GL operations:
 *
 * \code
 * struct vertex_attributes {
 *         GLfloat vertex[3];
 *         GLuint foo;
 *         GLint bar[2];
 * } vertex_data[] = {
 *         { { 0.0, 0.0, 0.0 }, 10, { 0, 0 } },
 *         { { 0.0, 1.0, 0.0 },  5, { 1, 1 } },
 *         { { 1.0, 1.0, 0.0 },  0, { 0, 1 } }
 * };
 * size_t stride = sizeof(vertex_data[0]);
 * GLint vertex_index = glGetAttribLocation(prog, "vertex");
 * GLint foo_index = glGetAttribLocation(prog, "foo");
 * GLint bar_index = glGetAttribLocation(prog, "bar");
 * GLuint buffer_handle;
 * glGenBuffers(1, &buffer_handle);
 * glBindBuffer(GL_ARRAY_BUFFER, buffer_handle);
 * glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), &vertex_data,
 *              GL_STATIC_DRAW);
 * glVertexAttribPointer(vertex_index, 3, GL_FLOAT, GL_FALSE, stride,
 *                       (void *) offsetof(vertex_attributes, vertex));
 * glVertexAttribIPointer(foo_index, 3, GL_UNSIGNED_INT, stride,
 *                        (void *) offsetof(vertex_attributes, foo));
 * glVertexAttribIPointer(bar_index, 3, GL_INT, stride,
 *                        (void *) offsetof(vertex_attributes, bar));
 * glEnableVertexAttribArray(vertex_index);
 * glEnableVertexAttribArray(foo_index);
 * glEnableVertexAttribArray(bar_index);
 * \endcode
 */

#include <string>
#include <vector>
#include <errno.h>
#include <ctype.h>

#include "piglit-util-gl.h"
#include "piglit-vbo.h"

/**
 * Currently all the attribute types we support (int, uint, and float)
 * are 4 bytes in width.
 */
const int ATTRIBUTE_SIZE = 4;


/**
 * Convert a type name string to a GLenum.
 */
GLenum
decode_type(const char *type)
{
	static struct type_table_entry {
		const char *type; /* NULL means end of table */
		GLenum enum_value;
	} const type_table[] = {
		{ "int",     GL_INT            },
		{ "uint",    GL_UNSIGNED_INT   },
		{ "float",   GL_FLOAT          },
		{ "double",  GL_DOUBLE         },
		{ NULL,      0                 }
	};


	for (int i = 0; type_table[i].type; ++i) {
		if (0 == strcmp(type, type_table[i].type))
			return type_table[i].enum_value;
	}

	printf("Unrecognized type: %s\n", type);
	piglit_report_result(PIGLIT_FAIL);
	return 0;
}


/**
 * Description of a vertex attribute, built from its column header
 */
class vertex_attrib_description
{
public:
	vertex_attrib_description(GLuint prog, const char *text);
	bool parse_datum(const char **text, void *data) const;
	void setup(size_t *offset, size_t stride) const;

	/**
	 * Data type of this attribute.
	 */
	GLenum data_type;

	/**
	 * Vector length of this attribute.
	 */
	size_t count;

	/**
	 * Index of this vertex attribute in the linked program.
	 */
	GLuint index;
};


/**
 * Build a vertex_attrib_description from a column header, by looking
 * up the vertex attribute in the linked program and interpreting the
 * type and count parts of the header.
 *
 * If there is a parse failure, print a description of the problem and
 * then exit with PIGLIT_FAIL.
 */
vertex_attrib_description::vertex_attrib_description(GLuint prog,
						     const char *text)
{
	/* Split the column header into name/type/count fields */
	const char *first_slash = strchr(text, '/');
	if (first_slash == NULL) {
		printf("Column headers must be in the form name/type/count.  "
		       "Got: %s\n",
		       text);
		piglit_report_result(PIGLIT_FAIL);
	}
	std::string name(text, first_slash);
	const char *second_slash = strchr(first_slash + 1, '/');
	if (second_slash == NULL) {
		printf("Column headers must be in the form name/type/count.  "
		       "Got: %s\n",
		       text);
		piglit_report_result(PIGLIT_FAIL);
	}
	std::string type_str(first_slash + 1, second_slash);
	this->data_type = decode_type(type_str.c_str());
	char *endptr;
	this->count = strtoul(second_slash + 1, &endptr, 10);
	if (*endptr != '\0') {
		printf("Column headers must be in the form name/type/count.  "
		       "Got: %s\n",
		       text);
		piglit_report_result(PIGLIT_FAIL);
	}

	GLint attrib_location = glGetAttribLocation(prog, name.c_str());
	if (attrib_location == -1) {
		printf("Unexpected vbo column name.  Got: %s\n", name.c_str());
		piglit_report_result(PIGLIT_FAIL);
	}
	this->index = attrib_location;
	/* If the type is integral, verify that integer vertex
	 * attribute support is present.  Note: we treat it as a FAIL
	 * if support is not present, because it's up to the test to
	 * either (a) not require integer vertex attribute support, or
	 * (b) skip itself if integer vertex attribute support is not
	 * present.
	 */
	if (this->data_type != GL_FLOAT &&
	    (piglit_is_gles() ||
	    (piglit_get_gl_version() < 30 &&
		!piglit_is_extension_supported("GL_EXT_gpu_shader4")))) {
		printf("Test uses glVertexAttribIPointer(),"
		       " which is unsupported.\n");
		piglit_report_result(PIGLIT_FAIL);
	}

	if (this->count < 1 || this->count > 4) {
		printf("Count must be between 1 and 4.  Got: %lu\n", (unsigned long) count);
		piglit_report_result(PIGLIT_FAIL);
	}
}


/**
 * Parse a single number (floating point or integral) from one of the
 * data rows, and store it in the location pointed to by \c data.
 * Update \c text to point to the next character of input.
 *
 * If there is a parse failure, print a description of the problem and
 * then return false.  Otherwise return true.
 */
bool
vertex_attrib_description::parse_datum(const char **text, void *data) const
{
	char *endptr;
	errno = 0;
	switch (this->data_type) {
	case GL_FLOAT: {
		double value = strtod(*text, &endptr);
		if (errno == ERANGE) {
			printf("Could not parse as double\n");
			return false;
		}
		*((GLfloat *) data) = (float) value;
		break;
	}
	case GL_DOUBLE: {
		double value = strtod(*text, &endptr);
		if (errno == ERANGE) {
			printf("Could not parse as double\n");
			return false;
		}
		*((GLdouble *) data) = value;
		break;
	}
	case GL_INT: {
		long value = strtol(*text, &endptr, 0);
		if (errno == ERANGE) {
			printf("Could not parse as signed integer\n");
			return false;
		}
		*((GLint *) data) = (GLint) value;
		break;
	}
	case GL_UNSIGNED_INT: {
		unsigned long value = strtoul(*text, &endptr, 0);
		if (errno == ERANGE) {
			printf("Could not parse as unsigned integer\n");
			return false;
		}
		*((GLuint *) data) = (GLuint) value;
		break;
	}
	default:
		assert(!"Unexpected data type");
		endptr = NULL;
		break;
	}
	*text = endptr;
	return true;
}


/**
 * Execute the necessary GL calls to bind this attribute to its data.
 */
void
vertex_attrib_description::setup(size_t *offset, size_t stride) const
{
	int attribute_size = ATTRIBUTE_SIZE;
	switch (this->data_type) {
	case GL_FLOAT:
		glVertexAttribPointer(this->index, this->count,
				      this->data_type, GL_FALSE, stride,
				      (void *) *offset);
		break;
	case GL_DOUBLE:
		if (!piglit_is_extension_supported("GL_ARB_vertex_attrib_64bit")) {
			fprintf(stderr,"vertex_attrib_description fail. no 64-bit float support\n");
			return;
		}
		glVertexAttribLPointer(this->index, this->count,
				      this->data_type, stride,
				      (void *) *offset);
		attribute_size *= 2;
		break;
	default:
		if (piglit_is_gles() && piglit_get_gl_version() < 30) {
			fprintf(stderr,"vertex_attrib_description fail. no int support\n");
			return;
		}
		glVertexAttribIPointer(this->index, this->count,
				       this->data_type, stride,
				       (void *) *offset);
		break;
	}
	glEnableVertexAttribArray(index);
	*offset += attribute_size * this->count;
}


/**
 * Data structure containing all of the data parsed from the text
 * input, as well as the methods that parse it and convert it to GL
 * calls.
 */
class vbo_data
{
public:
	vbo_data(std::string const &text, GLuint prog);
	size_t setup() const;

private:
	void parse_header_line(const std::string &line, GLuint prog);
	void parse_data_line(const std::string &line, unsigned int line_num);
	void parse_line(std::string line, unsigned int line_num, GLuint prog);

	/**
	 * True if the header line has already been parsed.
	 */
	bool header_seen;

	/**
	 * Description of each attribute.
	 */
	std::vector<vertex_attrib_description> attribs;

	/**
	 * Raw data buffer containing parsed numbers.
	 */
	std::vector<char> raw_data;

	/**
	 * Number of bytes in each row of raw_data.
	 */
	size_t stride;

	/**
	 * Number of rows in raw_data.
	 */
	size_t num_rows;
};



static bool
is_blank_line(const std::string &line)
{
	for (size_t i = 0; i < line.size(); ++i) {
		if (!isspace(line[i]))
			return false;
	}
	return true;
}


/**
 * Populate this->attribs and compute this->stride based on column
 * headers.
 *
 * If there is a parse failure, print a description of the problem and
 * then exit with PIGLIT_FAIL.
 */
void
vbo_data::parse_header_line(const std::string &line, GLuint prog)
{
	size_t pos = 0;
	this->stride = 0;
	while (pos < line.size()) {
		if (isspace(line[pos])) {
			++pos;
		} else {
			size_t column_header_end = pos;
			int mul;
			while (column_header_end < line.size() &&
			       !isspace(line[column_header_end]))
				++column_header_end;
			std::string column_header = line.substr(
				pos, column_header_end - pos);
			vertex_attrib_description desc(
				prog, column_header.c_str());
			attribs.push_back(desc);
			mul = (desc.data_type == GL_DOUBLE) ? 2 : 1;
			this->stride += ATTRIBUTE_SIZE * desc.count * mul;
			pos = column_header_end + 1;
		}
	}
}


/**
 * Convert a data row into binary form and append it to this->raw_data.
 *
 * If there is a parse failure, print a description of the problem and
 * then exit with PIGLIT_FAIL.
 */
void
vbo_data::parse_data_line(const std::string &line, unsigned int line_num)
{
	/* Allocate space in raw_data for this line */
	size_t old_size = this->raw_data.size();
	this->raw_data.resize(old_size + this->stride);
	char *data_ptr = &this->raw_data[old_size];

	const char *line_ptr = line.c_str();
	for (size_t i = 0; i < this->attribs.size(); ++i) {
		for (size_t j = 0; j < this->attribs[i].count; ++j) {
			int mul = (this->attribs[i].data_type == GL_DOUBLE) ? 2 : 1;
			if (!this->attribs[i].parse_datum(&line_ptr,
							  data_ptr)) {
				printf("At line %u of [vertex data] section\n",
				       line_num);
				printf("Offending text: %s\n", line_ptr);
				piglit_report_result(PIGLIT_FAIL);
			}
			data_ptr += ATTRIBUTE_SIZE * mul;
		}
	}

	++this->num_rows;
}


/**
 * Parse a line of input text.
 *
 * If there is a parse failure, print a description of the problem and
 * then exit with PIGLIT_FAIL.
 */
void
vbo_data::parse_line(std::string line, unsigned int line_num, GLuint prog)
{
	/* Ignore end-of-line comments */
	line = line.substr(0, line.find('#'));

	/* Ignore blank or comment-only lines */
	if (is_blank_line(line))
		return;

	if (!this->header_seen) {
		this->header_seen = true;
		parse_header_line(line, prog);
	} else {
		parse_data_line(line, line_num);
	}
}


/**
 * Parse the input but don't execute any GL commands.
 *
 * If there is a parse failure, print a description of the problem and
 * then exit with PIGLIT_FAIL.
 */
vbo_data::vbo_data(const std::string &text, GLuint prog)
	: header_seen(false), stride(0), num_rows(0)
{
	unsigned int line_num = 1;

	size_t pos = 0;
	while (pos < text.size()) {
		size_t end_of_line = text.find('\n', pos);
		if (end_of_line == std::string::npos)
			end_of_line = text.size();
		parse_line(text.substr(pos, end_of_line), line_num++, prog);
		pos = end_of_line + 1;
	}
}


/**
 * Execute the necessary GL commands to set up the vertex data passed
 * to the constructor.
 */
size_t
vbo_data::setup() const
{
	GLuint buffer_handle;
	glGenBuffers(1, &buffer_handle);
	glBindBuffer(GL_ARRAY_BUFFER, buffer_handle);
	glBufferData(GL_ARRAY_BUFFER, this->stride * this->num_rows,
		     &this->raw_data[0], GL_STATIC_DRAW);

	size_t offset = 0;
	for (size_t i = 0; i < attribs.size(); ++i)
		attribs[i].setup(&offset, this->stride);

	/* Leave buffer bound for later draw calls */

	return this->num_rows;
}


/**
 * Set up a vertex buffer object for the program prog based on the
 * data encoded in text_start.  text_end indicates the end of the text
 * string; if it is NULL, the string is assumed to be null-terminated.
 *
 * Return value is the number of rows of vertex data found.
 *
 * For details about the format of the text string, see the comment at
 * the top of this file.
 */
size_t
setup_vbo_from_text(GLuint prog, const char *text_start, const char *text_end)
{
	if (text_end == NULL)
		text_end = text_start + strlen(text_start);
	std::string text(text_start, text_end);
	return vbo_data(text, prog).setup();
}