import net.java.games.jogl.*;
import net.java.games.jogl.GLU.*;

import java.awt.Polygon;
import java.awt.Dimension;
import java.awt.event.MouseMotionListener;
import java.awt.event.MouseListener;
import java.awt.event.MouseEvent;

import java.io.Serializable;

import java.util.Vector;

/**
 * Basic class to view 3D worlds. This class creates a 3D-viewPlatform where different Groups can attach
 * to to make a whole 3D scenery.
 */
public class JBCad3D_OpenGl_v0
    implements Serializable {

////////////////////OPENGL variables********************************
  public static final float[] RED = {
      1.0f, 0.0f, 0.0f, 1.0f};
  public static final float[] DARK_GREEN = {
      0.0f, 0.3f, 0.0f, 1.f};
  public static final float[] GREEN = {
      0.0f, 1.0f, 0.0f, 1.0f};
  public static final float[] BLUE = {
      0.0f, 0.0f, 1.0f, 1.0f};
  public static final float[] YELLOW = {
      1.0f, 1.0f, 0.0f, 1.0f};
  public static final float[] ORANGE = {
      0.97f, 0.36f, 0.0f, 1.0f};
  public static final float[] CYAN = {
      0.0f, 1.0f, 1.0f, 1.0f};
  public static final float[] MAGENTA = {
      1.0f, 0.0f, 1.0f, 1.0f};
  public static final float[] PINK = {
      1.0f, 0.69f, 0.69f, 1.0f};

  public static final float[] WHITE = {
      1.0f, 1.0f, 1.0f, 1.0f, 1.0f};
  public static final float[] LIGHT_GRAY = {
      0.75f, 0.75f, 0.75f, 1.0f};
  public static final float[] GRAY = {
      0.5f, 0.5f, 0.5f, 1.0f};
  public static final float[] DARK_GRAY = {
      0.25f, 0.25f, 0.25f, 1.0f};
  public static final float[] BLACK = {
      0.0f, 0.0f, 0.0f, 1.0f};

  public static final float[] GRAY00 = {
      1.00f, 1.00f, 1.00f, 1.0f};
  public static final float[] GRAY10 = {
      0.90f, 0.90f, 0.90f, 1.0f};
  public static final float[] GRAY20 = {
      0.80f, 0.80f, 0.80f, 1.0f};
  public static final float[] GRAY30 = {
      0.70f, 0.70f, 0.70f, 1.0f};
  public static final float[] GRAY40 = {
      0.60f, 0.60f, 0.60f, 1.0f};
  public static final float[] GRAY50 = {
      0.50f, 0.50f, 0.50f, 1.0f};
  public static final float[] GRAY60 = {
      0.40f, 0.40f, 0.40f, 1.0f};
  public static final float[] GRAY70 = {
      0.30f, 0.30f, 0.30f, 1.0f};
  public static final float[] GRAY80 = {
      0.20f, 0.20f, 0.20f, 1.0f};
  public static final float[] GRAY90 = {
      0.10f, 0.10f, 0.10f, 1.0f};
  public static final float[] GRAY100 = {
      0.00f, 0.00f, 0.00f, 1.0f};

  public static final float[][] COLORS3D = {
      BLUE, RED, YELLOW, MAGENTA, CYAN,
      GREEN, ORANGE, BLUE, GRAY, WHITE,
      LIGHT_GRAY, GRAY, DARK_GRAY, BLACK};

  public static final float[][] GRAYS3D = {
      GRAY10, GRAY20, GRAY30,
      GRAY40, GRAY50, GRAY60,
      GRAY70, GRAY80, GRAY90};

  private static float[] normalfv = null;
  private static GLJPanel canvas;
  private static Animator animator = null;
  private Vector vector_of_spaces = new Vector();
  /**
   * Creates a empty instance of JCCad3D.
   * The groundpane has dimension 0f 20m x 20m
   */
  public JBCad3D_OpenGl_v0() {
  } //constructor

  public GLJPanel getCanvas() {
    canvas = GLDrawableFactory.getFactory().createGLJPanel(new
        GLCapabilities());

    canvas.addGLEventListener(new D3Renderer());

    System.err.println("Starting animator");
    animator = new Animator(canvas);
    animator.start();

    try {
      System.err.println("Going to sleep");
      Thread.sleep(1000);
      System.err.println("Waking up");
    } catch (InterruptedException e) {
    }

    return canvas;
  } //getCanvas

  public void close_canvas() {
    if (animator != null)
      animator.stop();
  } //close_canvas

  public void reset() {
    vector_of_spaces.clear();
  } //reset

  public void setGebouwData(Vector GebouwData) {
    if (GebouwData == null)
      return;

    vector_of_spaces = (Vector)GebouwData.clone();
  } //setGebouwData

  /*****************************************************************************
   * Private functions
   */

//****************************************************************
   private void drawPolygonBox(GLDrawable drawable, Polygon pg,
                               float[] mat_colora) {
     GL gl = drawable.getGL();


     float room_height = 2500.0f;
     float[] mat_color = COLORS3D[0];

     /*
      * Controle of polygon counterclockwise is getekend, als de polygoon clockwise
      * getekend is, dan de volgorde omdraaien
      */
     Polygon tmp = new Polygon();
     if (getOpp(pg) < 0) {
       Polygon tmpPg = new Polygon();
       for (int i = pg.npoints - 1; i >= 0; i--) {
         tmpPg.addPoint(pg.xpoints[i], pg.ypoints[i]);
       }
       pg = tmpPg;
     }

     float[] normal;

     float[][] vertices = get_vertices_of_polygon(pg, room_height);

     int[][] indices = get_indices_of_polygon(vertices);

     gl.glHint(gl.GL_LINE_SMOOTH_HINT, gl.GL_NICEST);
     gl.glEnable(gl.GL_LINE_SMOOTH);

     gl.glShadeModel(GL.GL_SMOOTH);

     for (int n = 0; n < 2; n++) {
       switch (n) {
         case 0:
           gl.glColor3fv(BLACK);
           gl.glLineWidth(2.0f);
           gl.glPolygonMode(GL.GL_FRONT, GL.GL_LINE);
           gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, BLACK);
           gl.glPolygonOffset(0.25f, 0.01f);
           gl.glEnable(GL.GL_POLYGON_OFFSET_LINE);
           break;
         case 1:
           gl.glColor3fv(mat_color);
           gl.glPolygonMode(GL.GL_FRONT, GL.GL_FILL);
           gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE,
                           mat_color);
           break;
       }

       normalfv = null;
       draw_top_bottom_face(drawable, 0, vertices, indices);
       for (int i = 1; i < indices.length - 1; i++) {
         gl.glBegin(gl.GL_POLYGON);
         normal = null;
         for (int ii = 0; ii < indices[i].length; ii++) {
           normal = normal3fv(i, vertices, indices);
           gl.glNormal3fv(normal);
           gl.glVertex3fv(vertices[indices[i][ii]]);
         }
         gl.glEnd();

       }
       normalfv = null;
       draw_top_bottom_face(drawable, indices.length - 1, vertices,
                            indices);
       gl.glColor3fv(mat_color);
     }

     gl.glEnable(GL.GL_LIGHT0);
   } //drawPolygonBox

  private float[][] get_vertices_of_polygon(Polygon pg, float room_height) {
    /*
     * Eerste de polygoon punten van het grondvlak in 3D wijzigen,
     * daarna het bovenvlak toevoegen
     */
    float[][] vertices = new float[pg.npoints * 2][3];
    for (int i = 0; i < pg.npoints; i++) {
      vertices[i][0] = (float) pg.xpoints[i] / 1000.0f;
      vertices[i][1] = room_height / 1000.0f;
      vertices[i][2] = (float) pg.ypoints[i] / 1000.0f;
      vertices[i + pg.npoints][0] = (float) pg.xpoints[i] / 1000.0f;
      vertices[i + pg.npoints][1] = (2500.0f + room_height) / 1000.0f;
      vertices[i + pg.npoints][2] = (float) pg.ypoints[i] / 1000.0f;
    }
    return vertices;
  } //get_vertices_of_polygon

  private int[][] get_indices_of_polygon(float[][] vertices) {
    /*
     * Uitgangspunt bij het toekennen van de indices is dat het grondvlak
     * counterclockwise is getekend
     */
    int n = vertices.length / 2;
    int[][] indices = new int[n + 2][];
    //bovenvlak
    int i;
    indices[0] = new int[n];
    for (i = 0; i < n; i++)
      indices[0][i] = i + n;
    //zijvlakken
    for (i = 0; i < n; i++) {
      indices[i + 1] = new int[4];
      indices[i + 1][0] = i;
      indices[i + 1][1] = (i == n - 1 ? 0 : i + 1);
      indices[i + 1][2] = (i == n - 1 ? n : i + 1 + n);
      indices[i + 1][3] = i + n;
    }
    //ondervlak, normaal moet naar onderen wijzen!! daarom counterclockwise
    indices[n + 1] = new int[n];
    for (i = n - 1; i >= 0; i--)
      indices[n + 1][i] = n - i - 1;
    return indices;
  } //get_indices_of_polygon

  private void draw_top_bottom_face(GLDrawable drawable, int i,
                                    float[][] vertices,
                                    int[][] indices) {
    GL gl = drawable.getGL();
    GLU glu = drawable.getGLU();
    double[][] verticesdv = new double[indices[i].length][3];

    for (int ii = 0; ii < indices[i].length; ii++) {
      for (int p = 0; p < vertices[indices[i][ii]].length; p++) {
        verticesdv[ii][p] = (double) vertices[indices[i][ii]][p];
      }
    }

    normalfv = normal3fv(i, vertices, indices);

    GLUtesselator tobj = glu.gluNewTess();
    TessCallback tessCallback = new TessCallback(gl, glu);

    glu.gluTessCallback(tobj, GLU.GLU_TESS_VERTEX, tessCallback);
    glu.gluTessCallback(tobj, GLU.GLU_TESS_EDGE_FLAG, tessCallback);
    glu.gluTessCallback(tobj, GLU.GLU_TESS_BEGIN, tessCallback);
    glu.gluTessCallback(tobj, GLU.GLU_TESS_END, tessCallback);
    glu.gluTessCallback(tobj, GLU.GLU_TESS_ERROR, tessCallback);

    glu.gluTessBeginPolygon(tobj, null);
    glu.gluTessBeginContour(tobj);

    for (int ii = 0; ii < indices[i].length; ii++) {
      glu.gluTessVertex(tobj, verticesdv[ii], verticesdv[ii]);
    }
    glu.gluTessEndContour(tobj);
    glu.gluTessEndPolygon(tobj);
    glu.gluDeleteTess(tobj);
  } //draw_top_bottom_face

  private float[] normal3fv(int i, float[][] vdata, int[][] indices) {
    float[] d1 = new float[3];
    float[] d2 = new float[3];
    for (int j = 0; j < 3; j++) {
      d1[j] = vdata[indices[i][0]][j] - vdata[indices[i][1]][j];
      d2[j] = vdata[indices[i][1]][j] - vdata[indices[i][2]][j];
    }

    float[] norm = new float[3];
    norm[0] = d1[1] * d2[2] - d1[2] * d2[1];
    norm[2] = d1[2] * d2[0] - d1[0] * d2[2];
    norm[1] = d1[0] * d2[1] - d1[1] * d2[0];

//normalize the vector
    double temp = Math.sqrt(norm[0] * norm[0] +
                            norm[1] * norm[1] +
                            norm[2] * norm[2]);
    norm[0] /= temp;
    norm[1] /= temp;
    norm[2] /= temp;
    return norm;
  } //normal3fv

  private void drawCirclePlate(GLDrawable drawable, float diameter,
                               float[] mat_color) {
    GL gl = drawable.getGL();
    int teeth = 50;
    float angle;
    gl.glBegin(GL.GL_TRIANGLE_FAN);
    gl.glMaterialfv(GL.GL_FRONT, GL.GL_AMBIENT_AND_DIFFUSE, mat_color);
    gl.glNormal3f(0.0f, 1.0f, 0.0f);
    gl.glVertex3f(0.0f, 0.0f, 0.0f);
    for (int i = teeth; i >= 0; i--) {
      angle = i * (2.0f * (float) Math.PI) / teeth;
      gl.glNormal3f(0.0f, 1.0f, 0.0f);
      gl.glVertex3f(diameter * (float) Math.cos(angle), 0.0f,
                    diameter * (float) Math.sin(angle));
    }
    gl.glEnd();
  } //drawCirclePlate

