// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

// Copyright 2007, Daniel Fontijne, University of Amsterdam -- fontijne@science.uva.nl

#ifdef WIN32
#include <windows.h>
#endif

#include <GL/gl.h>
#include <GL/glut.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>


#include <vector>
#include <set>
#include <string>
#include <utility>

#include <libgasandbox/common.h>
#include <libgasandbox/c3ga.h>
#include <libgasandbox/c3ga_draw.h>
#include <libgasandbox/c3ga_util.h>
#include <libgasandbox/gl_util.h>
#include <libgasandbox/glut_util.h>

using namespace c3ga;
using namespace mv_draw;

const char *WINDOW_TITLE = "Geometric Algebra, Chapter 14, Example 2: Drawing Euclid's Elements (SOLUTION)";

int g_randCol = 0;

// GLUT state information
int g_viewportWidth = 900;
int g_viewportHeight = 600;

void display() {
    doIntelWarning(); // warn for possible problems with pciking on Intel graphics chipsets

    glClearColor(1.0f, 1.0f, 1.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    //               flats rounds free, tangents
    // grade 2
    // grade 3
    // grade 4
    const int NB_HOR = 4;
    const int NB_VER = 3;

    const int labelHeight = 40;
    const int labelWidth = 80;

    // size of a 'box' where we draw the primitive:
    int boxWidth = (g_viewportWidth - labelWidth) / NB_HOR;
    int boxHeight = (g_viewportHeight - labelHeight) / NB_VER;

    // draw the labels:
    {
        glViewport(0, 0, g_viewportWidth, g_viewportHeight);
        glMatrixMode(GL_PROJECTION);
        glLoadIdentity();
        glOrtho(0, g_viewportWidth, 0, g_viewportHeight, -100.0, 100.0);
        glMatrixMode(GL_MODELVIEW);
        glLoadIdentity();

        glDisable(GL_LIGHTING);

        glColor3f(0.0, 0.0, 0.0);
        void *font = GLUT_BITMAP_HELVETICA_18;

        const char *LABEL_TYPE_NAMES[NB_HOR] = {
            "flat", "round", "free", "tangent"
        };
        const char *LABEL_GRADE_NAMES[NB_VER] = {
            "grade 2", "grade 3", "grade 4"
        };

        for (int i = 0; i < NB_HOR; i++) {
            const char *str = LABEL_TYPE_NAMES[i];
            int w = (int)getBitmapStringWidth(font, str);
            int x = labelWidth + i * boxWidth + (boxWidth - w) /2;
            renderBitmapString(x, g_viewportHeight - 20, font, str);
        }

        for (int i = 0; i < NB_VER; i++) {
            const char *str = LABEL_GRADE_NAMES[i];
            int w = (int)getBitmapStringWidth(font, str);
            int x = 0 + labelWidth - w - 10;
            int y = g_viewportHeight - (labelHeight + boxHeight * i + (boxHeight) / 2);
            renderBitmapString(x, y, font, str);
        }
    }

    g_drawState.m_pointSize = 0.25f;
    g_drawState.pushDrawModeOff(OD_ORIENTATION);
    g_drawState.pushDrawModeOff(OD_MAGNITUDE);

    // draw each box:
    for (int i = 0; i < NB_HOR; i++) {
        for (int j = 0; j < NB_VER; j++) {
            int x = labelWidth + i * boxWidth;
            int y = g_viewportHeight - (labelHeight + (j+1) * boxHeight);
            glViewport(x, y, boxWidth, boxHeight);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();
            glMatrixMode(GL_PROJECTION);
            const float screenWidth = 1000.0f;
            glLoadIdentity();
            GLpick::g_frustumWidth = 2.0 *  (double)boxWidth / screenWidth;
            GLpick::g_frustumHeight = 2.0 *  (double)boxHeight / screenWidth;
            glFrustum(
                -GLpick::g_frustumWidth / 2.0, GLpick::g_frustumWidth / 2.0,
                -GLpick::g_frustumHeight / 2.0, GLpick::g_frustumHeight / 2.0,
                GLpick::g_frustumNear, GLpick::g_frustumFar);
            glMatrixMode(GL_MODELVIEW);
            glTranslatef(0.0f, 0.0f, -40.0f);

            // draw back plane:
            float r = 0.85f + 0.15f * (float)((g_randCol + i * 13 + j * 7) % 11) / 11.0f;
            glColor3f(r, r, r);
            glDisable(GL_LIGHTING);
            glBegin(GL_QUADS);
            glVertex3f(-100.0f, -100.0f, -50.0f);
            glVertex3f(100.0f, -100.0f, -50.0f);
            glVertex3f(100.0f, 100.0f, -50.0f);
            glVertex3f(-100.0f, 100.0f, -50.0f);
            glEnd();

            glEnable(GL_LIGHTING);
            glEnable(GL_LIGHT0);
            glEnable(GL_NORMALIZE);
            glColor3fm(1.0f, 0.0f, 0.0f);
            glEnable(GL_DEPTH_TEST);
            glEnable(GL_CULL_FACE);
            glCullFace(GL_BACK);


            mv X; // X is initialized below
            const char *name = "-";
            switch (i) {
                case 0:
                    // flat
                    switch (j + 2) {
                        case 2:
                            name = "flat point";
                            X = no ^ ni;
                            break;
                        case 3:
                            name = "line";
                            X = no ^ e1 ^ ni;
                            break;
                        case 4:
                            name = "plane";
                            X = no ^ (e2 - 2.0f * e3) ^ e1  ^ ni;
                            break;
                    }
                    break;
                case 1:
                    // round
                    switch (j + 2) {
                        case 2:
                            name = "point pair";
                            X = c3gaPoint(_vectorE3GA(3.0f * e1)) ^ c3gaPoint(_vectorE3GA(-3.0 * e1));
                            break;
                        case 3:
                            name = "circle";
                            X = c3gaPoint(_vectorE3GA(3.0f * e1)) ^ c3gaPoint(_vectorE3GA(3.0 * e2)) ^ c3gaPoint(_vectorE3GA(-3.0 * e1));
                            break;
                        case 4:
                            name = "sphere";
                            X = c3gaPoint(_vectorE3GA(3.0f * e1)) ^ c3gaPoint(_vectorE3GA(3.0 * e2)) ^ c3gaPoint(_vectorE3GA(-3.0 * e1)) ^ c3gaPoint(_vectorE3GA(-3.0 * e3));
                            break;
                    }
                    break;
                case 2:
                    // free
                    switch (j + 2) {
                        case 2:
                            name = "free vector";
                            X = 6.0f * e1 ^ ni;
                            break;
                        case 3:
                            name = "free bivector";
                            X = 3.0f * e1 ^ e2 ^ ni;
                            break;
                        case 4:
                            name = "free trivector";
                            X = 50.0f * e1 ^ e2 ^ e3 ^ ni;
                            break;
                    }
                    break;
                case 3:
                    // tangent
                    switch (j + 2) {
                        case 2:
                            name = "tangent vector";
                            X = 6.0f * no ^ e1;
                            break;
                        case 3:
                            name = "tangent bivector";
                            X = 3.0f * no ^ e1 ^ e2;
                            break;
                        case 4:
                            name = "tangent trivector";
                            X = 50.0f * no ^ e1 ^ e2 ^ e3;
                            break;
                    }
                    break;
            }
            draw(X);

            glMatrixMode(GL_PROJECTION);
            glLoadIdentity();
            glOrtho(0, boxWidth, 0, boxHeight, -100.0, 100.0);
            glMatrixMode(GL_MODELVIEW);
            glLoadIdentity();

            glDisable(GL_LIGHTING);

            glColor3f(0.0, 0.0, 0.0);
            void *font = GLUT_BITMAP_HELVETICA_12;

            int w = (int)getBitmapStringWidth(font, name);
            renderBitmapString((boxWidth - w)/2, boxHeight - 20, font, name);

            font = GLUT_BITMAP_HELVETICA_10;
            std::string coordStr = X.toString();
            w = (int)getBitmapStringWidth(font, coordStr.c_str());
            renderBitmapString((boxWidth - w)/2, boxHeight - 40, font, coordStr.c_str());


        }
    }

    g_drawState.popDrawMode();
    g_drawState.popDrawMode();

    glutSwapBuffers();
}

void reshape(GLint width, GLint height) {
    g_viewportWidth = width;
    g_viewportHeight = height;

}


int main(int argc, char*argv[]) {
    // profiling for Gaigen 2:
    c3ga::g2Profiling::init();

     srand(time(NULL));
     g_randCol = rand();

    // GLUT Window Initialization:
    glutInit (&argc, argv);
    glutInitWindowSize(g_viewportWidth, g_viewportHeight);
    glutInitDisplayMode( GLUT_RGB | GLUT_ALPHA | GLUT_DOUBLE | GLUT_DEPTH);
    glutCreateWindow(WINDOW_TITLE);

    // Register callbacks:
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);

    glutMainLoop();

    return 0;
}