#include "nebulus.h"

GLfloat knot_time, old_knot_time;

typedef struct
{
  GLfloat r, g, b;
} glcolors;

typedef struct
{
  int numfaces, numverts, numsides;
  GLuint faces[16*64*4*4];
  glcoord vertices[16*64];
  glcolors colors[16*64];
} tknotobject;

tknotobject knotobject;


void
initknot(int scaling_factor1, int scaling_factor2, GLfloat radius1, GLfloat radius2, GLfloat radius3)
{
  int count1, count2;
  GLfloat alpha, beta, distance, mindistance, rotation;
  GLfloat x, y, z, dx, dy, dz;
  GLfloat value, modulus, dist;
  int index1, index2;

  knotobject.numsides = 4;
  knotobject.numverts = 0;
  alpha = 0;
  for (count2 = 0; count2 < scaling_factor2; count2++) {
    alpha = alpha + 2*PI / scaling_factor2;
    x = radius2 * cos(2 * alpha) + radius1 * sin(alpha);
    y = radius2 * sin(2 * alpha) + radius1 * cos(alpha);
    z = radius2 * cos(3 * alpha);
    dx = -2 * radius2 * sin(2 * alpha) + radius1 * cos(alpha);
    dy = 2 * radius2 * cos(2 * alpha) - radius1 * sin(alpha);
    dz = -3 * radius2 * sin(3 * alpha);
    value = sqrt(dx * dx + dz * dz);
    modulus = sqrt(dx * dx + dy * dy + dz * dz);

    beta = 0;
    for (count1 = 0; count1 < scaling_factor1; count1++) {
      beta = beta + 2 * PI / scaling_factor1;

      knotobject.vertices[knotobject.numverts].x = x - radius3 * (cos(beta) * dz - sin(beta) * dx * dy / modulus) / value;
      knotobject.vertices[knotobject.numverts].y = y - radius3 * sin(beta) * value / modulus;
      knotobject.vertices[knotobject.numverts].z = z + radius3 * (cos(beta) * dx + sin(beta) * dy * dz / modulus) / value;


      dist = sqrt(knotobject.vertices[knotobject.numverts].x*knotobject.vertices[knotobject.numverts].x +
                   knotobject.vertices[knotobject.numverts].y*knotobject.vertices[knotobject.numverts].y +
                   knotobject.vertices[knotobject.numverts].z*knotobject.vertices[knotobject.numverts].z);

      knotobject.colors[knotobject.numverts].r = ((2/dist)+(0.5*sin(beta)+0.4))/2.0;
      knotobject.colors[knotobject.numverts].g = ((2/dist)+(0.5*sin(beta)+0.4))/2.0;
      knotobject.colors[knotobject.numverts].b = ((2/dist)+(0.5*sin(beta)+0.4))/2.0;

      knotobject.numverts++;
    }
  }

  for (count1 = 0; count1 < scaling_factor2; count1++) {
    index1 = count1 * scaling_factor1;
    index2 = index1 + scaling_factor1;
    index2 = index2 % knotobject.numverts;
    rotation = 0;
    mindistance = (knotobject.vertices[index1].x - knotobject.vertices[index2].x) * (knotobject.vertices[index1].x - knotobject.vertices[index2].x) +
                   (knotobject.vertices[index1].y - knotobject.vertices[index2].y) * (knotobject.vertices[index1].y - knotobject.vertices[index2].y) +
                   (knotobject.vertices[index1].z - knotobject.vertices[index2].z) * (knotobject.vertices[index1].z - knotobject.vertices[index2].z);
    for (count2 = 1; count2 < scaling_factor1; count2++) {
      index2 = count2 + index1 + scaling_factor1;
      if (count1 == scaling_factor2 - 1)
        index2 = count2;
      distance = (knotobject.vertices[index1].x - knotobject.vertices[index2].x) * (knotobject.vertices[index1].x - knotobject.vertices[index2].x) +
                  (knotobject.vertices[index1].y - knotobject.vertices[index2].y) * (knotobject.vertices[index1].y - knotobject.vertices[index2].y) +
                  (knotobject.vertices[index1].z - knotobject.vertices[index2].z) * (knotobject.vertices[index1].z - knotobject.vertices[index2].z);
      if (distance < mindistance) {
        mindistance = distance;
        rotation = count2;
      }
    }

    for (count2 = 0; count2 < scaling_factor1; count2++) {
      knotobject.faces[4*(index1+count2)+0] = index1 + count2;

      index2 = count2 + 1;
      index2 = index2 % scaling_factor1;
      knotobject.faces[4*(index1+count2)+1] = index1 + index2;

      index2 = (count2 + rotation + 1);
      index2 = index2 % scaling_factor1;
      knotobject.faces[4*(index1+count2)+2] = (index1 + index2 + scaling_factor1) % knotobject.numverts;

      index2 = (count2 + rotation);
      index2 = index2 % scaling_factor1;

      knotobject.faces[4*(index1+count2)+3] = (index1 + index2 + scaling_factor1) % knotobject.numverts;
      knotobject.numfaces++;
    }
  }
}


