#include "../csc321.h"
#include "Shape.h"
#include <fstream>

Shape::~Shape() {
	// destroy your data here
}

void Shape::draw() {
    // render the shape using OpenGL
    glBegin(GL_TRIANGLES);
    if (vertices.size() == normals.size()) { // one normal per vertex
        for (unsigned int i = 0; i < vertices.size(); i += 3) {
            glNormal3dv(&normals[i][0]);
            glVertex3dv(&vertices[i][0]);
            glNormal3dv(&normals[i+1][0]);
            glVertex3dv(&vertices[i+1][0]);
            glNormal3dv(&normals[i+2][0]);
            glVertex3dv(&vertices[i+2][0]);
        }
    } else { // one normal per face
        for (unsigned int i = 0; i < vertices.size(); i += 3) {
            glNormal3dv(&normals[i/3][0]);
            glVertex3dv(&vertices[i][0]);
            glVertex3dv(&vertices[i+1][0]);
            glVertex3dv(&vertices[i+2][0]);
        }
    }
    glEnd();
}

Shape::Shape() : vertices(), normals() {
}

void Shape::addTriangle(const Point3& p1, const Point3& p2, const Point3& p3) {
    vertices.push_back(p1);
    vertices.push_back(p2);
    vertices.push_back(p3);
}

void Shape::addTriangle(const Point3& p1, const Point3& p2, const Point3& p3, const Vector3& n) {
    vertices.push_back(p1);
    vertices.push_back(p2);
    vertices.push_back(p3);
    normals.push_back(n);
}

void Shape::addTriangle(const Point3& p1, const Point3& p2, const Point3& p3, const Vector3& n1, const Vector3& n2, const Vector3& n3) {
    vertices.push_back(p1);
    vertices.push_back(p2);
    vertices.push_back(p3);
    normals.push_back(n1);
    normals.push_back(n2);
    normals.push_back(n3);
}

void Shape::addSquare(const Point3& p1, const Point3& p2, const Point3& p3, const Point3& p4) {
    addTriangle(p1, p2, p4);
    addTriangle(p2, p3, p4);
}

void Shape::addSquare(const Point3& p1, const Point3& p2, const Point3& p3, const Point3& p4, const Vector3& n) {
    addTriangle(p1, p2, p4, n);
    addTriangle(p2, p3, p4, n);
}

void Shape::addSquare(const Point3& p1, const Point3& p2, const Point3& p3, const Point3& p4, const Vector3& n1, const Vector3& n2, const Vector3& n3, const Vector3& n4) {
    addTriangle(p1, p2, p4, n1, n2, n4);
    addTriangle(p2, p3, p4, n2, n3, n4);
}

Cube::Cube(int n) : Shape() {
    for (int u = 0; u < n; u++) {
        for (int v = 0; v < n; v++) {
            double s0 = -0.5 + (1.0 * u) / n;
            double t0 = 0.5 - (1.0 * v) / n;

            double s1 = -0.5 + (1.0 * (u + 1)) / n;
            double t1 = 0.5 - (1.0 * (v + 1)) / n;

            // z = 0.5
            addSquare(Point3(s0, t0, 0.5), Point3(s0, t1, 0.5), Point3(s1, t1, 0.5), Point3(s1, t0, 0.5), Vector3(0, 0, 1));
            // z = -0.5
            addSquare(Point3(s1, t0, -0.5), Point3(s1, t1, -0.5), Point3(s0, t1, -0.5), Point3(s0, t0, -0.5), Vector3(0, 0, -1));
            // x = 0.5
            addSquare(Point3(0.5, t0, s0), Point3(0.5, t0, s1), Point3(0.5, t1, s1), Point3(0.5, t1, s0), Vector3(1, 0, 0));
            // x = -0.5
            addSquare(Point3(-0.5, t0, s1), Point3(-0.5, t0, s0), Point3(-0.5, t1, s0), Point3(-0.5, t1, s1), Vector3(-1, 0, 0));
            // y = 0.5
            addSquare(Point3(s0, 0.5, t0), Point3(s1, 0.5, t0), Point3(s1, 0.5, t1), Point3(s0, 0.5, t1), Vector3(0, 1, 0));
            // y = -0.5
            addSquare(Point3(s1, -0.5, t0), Point3(s0, -0.5, t0), Point3(s0, -0.5,  t1), Point3(s1, -0.5,  t1), Vector3(0, -1, 0));
        }
    }
}


