/*
 * (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.
 */

#include <vector>
#include <stdio.h>
#include <string.h>
#include <float.h>
#include <zlib.h>

#include "vector.h"
#include "primitives.h"
#include "ply.h"
#include "gl_error.h"
#include "rtree.h"

#define EPSILON 0.00000001f

struct shadow_volume_quad {
    idr_vec4 p[2];
    idr_vec4 n[2];
    bool complete;
};

/* vertex and face definitions for a polygonal object */

struct Vertex {
  float x,y,z;
  float r,g,b;
  float nx,ny,nz;
  void *other_props;       /* other properties */
};

struct Face {
    unsigned char nverts;    /* number of vertex indices in list */
    int *verts;              /* vertex index list */
    void *other_props;       /* other properties */
};

PlyProperty vert_props[] = { /* list of property information for a vertex */
  {"x", Float32, Float32, offsetof(Vertex,x), 0, 0, 0, 0},
  {"y", Float32, Float32, offsetof(Vertex,y), 0, 0, 0, 0},
  {"z", Float32, Float32, offsetof(Vertex,z), 0, 0, 0, 0},
  {"r", Float32, Float32, offsetof(Vertex,r), 0, 0, 0, 0},
  {"g", Float32, Float32, offsetof(Vertex,g), 0, 0, 0, 0},
  {"b", Float32, Float32, offsetof(Vertex,b), 0, 0, 0, 0},
  {"red", Float32, Float32, offsetof(Vertex,r), 0, 0, 0, 0},
  {"green", Float32, Float32, offsetof(Vertex,g), 0, 0, 0, 0},
  {"blue", Float32, Float32, offsetof(Vertex,b), 0, 0, 0, 0},
  {"nx", Float32, Float32, offsetof(Vertex,nx), 0, 0, 0, 0},
  {"ny", Float32, Float32, offsetof(Vertex,ny), 0, 0, 0, 0},
  {"nz", Float32, Float32, offsetof(Vertex,nz), 0, 0, 0, 0},
};

PlyProperty face_props[] = { /* list of property information for a face */
  {"vertex_indices", Int32, Int32, offsetof(Face,verts),
   1, Uint8, Uint8, offsetof(Face,nverts)},
};


/*** the PLY object ***/


void idr_ply_model::read_file(gzFile in)
{
    int i,j;
    int elem_count;
    char *elem_name;
    PlyFile *in_ply;
    float min[4] = { FLT_MAX, FLT_MAX, FLT_MAX, 0.0 };
    float max[4] = { FLT_MIN, FLT_MIN, FLT_MIN, 0.0 };
    bool per_vertex_color = false;
    bool has_normals = false;

    /*** Read in the original PLY object ***/

    in_ply  = read_ply(in);


    for (i = 0; i < in_ply->num_elem_types; i++) {
        /* prepare to read the i'th list of elements */
        elem_name = setup_element_read_ply (in_ply, i, &elem_count);

        if (strcmp("vertex", elem_name) == 0) {
            /* create a vertex list to hold all the vertices */
            total_verts = elem_count;

            /* set up for getting vertex elements */

            setup_property_ply (in_ply, &vert_props[0]);
            setup_property_ply (in_ply, &vert_props[1]);
            setup_property_ply (in_ply, &vert_props[2]);

            for (j = 0; j < in_ply->elems[i]->nprops; j++) {
                PlyProperty *prop;
                prop = in_ply->elems[i]->props[j];
                if (strcmp("r", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[3]);
                    per_vertex_color = true;
                }
                if (strcmp("g", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[4]);
                    per_vertex_color = true;
                }
                if (strcmp("b", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[5]);
                    per_vertex_color = true;
                }
                if (strcmp("red", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[6]);
                    per_vertex_color = true;
                }
                if (strcmp("green", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[7]);
                    per_vertex_color = true;
                }
                if (strcmp("blue", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[8]);
                    per_vertex_color = true;
                }
                if (strcmp("nx", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[9]);
                    has_normals = true;
                }
                if (strcmp("ny", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[10]);
                    has_normals = true;
                }
                if (strcmp("nz", prop->name) == 0) {
                    setup_property_ply (in_ply, &vert_props[11]);
                    has_normals = true;
                }
            }

            /* grab all the vertex elements */
            const unsigned vert_size = (per_vertex_color) ? 12 : 9;
            tangent_offset = vert_size - 3;
            data = new GLfloat[elem_count * vert_size];
            stride = sizeof(GLfloat) * vert_size;

            color_offset = (per_vertex_color) ? 6 : -1;
            normal_offset = (has_normals) ? 3 : -1;

            for (j = 0; j < elem_count; j++) {
                struct Vertex v;

                get_element_ply (in_ply, (void *) & v);

                if (v.x < min[0]) {
                    min[0] = v.x;
                }

                if (v.y < min[1]) {
                    min[1] = v.y;
                }

                if (v.z < min[2]) {
                    min[2] = v.z;
                }

                if (v.x > max[0]) {
                    max[0] = v.x;
                }

                if (v.y > max[1]) {
                    max[1] = v.y;
                }

                if (v.z > max[2]) {
                    max[2] = v.z;
                }

                data[(j * vert_size) + 0] = v.x;
                data[(j * vert_size) + 1] = v.y;
                data[(j * vert_size) + 2] = v.z;
                data[(j * vert_size) + 3] = v.nx;
                data[(j * vert_size) + 4] = v.ny;
                data[(j * vert_size) + 5] = v.nz;
                if (per_vertex_color) {
                    data[(j * vert_size) + 6] = v.r;
                    data[(j * vert_size) + 7] = v.g;
                    data[(j * vert_size) + 8] = v.b;
                }
            }
        }
        else if (strcmp("face", elem_name) == 0) {
            /* create a list to hold all the face elements */
            flist = new Face *[elem_count];
            nfaces = elem_count;

            /* set up for getting face elements */

            setup_property_ply (in_ply, &face_props[0]);

            /* grab all the face elements */
            for (j = 0; j < elem_count; j++) {
                flist[j] = new Face;
                get_element_ply (in_ply, (void *) flist[j]);
            }
        }
        else
            get_other_element_ply (in_ply);
    }

    close_ply (in_ply);
    free_ply (in_ply);


    aabb[0] = idr_vec4(min);
    aabb[1] = idr_vec4(max);
}