void
recalcknot(int scaling_factor1, int scaling_factor2, GLfloat radius1, GLfloat radius2, GLfloat radius3)
{
  int count1, count2;
  GLfloat alpha, beta;
  GLfloat x, y, z, dx, dy, dz;
  GLfloat value, modulus;
  int index1, index2;
  GLfloat distance, Mindistance, rotation;

  knotobject.numverts = 0;
  alpha = 0;
  for (count2 = 0; count2 < scaling_factor2; count2++) {
    alpha = alpha + 2*PI / scaling_factor2;
    x = radius2 * cos(2 * alpha) + radius1 * sin(alpha);
    y = radius2 * sin(2 * alpha) + radius1 * cos(alpha);
    z = radius2 * cos(3 * alpha);
    dx = -2 * radius2 * sin(2 * alpha) + radius1 * cos(alpha);
    dy = 2 * radius2 * cos(2 * alpha) - radius1 * sin(alpha);
    dz = -3 * radius2 * sin(3 * alpha);
    value = sqrt(dx * dx + dz * dz);
    modulus = sqrt(dx * dx + dy * dy + dz * dz);

    beta = 0;
    for (count1 = 0; count1 < scaling_factor1; count1++) {
      beta = beta + 2 * PI / scaling_factor1;

      knotobject.vertices[knotobject.numverts].x = x - radius3 * (cos(beta) * dz - sin(beta) * dx*dy/modulus) / value;
      knotobject.vertices[knotobject.numverts].y = y - radius3 *  sin(beta) * value / modulus;
      knotobject.vertices[knotobject.numverts].z = z + radius3 * (cos(beta) * dx + sin(beta) * dy*dz/modulus) / value;

      knotobject.numverts++;
    }
  }

  knotobject.numfaces = 0;
  for (count1 = 0; count1 < scaling_factor2; count1++) {
    index1 = count1 * scaling_factor1;
    index2 = index1 + scaling_factor1;
    index2 = index2 % knotobject.numverts;
    rotation = 0;
    Mindistance = (knotobject.vertices[index1].x - knotobject.vertices[index2].x) * (knotobject.vertices[index1].x - knotobject.vertices[index2].x) +
                   (knotobject.vertices[index1].y - knotobject.vertices[index2].y) * (knotobject.vertices[index1].y - knotobject.vertices[index2].y) +
                   (knotobject.vertices[index1].z - knotobject.vertices[index2].z) * (knotobject.vertices[index1].z - knotobject.vertices[index2].z);
    for (count2  = 1; count2 < scaling_factor1; count2++) {
      index2 = count2 + index1 + scaling_factor1;
      if (count1 == scaling_factor2 - 1)
        index2 = count2;
      distance = (knotobject.vertices[index1].x - knotobject.vertices[index2].x) * (knotobject.vertices[index1].x - knotobject.vertices[index2].x) +
                  (knotobject.vertices[index1].y - knotobject.vertices[index2].y) * (knotobject.vertices[index1].y - knotobject.vertices[index2].y) +
                  (knotobject.vertices[index1].z - knotobject.vertices[index2].z) * (knotobject.vertices[index1].z - knotobject.vertices[index2].z);
      if (distance < Mindistance) {
        Mindistance = distance;
        rotation = count2;
      }
    }

    for (count2 = 0; count2 < scaling_factor1; count2++) {
      knotobject.faces[4*(index1+count2)+0] = index1 + count2;

      index2 = index2 % scaling_factor1;

      knotobject.faces[4*(index1+count2)+2] = (index1 + index2 + scaling_factor1) %  knotobject.numverts;

      index2 = count2 + rotation;
      index2 = index2 % scaling_factor1;

      knotobject.faces[4*(index1+count2)+3] = (index1 + index2 + scaling_factor1) % knotobject.numverts;
      knotobject.numfaces++;
    }
  }
}


void
draw_the_knot(void)
{
  int i, j, num;

  glBegin(GL_QUADS);
  j = knotobject.numfaces*4;
  for (i = 0; i < j; i++) {
    num = knotobject.faces[i];
    glColor3f(knotobject.colors[num].r*2.5, knotobject.colors[num].g*2, knotobject.colors[num].b*1.5);
    glVertex3f(knotobject.vertices[num].x, knotobject.vertices[num].y,  knotobject.vertices[num].z);
  }
  glEnd();
}


void
createknot(void)
{
  recalcknot(16, 64, 3.4*sin(0.02*knot_time)-1, 4*cos(0.006*knot_time)-2, 1.1);
  glTranslatef(0.0f, 0.2f, -30.0f);
  glRotatef(x_angle, 1.0f, 0.0f, 0.0f);
  glRotatef(y_angle, 0.0f, 1.0f, 0.0f);
  glRotatef(z_angle, 0.0f, 0.0f, 1.0f);
  draw_the_knot();
}


void
drawknot(void)
{
  glPushMatrix();
  createknot();
  glPopMatrix();
}


void
render_knot(void)
{
  glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
  glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
  glColor3f(0.9f, 0.9f, 0.9f);
  glDisable(GL_BLEND);
  glDisable(GL_NORMALIZE);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_TEXTURE_2D);
  if (!create_quadratic) {
    myquadratic = gluNewQuadric();
    gluQuadricNormals(myquadratic, GLU_SMOOTH);
    gluQuadricTexture(myquadratic, GL_TRUE);
    create_quadratic = TRUE;
  }
  recalc_perspective();
  glViewport(0, 0, point_general->WIDTH, point_general->HEIGHT);
}


void
draw_knot(void)
{
  if (!create_knot) {
    initknot(16, 64, 2, 2.0, 1.0);
    create_knot = TRUE;
  }
  y_speed = y_old_speed;
  y_speed = reduce_vsync(y_speed);
  x_angle += x_speed;
  if(x_angle >= 360.0) x_angle -= 360.0;
  y_angle += y_speed;
  if(y_angle >= 360.0) y_angle -= 360.0;
  z_angle += z_speed;
  if(z_angle >= 360.0) z_angle -= 360.0;
  old_knot_time = knot_time;
  speed_bg = old_speed_bg;
  speed_bg = reduce_vsync(speed_bg);
  elapsed_time += speed_bg;
  point_general->energy = reduce_vsync(point_general->energy);
  knot_time += point_general->energy;
  if (knot_time < old_knot_time)
    knot_time = old_knot_time;
  render_knot();
  drawknot();
  draw_background(FALSE);
}