Cone::Cone(int n, int m) : Shape() {
    if (n < 3)
        n = 3;
    double ca = 0.5, sa = 0.0;
    for (int i = 1; i <= n; i++) {
        double a = (2 * M_PI * i) / n;
        double ca1 = 0.5 * cos(a);
        double sa1 = 0.5 * sin(a);
        addTriangle(Point3(0, -0.5, 0), Point3(ca, -0.5, sa), Point3(ca1, -0.5, sa1), Vector3(0, -1, 0), Vector3(0, -1, 0), Vector3(0, -1, 0));
        float y = 0.5f - 1.0f / m;
        double r = 1.0 / m;
        addTriangle(Point3(0, 0.5, 0), 
                    Point3(r * ca1, y, r * sa1), 
                    Point3(r * ca, y, r * sa), 
                    Vector3(0, 1, 0), 
                    Vector3(ca1 * 2, 0.5, sa1 * 2), 
                    Vector3(ca * 2, 0.5, sa * 2));

        for (int j = 1; j < m; j++) {
            float y1 = 0.5f - (float) j / m;
            float y2 = 0.5f - (float) (j + 1) / m;
            r = (double) j / m;
            double r2 = (double) (j + 1) / m;
            addSquare(Point3(r * ca, y1, r * sa), 
                      Point3(r * ca1, y1,r * sa1), 
                      Point3(r2 * ca1, y2, r2 * sa1), 
                      Point3(r2 * ca, y2, r2 * sa), 
                      Vector3(ca * 2, 0.5, sa * 2), 
                      Vector3(ca1 * 2, 0.5, sa1 * 2), 
                      Vector3(ca1 * 2, 0.5, sa1 * 2), 
                      Vector3(ca * 2, 0.5, sa * 2));
        }
        ca = ca1;
        sa = sa1;
    }
}

Cylinder::Cylinder(int n, int m) : Shape() {
    if (n < 3)
        n = 3;
    double ca = 0.5, sa = 0.0;
    for (int i = 1; i <= n; i++) {
        double a = (2 * M_PI * i) / n;
        double ca1 = 0.5 * cos(a);
        double sa1 = 0.5 * sin(a);
        addTriangle(Point3(0, 0.5, 0), Point3(ca1, 0.5, sa1), Point3(ca, 0.5, sa), Vector3(0, 1, 0), Vector3(0, 1, 0), Vector3(0, 1, 0));
        addTriangle(Point3(0, -0.5, 0), Point3(ca, -0.5, sa), Point3(ca1, -0.5, sa1), Vector3(0, -1, 0), Vector3(0, -1, 0), Vector3(0, -1, 0));
        for (int j = 0; j < m; j++) {
            float y1 = 0.5f - (float) j / m;
            float y2 = 0.5f - (float) (j + 1) / m;
            addSquare(Point3(ca, y1, sa), Point3(ca1, y1, sa1), Point3(ca1, y2, sa1), Point3(ca, y2, sa), Vector3(ca * 2, 0, sa * 2), Vector3(ca1 * 2, 0, sa1 * 2), Vector3(ca1 * 2, 0, sa1 * 2), Vector3(ca * 2, 0, sa * 2));
        }
        ca = ca1;
        sa = sa1;
    }
}