void idr_ply_model::calculate_per_vertex_normals(void)
{
    static const float zeros[3] = { 0.0, 0.0, 0.0 };
    const unsigned vert_size = vertex_stride();
    int i;
    int j;


    normal_offset = 3;

    /* Make a pass over each vertex and normalize its normal vector.
     */
    for (i = 0; i < total_verts; i++) {
        (void) memcpy(& data[(i * vert_size) + 3], zeros, sizeof(zeros));
    }


    /* Begin by calculating the per-face normals.  Add this normal to the
     * per-vertex normal for each of the face's vertexes.
     */
    for (i = 0; i < nfaces; i++) {
        const unsigned a_idx = flist[i]->verts[0];
        const unsigned b_idx = flist[i]->verts[1];
        const unsigned c_idx = flist[i]->verts[2];
        const idr_vec4 a(data[(a_idx * vert_size) + 0], data[(a_idx * vert_size) + 1],
                         data[(a_idx * vert_size) + 2], 0.0);
        const idr_vec4 b(data[(b_idx * vert_size) + 0], data[(b_idx * vert_size) + 1],
                         data[(b_idx * vert_size) + 2], 0.0);
        const idr_vec4 c(data[(c_idx * vert_size) + 0], data[(c_idx * vert_size) + 1],
                         data[(c_idx * vert_size) + 2], 0.0);

        const idr_vec4 n = ((c - b).cross(a - b)).normalize();

        for (j = 0; j < flist[i]->nverts; j++) {
            const unsigned idx = flist[i]->verts[j];

            data[(idx * vert_size) + 3] += n[0];
            data[(idx * vert_size) + 4] += n[1];
            data[(idx * vert_size) + 5] += n[2];
        }
    }


    /* Make a pass over each vertex and normalize its normal vector.
     */
    for (i = 0; i < total_verts; i++) {
        idr_vec4 n(data[(i * vert_size) + 3], data[(i * vert_size) + 4],
                   data[(i * vert_size) + 5], 0.0);
        idr_vec4 t;

        n = n.normalize();

        if (n[1] > 0.8) {
            const idr_vec4 b(1.0, 0.0, 0.0, 0.0);
            t = n.cross(b);
        }
        else {
            const idr_vec4 b(0.0, 1.0, 0.0, 0.0);
            t = n.cross(b);
        }

        data[(i * vert_size) + 3] = n[0];
        data[(i * vert_size) + 4] = n[1];
        data[(i * vert_size) + 5] = n[2];
        data[(i * vert_size) + (tangent_offset + 0)] = t[0];
        data[(i * vert_size) + (tangent_offset + 1)] = t[1];
        data[(i * vert_size) + (tangent_offset + 2)] = t[2];
    }
}


