/*
 * (C) Copyright Ian D. Romanick 2007
 * 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
 * on the rights to use, copy, modify, merge, publish, distribute, sub
 * license, 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 NON-INFRINGEMENT.  IN NO EVENT SHALL
 * AUTHORS, COPYRIGHT HOLDERS, AND/OR THEIR SUPPLIERS 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.
 */

#ifndef IDR_PRIMITIVES_H
#define IDR_PRIMITIVES_H

#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif

#include <GL/glew.h>
#include <zlib.h>
#include "vector.h"

class idr_primitive {
public:
    idr_primitive(void)
    {
	init();
    }
    
    void init(void)
    {
  		data = NULL;
		index_16 = NULL;
		index_32 = NULL;
		data_vbo = 0;
		index_vbo = 0;

		position_offset = -1;
		normal_offset = -1;
		tangent_offset = -1;
		color_offset = -1;
    }

    virtual ~idr_primitive(void)
    {
		if (data_vbo) {
		    GLuint buffs[2] = { data_vbo, index_vbo };

			glDeleteBuffersARB(2, buffs);
		}

		if (data) {
		    delete data;
		}

		if (index_32) {
			delete index_32;
		}

		if (index_16) {
		    delete index_16;
		}
    }

	virtual bool model_good(void) const {
		return true;
	}

    /**
     * Calculate limits of a bounding box around object.
     * 
     * Calculates the two diagonally opposing corners of an axis aligned
     * bounding box (AABB) that encloses the object.
     */
    virtual void bounding_box(idr_vec4 *c1, idr_vec4 *c2) = 0;

    /**
     * Draw the object
     */
    virtual void draw(GLuint tangent_attribute) = 0;

    virtual void draw_shadow_volume(void) = 0;

    bool has_per_vertex_color(void)
    {
		return (color_offset > 0);
    }


protected:
    void create_vbos(void);


    /**
     * Element stride, in \c data, between each vertex.
     */
    unsigned vertex_stride(void) const
    {
		return stride / sizeof(GLfloat);
    }


    /**
     * Dereference an index and return element
     *
     * Internally element index are stored in one of several different
     * formats.  This function dereferences the ith element and returns the
     * index (into the vertex arrays).
     *
     * \param i  Element index to dereference.
     *
     * \return
     * Value stored at the ith element. 
     */
    unsigned vertex_index(unsigned i) const
    {
		return (index_32 != NULL) ? index_32[i] : index_16[i];
    }


    /**
     * Handle for the (vertex) data buffer object.
     *
     * \note
     * This is zero if VBOs are not in use.
     */
    GLuint data_vbo;


    /**
     * Handle for the element buffer object.
     *
     * \note
     * This is zero if VBOs are not in use.
     */
    GLuint index_vbo;


    /**
     * Interleaved array of per-vertex data.
     */
    GLfloat *data;

    /**
     * Per-vertex, byte stride.
     */
    GLsizei stride;

    /**
     * Total number of vertexes stored in \c data.
     */
    GLsizei total_verts;

    GLuint *index_32;
    GLushort *index_16;
    GLubyte *elements;

    GLsizei total_elements;
    GLsizei element_size;
    GLenum element_enum;

    /**
     */
    int position_offset;
    int normal_offset;
    int tangent_offset;
    int color_offset;
};


/**
 * Infinite plane
 */
class idr_plane : public idr_primitive {
public:
    idr_plane(const GLfloat *point, const GLfloat *normal,
	      const GLfloat *tangent);
    idr_plane(const idr_vec4 &point, const idr_vec4 &normal,
	      const idr_vec4 &tangent);

    void projection_matrix(const GLfloat *light, GLfloat factor, GLfloat *m);
    void projection_matrix(const idr_vec4 &light, GLfloat factor, GLfloat *m);

    virtual void bounding_box(idr_vec4 *c1, idr_vec4 *c2);
    virtual void draw(GLuint tangent_attribute);
    virtual void draw_shadow_volume(void) { return; }

private:
    idr_vec4 p;
    idr_vec4 n;
    idr_vec4 t;
    idr_vec4 b;
};


class idr_torus : public idr_primitive {
public:
    idr_torus(GLdouble inner_radius, GLdouble circle_radius,
	      GLint sides, GLint rings);

    virtual void bounding_box(idr_vec4 *c1, idr_vec4 *c2);
    virtual void draw(GLuint tangent_attribute);
    virtual void draw_shadow_volume(void) { return; }

private:
    double inner_radius;
    double circle_radius;
    unsigned draw_calls;
    unsigned elements_per_draw_call;
};


class idr_sphere : public idr_primitive {
public:
    idr_sphere(GLdouble radius, GLint slices, GLint stacks);

    virtual void bounding_box(idr_vec4 *c1, idr_vec4 *c2);
    virtual void draw(GLuint tangent_attribute);
    virtual void draw_shadow_volume(void) { return; }

private:
    double radius;
    unsigned quad_draw_calls;
    unsigned elements_per_quad_draw_call;
    unsigned elements_per_tri_draw_call;
};


class idr_ply_model : public idr_primitive {
public:
    idr_ply_model(const char *filename);

    virtual ~idr_ply_model(void)
	{
		if (shadow_vbo) {
			glDeleteBuffersARB(1, & shadow_vbo);
		}

		if (shadow_verts) {
		    delete shadow_verts;
		}
	}

	virtual bool model_good(void) const
	{
		return flist != NULL;
	}

	virtual void bounding_box(idr_vec4 *c1, idr_vec4 *c2);
    virtual void draw(GLuint tangent_attribute);
    virtual void draw_shadow_volume(void);

private:
    int nfaces;
    struct Face **flist;
    
    GLfloat *shadow_verts;
    GLuint num_shadow_verts;

    /**
     * Axis aligned bounding box enclosing the object.
     */
    idr_vec4 aabb[2];


    /**
     * Handle for the shadow volume (vertex) data buffer object.
     *
     * \note
     * This is zero if VBOs are not in use.
     */
    GLuint shadow_vbo;

    void read_file(gzFile in);
    void calculate_per_vertex_normals(void);
    void create_shadow_volume(void);
};

#endif /* IDR_PRIMITIVES_H */