Sphere::Sphere(int n) : Shape() {
    unsigned int i;
    int j;
    if (n > 5)
        n = 5;
    double a = 2.0 / (1.0 + sqrt(5.0));
      
    addTriangle(Point3( 0,  a, -1), Point3(-a,  1,  0), Point3( a,  1,  0));
    addTriangle(Point3( 0,  a,  1), Point3( a,  1,  0), Point3(-a,  1,  0));
    addTriangle(Point3( 0,  a,  1), Point3(-1,  0,  a), Point3( 0, -a,  1));
    addTriangle(Point3( 0,  a,  1), Point3( 0, -a,  1), Point3( 1,  0,  a));
    addTriangle(Point3( 0,  a, -1), Point3( 1,  0, -a), Point3( 0, -a, -1));
    addTriangle(Point3( 0,  a, -1), Point3( 0, -a, -1), Point3(-1,  0, -a));
    addTriangle(Point3( 0, -a,  1), Point3(-a, -1,  0), Point3( a, -1,  0));
    addTriangle(Point3( 0, -a, -1), Point3( a, -1,  0), Point3(-a, -1,  0));
    addTriangle(Point3(-a,  1,  0), Point3(-1,  0, -a), Point3(-1,  0,  a));
    addTriangle(Point3(-a, -1,  0), Point3(-1,  0,  a), Point3(-1,  0, -a));
    addTriangle(Point3( a,  1,  0), Point3( 1,  0,  a), Point3( 1,  0, -a));
    addTriangle(Point3( a, -1,  0), Point3( 1,  0, -a), Point3( 1,  0,  a));
    addTriangle(Point3( 0,  a,  1), Point3(-a,  1,  0), Point3(-1,  0,  a));
    addTriangle(Point3( 0,  a,  1), Point3( 1,  0,  a), Point3( a,  1,  0));
    addTriangle(Point3( 0,  a, -1), Point3(-1,  0, -a), Point3(-a,  1,  0));
    addTriangle(Point3( 0,  a, -1), Point3( a,  1,  0), Point3( 1,  0, -a));
    addTriangle(Point3( 0, -a, -1), Point3(-a, -1,  0), Point3(-1,  0, -a));
    addTriangle(Point3( 0, -a, -1), Point3( 1,  0, -a), Point3( a, -1,  0));
    addTriangle(Point3( 0, -a,  1), Point3(-1,  0,  a), Point3(-a, -1,  0));
    addTriangle(Point3( 0, -a,  1), Point3( a, -1,  0), Point3( 1,  0,  a));
    
    std::vector<Point3> vOld;
    for (j = 1; j < n; j++) {
        // normalize to sphere
        Point3 origin;
        for (i = 0; i < vertices.size(); i++) {
            Vector3 v = unit( vertices[i] - origin );
            vertices[i] = origin + v;
        }
        vOld = vertices;
        vertices.clear();
        for (i = 0; i < vOld.size(); i += 3) {
            Point3 v1 = vOld[i];
            double x1 = v1[0], y1 = v1[1], z1 = v1[2];
            Point3 v2 = vOld[i + 1];
            double x2 = v2[0], y2 = v2[1], z2 = v2[2];
            Point3 v3 = vOld[i + 2];
            double x3 = v3[0], y3 = v3[1], z3 = v3[2];

            addTriangle(v1, Point3((x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5), Point3((x1 + x3) * 0.5, (y1 + y3) * 0.5, (z1 + z3) * 0.5));
            addTriangle(Point3((x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5), v2, Point3((x3 + x2) * 0.5, (y3 + y2) * 0.5, (z3 + z2) * 0.5));
            addTriangle(Point3((x1 + x3) * 0.5, (y1 + y3) * 0.5, (z1 + z3) * 0.5), Point3((x3 + x2) * 0.5, (y3 + y2) * 0.5, (z3 + z2) * 0.5), v3);
            addTriangle(Point3((x1 + x3) * 0.5, (y1 + y3) * 0.5, (z1 + z3) * 0.5), Point3((x1 + x2) * 0.5, (y1 + y2) * 0.5, (z1 + z2) * 0.5), Point3((x3 + x2) * 0.5, (y3 + y2) * 0.5, (z3 + z2) * 0.5));
        }
    }
    // compute normals and scale to 0.5 radius
    Point3 origin;
    for (i = 0; i < vertices.size(); i++) {
        Vector3 n = unit( vertices[i] - origin );
        normals.push_back(n);
        vertices[i] = origin + n * 0.5;
    }
}

MoebiusStrip::MoebiusStrip(int n, int m) : Shape() {
    unsigned int k;
    int i, j;
    if (n < 3)
        n = 3;
    for (i = 0; i < n; i++) {
        for (j = 0; j < m; j++) {
            double s0 = 2 * M_PI * i / n;
            double t0 = -0.4 + 0.8 * j / m;

            double s1 = 2 * M_PI * (i + 1) / n;
            double t1 = -0.4 + 0.8 * (j + 1)  / m;

            double x1 = cos(s0) + t0 * cos(s0 * 0.5) * cos(s0);
            double y1 = sin(s0) + t0 * cos(s0 * 0.5) * sin(s0);
            double z1 = t0 * sin(s0 * 0.5);

            double x2 = cos(s1) + t0 * cos(s1 * 0.5) * cos(s1);
            double y2 = sin(s1) + t0 * cos(s1 * 0.5) * sin(s1);
            double z2 = t0 * sin(s1 * 0.5);

            double x3 = cos(s1) + t1 * cos(s1 * 0.5) * cos(s1);
            double y3 = sin(s1) + t1 * cos(s1 * 0.5) * sin(s1);
            double z3 = t1 * sin(s1 * 0.5);

            double x4 = cos(s0) + t1 * cos(s0 * 0.5) * cos(s0);
            double y4 = sin(s0) + t1 * cos(s0 * 0.5) * sin(s0);
            double z4 = t1 * sin(s0 * 0.5);

            // add the square both ways (because surface is two sided)
            addSquare(Point3(x1, y1, z1), Point3(x2, y2, z2), Point3(x3, y3, z3), Point3(x4, y4, z4));
            addSquare(Point3(x2, y2, z2), Point3(x1, y1, z1), Point3(x4, y4, z4), Point3(x3, y3, z3));
        }
    }
  
    // calculate only one normal per face, the explicit expression for the surface normal is
    // a little too messy
    for (k = 0; k < vertices.size(); k += 3) {
        vertices[k    ] *= 0.70;
        vertices[k + 1] *= 0.70;
        vertices[k + 2] *= 0.70;
        Vector3 a = vertices[k + 1] - vertices[k];
        Vector3 b = vertices[k + 2] - vertices[k];
        Vector3 n = a ^ b;
        n.normalize();
        normals.push_back(n);
    }
}

Cow::Cow() : Shape() {
    std::ifstream cowFile("../data/cow.raw");
    if ( !cowFile.good() )
        return;

    bool done = !cowFile;
    while (!done) {
        Point3 p1, p2, p3;
        cowFile >> p1[0];
        cowFile >> p1[1];
        cowFile >> p1[2];
        cowFile >> p2[0];
        cowFile >> p2[1];
        cowFile >> p2[2];
        cowFile >> p3[0];
        cowFile >> p3[1];
        done = !(cowFile >> p3[2]);
        addTriangle(p1, p2, p3);
    }
    for (unsigned int i = 0; i < vertices.size(); i += 3) {
        vertices[i    ] *= 0.20;
        vertices[i + 1] *= 0.20;
        vertices[i + 2] *= 0.20;
        Vector3 a = vertices[i + 1] - vertices[i];
        Vector3 b = vertices[i + 2] - vertices[i];
        Vector3 n = a ^ b;
        n.normalize();
        normals.push_back(n);
    }
}