//****************************************************************
//Inner class*****************************************************
//****************************************************************
    class D3Renderer
        implements GLEventListener, MouseListener,
        MouseMotionListener {
      int[] vertexProgram;
      private float view_rotx = 00.0f, view_roty = 0.0f, view_rotz = 0.0f;
      private int prevMouseX, prevMouseY;

      public void init(GLDrawable drawable) {
        GL gl = drawable.getGL();
        GLU glu = drawable.getGLU();

    canvas.setGL(new DebugGL(canvas.getGL()));
    System.err.println("DRAWABLE GL IS: " +
                       canvas.getGL().getClass().getName());
    System.err.println("DRAWABLE GLU IS: " +
                       canvas.getGLU().getClass().getName());

        gl.glClearColor(1.0f, 1.0f, 1.0f, 1.0f); //erasing color of background color
        gl.glColor3fv(BLACK); // drawing color

        gl.glShadeModel(GL.GL_SMOOTH);
        gl.glEnable(GL.GL_CULL_FACE);
        gl.glEnable(GL.GL_DEPTH_TEST);
        gl.glDepthFunc(gl.GL_LESS);

        float[] posLight0 = {
            0.0f, 00.0f, 0.0f, 1.0f};
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_POSITION, posLight0);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_AMBIENT, WHITE);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_DIFFUSE, WHITE);
        gl.glLightfv(GL.GL_LIGHT0, GL.GL_SPECULAR, WHITE);

        float[] posLight1 = {
            100.0f, 200.0f, 100.0f, 1.0f};
        gl.glLightfv(GL.GL_LIGHT1, GL.GL_POSITION, posLight1);
        gl.glLightfv(GL.GL_LIGHT1, GL.GL_AMBIENT, WHITE);
        gl.glLightfv(GL.GL_LIGHT1, GL.GL_DIFFUSE, WHITE);
        gl.glLightfv(GL.GL_LIGHT1, GL.GL_SPECULAR, WHITE);
        gl.glLightModeli(GL.GL_LIGHT_MODEL_TWO_SIDE, GL.GL_FALSE);
        gl.glEnable(GL.GL_LIGHTING);
        gl.glEnable(GL.GL_LIGHT0);
        gl.glEnable(GL.GL_LIGHT1);
        gl.glEnable(GL.GL_NORMALIZE);

        drawable.addMouseListener(this);
        drawable.addMouseMotionListener(this);
      }

      public void reshape(GLDrawable drawable, int x, int y, int width,
                          int height) {
        GL gl = drawable.getGL();
        GLU glu = drawable.getGLU();
        System.err.println("GL_VENDOR: " + gl.glGetString(GL.GL_VENDOR));
        System.err.println("GL_RENDERER: " + gl.glGetString(GL.GL_RENDERER));
        System.err.println("GL_VERSION: " + gl.glGetString(GL.GL_VERSION));
        System.err.println();

        float h = (float) height / (float) width;

        gl.glViewport(0, 0, width, height);
        gl.glMatrixMode(GL.GL_PROJECTION);
        gl.glLoadIdentity();
        glu.gluPerspective(45.0, 1 /*h*/, 0.01, 1000.0);

        gl.glMatrixMode(GL.GL_MODELVIEW);
        gl.glLoadIdentity();
        glu.gluLookAt(0.0, -18.0, -100.0, 0, 0, 0, 0, 1, 0);
      }

      public void display(GLDrawable drawable) {
        GL gl = drawable.getGL();

        gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);

        gl.glPushMatrix();

        gl.glRotatef( -view_rotx, 1.0f, 0.0f, 0.0f);
        gl.glRotatef(view_roty, 0.0f, 1.0f, 0.0f);
        gl.glPushMatrix();
        gl.glTranslatef(0.0f, -20.0f, 0.0f);
        drawCirclePlate(drawable, 1000.0f, DARK_GREEN);
        gl.glPopMatrix();

        Polygon ruimte;
        for (int n = 0; n < vector_of_spaces.size(); n++) {
          ruimte = (Polygon) vector_of_spaces.get(n);
          gl.glPushMatrix();
          gl.glTranslatef(0.0f, -20.0f, 0.0f);
          drawPolygonBox(drawable, ruimte, BLUE);
          gl.glPopMatrix();
        }

        gl.glPopMatrix();

        gl.glFlush();
      }

      public void displayChanged(GLDrawable drawable, boolean modeChanged,
                                 boolean deviceChanged) {}

      // Methods required for the implementation of MouseListener
      public void mouseEntered(MouseEvent e) {}

      public void mouseExited(MouseEvent e) {}

      public void mousePressed(MouseEvent e) {
        prevMouseX = e.getX();
        prevMouseY = e.getY();
      }

      public void mouseReleased(MouseEvent e) {}

      public void mouseClicked(MouseEvent e) {}

      // Methods required for the implementation of MouseMotionListener
      public void mouseDragged(MouseEvent e) {
        int x = e.getX();
        int y = e.getY();
        Dimension size = e.getComponent().getSize();

        float thetaY = 360.0f *
            ( (float) (x - prevMouseX) / (float) size.width);
        float thetaX = 360.0f *
            ( (float) (prevMouseY - y) / (float) size.height);

        prevMouseX = x;
        prevMouseY = y;

        view_rotx += thetaX;
        view_roty += thetaY;
      }

      public void mouseMoved(MouseEvent e) {}
    }

  public static class TessCallback
      extends net.java.games.jogl.GLUtesselatorCallbackAdapter {
    GL gl;
    GLU glu;
    boolean flag;

    public TessCallback(GL gl, GLU glu) {
      this.gl = gl;
      this.glu = glu;
    };
    public void begin(int type) {
      gl.glBegin(type);
    }

    public void end() {
      gl.glEnd();
    }

    public void vertex(Object data) {
      if (data instanceof double[]) {
        double[] d = (double[]) data;
        if (d.length == 6) {
          double[] d2 = {
              d[0], d[1], d[2]};
          if (normalfv != null)
            gl.glNormal3fv(normalfv);
          gl.glVertex3dv(d2);
          d2 = new double[] {
              d[3], d[4], d[5]};
          gl.glColor3dv(d2);
        }
        else if (d.length == 3) {
          if (normalfv != null)
            gl.glNormal3fv(normalfv);
          gl.glVertex3dv(d);
        }
      }
    }

    public void edgeFlag(boolean flag) {
      gl.glEdgeFlag(flag);
      flag = !flag;
    }

    public void error(int errnum) {
      String estring;
      estring = glu.gluErrorString(errnum);
      System.out.println("Tessellation Error: " + estring);
      //System.exit(0);
      throw new RuntimeException();
    }

    public void combine(double[] coords, Object[] data,
                        float[] weight, Object[] outData) {
      double[] vertex = new double[6];

      int i;
      vertex[0] = coords[0];
      vertex[1] = coords[1];
      vertex[2] = coords[2];
      for (i = 3; i < 6; i++)
        vertex[i] = weight[0] * ( (double[]) data[0])[i] +
            weight[1] * ( (double[]) data[1])[i] +
            weight[2] * ( (double[]) data[2])[i] +
            weight[3] * ( (double[]) data[3])[i];
      outData[0] = vertex;
    }
  } //End TessCallback