idr_ply_model::idr_ply_model(const char *filename)
{
	init();
	flist = NULL;

	shadow_vbo = 0;

	gzFile f = gzopen(filename, "rb");
	if (f == NULL) {
		return;
	}

	read_file(f);

	if (normal_offset < 0) {
		calculate_per_vertex_normals();
	}


    /* Make an element list to draw the mesh in a single go as GL_TRIANGLES.
     * This means that polygons with more than 3 vertexes will be divided
     * up.
     */
    total_elements = 0;
    for (int i = 0; i < nfaces; i++) {
        if (flist[i]->nverts >= 3) {
            total_elements += 3 + ((flist[i]->nverts - 3) * 3);
        }
    }

    index_32 = new GLuint[total_elements];
    elements = (GLubyte *) index_32;
    element_size = sizeof(GLuint);
    element_enum = GL_UNSIGNED_INT;

    unsigned k = 0;
    for (int i = 0; i < nfaces; i++) {
        if (flist[i]->nverts >= 3) {
            unsigned j;

            for (j = 0; j < 3; j++) {
                index_32[k] = flist[i]->verts[j];
                k++;
            }

            for (/* empty */; j < flist[i]->nverts; j++) {
                index_32[k + 0] = index_32[0];
                index_32[k + 1] = index_32[k - 1];
                index_32[k + 2] = flist[i]->verts[j];
                k += 3;
            }
        }
    }

    position_offset = 0;
    create_shadow_volume();
    create_vbos();
}


void idr_ply_model::draw(GLuint tangent_attribute)
{
    if (data_vbo != 0) {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, data_vbo);
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, index_vbo);
    }

    glVertexPointer(3, GL_FLOAT, stride, data + position_offset);
    glNormalPointer(GL_FLOAT, stride, data + normal_offset);

    if (color_offset > 0) {
        glColorPointer(3, GL_FLOAT, stride, data + color_offset);
    }

    if (tangent_attribute) {
        glVertexAttribPointerARB(tangent_attribute, 3, GL_FLOAT, GL_FALSE,
                                 stride, data + tangent_offset);
    }

    glDrawRangeElements(GL_TRIANGLES, 0, total_verts,
                        total_elements, element_enum, elements);

    if (data_vbo != 0) {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
        glBindBufferARB(GL_ELEMENT_ARRAY_BUFFER_ARB, 0);
    }
}


void idr_ply_model::draw_shadow_volume(void)
{
    if (shadow_vbo != 0) {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, shadow_vbo);
    }

	glVertexPointer(3, GL_FLOAT, sizeof(GLfloat) * 6, shadow_verts);
	glNormalPointer(GL_FLOAT, sizeof(GLfloat) * 6, shadow_verts + 3);

	glDrawArrays(GL_QUADS, 0, num_shadow_verts);

    if (shadow_vbo != 0) {
        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    }
}


void idr_ply_model::bounding_box(idr_vec4 *c1, idr_vec4 *c2)
{
    *c1 = aabb[0];
    *c2 = aabb[1];
}


void add_edge_to_shadow_volume(volume_rtree &obj_tree,
                               std::vector<shadow_volume_quad> &shadow_quads,
                               const idr_vec4 &p1, const idr_vec4 &p2,
                               const idr_vec4 &p3, const idr_vec4 &n)
{
    int q = -1;
    idr_vec4 seek_point;
    idr_vec4 other_point;


    if ((p1[0] < p2[0])
        || ((p1[0] == p2[0]) && (p1[1] < p2[1]))
        || ((p1[0] == p2[0]) && (p1[1] == p2[1]) && (p1[2] < p2[2]))) {
        seek_point = p1;
        other_point = p2;
    }
    else {
        seek_point = p2;
        other_point = p1;
    }

    const idr_vec4 point_AABB[2] = {
        seek_point - idr_vec4(EPSILON),
        seek_point + idr_vec4(EPSILON)
    };


    /* Try to find a quad in the shadow volume that matches the
     * new edge.
     */
    volume_rtree_iterator it = obj_tree.search(point_AABB);
    for (/* empty */; it != obj_tree.end(); ++it) {
        long int t = (long int)(*it);

        if (!shadow_quads[t].complete) {
            const idr_vec4 d1(other_point - shadow_quads[t].p[0]);
            const idr_vec4 d2(other_point - shadow_quads[t].p[1]);

            if ((d1.dot4(d1) < EPSILON) || (d2.dot4(d2) < EPSILON)) {
                q = t;
                break;
            }
        }
    }


    /* If a matching edge was found, set the second normal to the
     * normal of the new edge.  If a matching edge was not found,
     * add an edge with the position and normal data of the new
     * edge.
     */
    if (q >= 0) {
        shadow_quads[q].n[1] = n;
        shadow_quads[q].complete = true;
    }
    else {
        shadow_volume_quad t;

        t.p[0] = p1;
        t.p[1] = p2;
        t.n[0] = n;
        t.complete = false;

        q = (int)shadow_quads.size();
        shadow_quads.push_back(t);
        obj_tree.insert(seek_point, (void *) q);
    }
}


void idr_ply_model::create_shadow_volume(void)
{
    std::vector<shadow_volume_quad> shadow_quads;
    const unsigned vert_stride = vertex_stride();
    volume_rtree obj_tree;

    shadow_quads.reserve(total_elements);

    const unsigned num_triangles = total_elements / 3;
    for (unsigned i = 0; i < num_triangles; i++) {
        const unsigned base_index = (i * 3);
        const idr_vec4 A(& data[position_offset
                                + (vert_stride * vertex_index(base_index))]);
        const idr_vec4 B(& data[position_offset
                                + (vert_stride * vertex_index(base_index + 1))]);
        const idr_vec4 C(& data[position_offset
                                + (vert_stride * vertex_index(base_index + 2))]);

        idr_vec4 N(& data[normal_offset
                          + (vert_stride * vertex_index(base_index))]);
        N[3] = 0.0;

        const idr_vec4 A_hat(A - C);
        const idr_vec4 B_hat(B - C);
        idr_vec4 n = idr_vec4(A_hat.cross(B_hat)).normalize();

        /* If the direction of the surface normal differs from the direction
         * of the vertex normal, flip the surface normal.
         */
        if (n.dot4(N) < 0.0) {
            n = -n;
        }

        /* Each triangle has three edges: A-B, B-C, and C-A.
         */
        add_edge_to_shadow_volume(obj_tree, shadow_quads, A, B, C, n);
        add_edge_to_shadow_volume(obj_tree, shadow_quads, B, C, A, n);
        add_edge_to_shadow_volume(obj_tree, shadow_quads, C, A, B, n);
    }
    

    unsigned idx = 0;

    /* 4 verts per shadow quad.  Each vert has 3 floats for
     * the normal and 3 floats for the position.
     */
    shadow_verts = new GLfloat[shadow_quads.size() * 24];


    unsigned rejected_cop = 0;
    unsigned incomplete = 0;
    for (std::vector<shadow_volume_quad>::iterator it = shadow_quads.begin();
         it != shadow_quads.end();
         ++it) {

        if ((*it).complete) {
            /* If both sides of the edge have essentially the same normal,
             * ignore the edge.  This typically happens when a larger polygon
             * (such as a quad) is divided into triangles.
             */
            const idr_vec4 n0((*it).n[0]);
            const idr_vec4 n1(((*it).complete) ? (*it).n[1] : -(*it).n[0]);

            if (n0.dot4(n1) < (1.0 - EPSILON)) {
                shadow_verts[idx + 0] = (*it).p[0][0];
                shadow_verts[idx + 1] = (*it).p[0][1];
                shadow_verts[idx + 2] = (*it).p[0][2];

                shadow_verts[idx + 3] = (*it).n[0][0];
                shadow_verts[idx + 4] = (*it).n[0][1];
                shadow_verts[idx + 5] = (*it).n[0][2];

                shadow_verts[idx + 6] = (*it).p[1][0];
                shadow_verts[idx + 7] = (*it).p[1][1];
                shadow_verts[idx + 8] = (*it).p[1][2];

                shadow_verts[idx + 9] = (*it).n[0][0];
                shadow_verts[idx + 10] = (*it).n[0][1];
                shadow_verts[idx + 11] = (*it).n[0][2];


                shadow_verts[idx + 12] = (*it).p[1][0];
                shadow_verts[idx + 13] = (*it).p[1][1];
                shadow_verts[idx + 14] = (*it).p[1][2];

                shadow_verts[idx + 15] = (*it).n[1][0];
                shadow_verts[idx + 16] = (*it).n[1][1];
                shadow_verts[idx + 17] = (*it).n[1][2];

                shadow_verts[idx + 18] = (*it).p[0][0];
                shadow_verts[idx + 19] = (*it).p[0][1];
                shadow_verts[idx + 20] = (*it).p[0][2];

                shadow_verts[idx + 21] = (*it).n[1][0];
                shadow_verts[idx + 22] = (*it).n[1][1];
                shadow_verts[idx + 23] = (*it).n[1][2];

                idx += 24;
            }
            else {
                rejected_cop++;
            }
        }
        else {
            incomplete++;
        }
    }

    printf("rejected %u coplanar edges\n", rejected_cop);
    printf("rejected %u incomplete edges\n", incomplete);

    num_shadow_verts = (idx / 6);
    
    if (false && GLEW_ARB_vertex_buffer_object) {
        glGenBuffersARB(1, &shadow_vbo);

        glBindBufferARB(GL_ARRAY_BUFFER_ARB, shadow_vbo);
        glBufferData(GL_ARRAY_BUFFER_ARB, 
                     6 * sizeof(GLfloat) * num_shadow_verts,
                     shadow_verts, GL_STATIC_DRAW);

        delete shadow_verts;
        shadow_verts = NULL;

        glBindBufferARB(GL_ARRAY_BUFFER_ARB, 0);
    }
}