// ******************************************************************************
// ******************************************************************************

  private double getOpp(float[][] punten) {
    /*
     * Tegen de klok in draaien is negatief
     * Met de klok mee draaien is positief
     */
    if (punten == null)
      return (0.0);

    float[] P = new float[3];
    float[] Pprev = new float[3];
    float[] Pnext = new float[3];

    double opp = 0;
    for (int i = 1; i < punten.length - 1; i++) {
      P = punten[i];
      Pnext = punten[i + 1];
      Pprev = punten[i - 1]; //*
      opp = opp + P[0] * (Pprev[2] - Pnext[2]);
    }

    return (opp / 2.0);
  } //getOpp

  private double getOpp(Polygon polygon) {
    if (polygon == null || polygon.npoints == 0)
      return 0.0;

    float[][] punten = new float[polygon.npoints + 2][3];
    int npoints = polygon.npoints;

    punten[0][0] = polygon.xpoints[npoints - 1];
    punten[0][1] = 0.0f;
    punten[0][2] = polygon.ypoints[npoints - 1];

    for (int i = 0; i < npoints; i++) {
      punten[i + 1][0] = polygon.xpoints[i];
      punten[i + 1][1] = 0.0f;
      punten[i + 1][2] = polygon.ypoints[i];
    }
    punten[npoints + 1][0] = polygon.xpoints[0];
    punten[npoints + 1][1] = 0.0f;
    punten[npoints + 1][2] = polygon.ypoints[0];

    return getOpp(punten);
  } //getOpp

} //end of class

