////////////////////////////////////////////////////////////////////////
//
// hello-world.cpp
//
// This is a simple, introductory OpenCV program. The program reads an
// image from a file, inverts it, and displays the result. 
//
////////////////////////////////////////////////////////////////////////
#include "stdafx.h"
#include "hw3.h"

typedef unsigned int uint;

#define SIZE 512

//#define INCREMENTAL
#define WINDOWS

#define CV_SUBDIV2D_POINT_IS_DELAUNAY( pt ) \
       (CV_IS_SET_ELEM(pt) && ((pt)->flags & CV_SUBDIV2D_VIRTUAL_POINT_FLAG)==0)

CvSubdiv2D *subdiv;
BwImage imgA;
IplImage *img;
GLint viewport[4];
Extent world = {-1, 1, -1, 1};
int height, width;
int nonBCount = 0, bCount = 0;
const double a = .375;
const double b = .125;
const double c = .75;
const double d = .1875;

#ifdef INCREMENTAL
#define FPS 100000
bool firstRender = true;
#endif

bool subDivide = false;

double xw(int x) {
       return ((world.r - world.l) * (x - viewport[0]) / viewport[2]) + world.l;
}

double yw(int y) {
       return ((world.t - world.b) *  ((viewport[3] -y) - viewport[1]) / viewport[3]) + world.b;
}

/*  Helper function to determine if a vertex is on the boundary of the image
 *  Once we know this, we know which type of subdivision to implement */
bool isBoundaryPoint(CvSubdiv2DPoint *pt){
    if(cvRound(pt->pt.x) == 0 || cvRound(pt->pt.x) == width ||
       cvRound(pt->pt.y) == 0 || cvRound(pt->pt.y) == height)
        return true;
    else
        return false;
}

/* Helper function to determine if an edge is on the boundary of the image
 * Once we know this, we knwo which type of subdivision to create */
bool isBoundaryEdge(CvSubdiv2DPoint* org, CvSubdiv2DPoint* dst){
    if( (cvRound(org->pt.x) == 0) && (cvRound(dst->pt.x) == 0) )
        return true;
    if( (cvRound(org->pt.x) == width) && (cvRound(dst->pt.x) == width) )
        return true;
    if( (cvRound(org->pt.y) == 0) && (cvRound(dst->pt.y) == 0) )
        return true;
    if( (cvRound(org->pt.y) == height) && (cvRound(dst->pt.y) == height) )
        return true;

    return false;
}

void enforcePixel(int *x, int *y){
#ifdef WINDOWS
	if(*x < 0)
		*x = 0;
	if(*y < 0)
		*y = 0;
	if(*x > width)
		*x = width;
	if(*y > height)
		*y = height;
#endif
}

/*  This function will rasterize either the triangle making up the current
 *  Delaunay triangulation, OR, if subdivision mode is on, will perform the
 *  subdivision of the original Delaunay subdivision on the fly */

void draw_subdiv_facet( CvSubdiv2DEdge edge, CvGraphEdge *ge = NULL )
{
    CvSubdiv2DEdge t = edge;
    CvSubdiv2DEdge orig = t;
    int i, count = 0;
    CvPoint* buf = 0;

    if(!t) return;
    count = 3;

    /*  Rasterize triangles as normal */
    if(!subDivide){
        buf = (CvPoint*)malloc( count * sizeof(buf[0]));

        // gather points
        t = orig;
        for( i = 0; i < count; i++ )
        {
            CvSubdiv2DPoint* pt = cvSubdiv2DEdgeOrg( t );
            if( !pt )
                break;
            buf[i] = cvPoint( cvRound(pt->pt.x), cvRound(pt->pt.y));
            t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
        }

        if( i == count )
        {
            int x, y;
            glBegin(GL_TRIANGLES);
            for(i=0; i<count; i++){
                x = buf[i].x;
                y = buf[i].y;
                uint intensity = imgA[y][x];
                glColor3ub(intensity, intensity, intensity);
                glVertex2d(xw(x), yw(y));
            }
            glEnd();
        }
    }

    /*  SUBDIVISION */
    else{
        //  opposite vertices
        CvSubdiv2DPoint *t1, *t2;
        double newX, newY;
        bool bound = false;
        buf = (CvPoint*)malloc( count * 2 * sizeof(buf[0]));

        // gather points using subdivision formulae
        t = orig;
        for( i = 0; i < count; i++ )
        {
            //  first, we need to get the new edge-vertices
            CvSubdiv2DPoint* org = cvSubdiv2DEdgeOrg( t );
            CvSubdiv2DPoint* dst = cvSubdiv2DEdgeDst( t );

            if( !org )
                break;

            //  check for boundary edge:
            if(bound = isBoundaryEdge(org, dst)){
                bCount++;
                //  new boundary edge vertex is just 1/2 * both surrounding verts
                buf[2*i+1] = cvPoint( (int)((org->pt.x + dst->pt.x)/2.0 + .5), 
                                      (int)((org->pt.y + dst->pt.y)/2.0 + .5));

                //  now we want the new vertex point
                t1 = dst;
                t2 = cvSubdiv2DEdgeDst(cvSubdiv2DGetEdge( t, CV_PREV_AROUND_ORG)); 

                if(bound = isBoundaryEdge(t1, t2)){
                    newX = c*(double)(org->pt.x) + b*(double)(t1->pt.x+t2->pt.x);
                    newY = c*(double)(org->pt.y) + b*(double)(t1->pt.y+t2->pt.y);
                    /*
                    if(newX < 0 || newX > width || newY < 0 || newY > height)
                        cout << newX  << " " << newY << endl;
                        */
                    buf[2*i] = cvPoint( cvRound(newX), cvRound(newY));
                }
            }
            // regular edge
            else{
                nonBCount++;

                //  get the opposite vertices from this edge
                t1 = cvSubdiv2DEdgeDst(cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT));
                t2 = cvSubdiv2DEdgeDst(cvSubdiv2DGetEdge( t, CV_PREV_AROUND_ORG));
                
                if(t2->pt.x < 0 || t2->pt.x > width || t2->pt.y < 0 || t2->pt.y > height){
                    //cout << CV_IS_SET_ELEM(t2) << endl;
                    buf[2*i+1] = cvPoint( cvRound( (org->pt.x + dst->pt.x)/2.0),
                         cvRound( (org->pt.y + dst->pt.y)/2.0));
                }
                else{
                   // printf("[%f %f] -> [%f %f]\n", t2->pt.x, t2->pt.y, t1->pt.x, t1->pt.y);
                   //  loop subdivision formula for a new edge-vertex
                    newX = a*(double)(org->pt.x + dst->pt.x) + b*(double)(t1->pt.x + t2->pt.x);
                    newY = a*(double)(org->pt.y + dst->pt.y) + b*(double)(t1->pt.y + t2->pt.y);

                    buf[2*i+1] = cvPoint(cvRound(newX), cvRound(newY));
                }
                // printf("[%f %f] -> [%f %f]\n", t2->pt.x, t2->pt.y, t1->pt.x, t1->pt.y);
                // printf("org[%f %f] dst[%f %f] = new[%f %f]\n", org->pt.x, org->pt.y,
                //  dst->pt.x, dst->pt.y, newX, newY);

            }

            //  set the new vertex point via subdivision
            if(!bound)
                buf[2*i] = cvPoint( cvRound(org->pt.x), cvRound(org->pt.y));
            t = cvSubdiv2DGetEdge( t, CV_NEXT_AROUND_LEFT );
        }

        int x, y;
        uint intensity;
        glBegin(GL_TRIANGLES);

        //  each former triangle is now split up into an inner triangle
        //  and three outer triangles like so:
        //
        //     /\
        //    /__\
        //   /\  /\
        //  /__\/__\
        //
        //  The following code rasterizes each of them

        //  inner triangle
        for(i=1; i<=5; i+=2){
            x = buf[i].x;
            y = buf[i].y;
			enforcePixel(&x, &y);
            intensity = imgA[y][x];
            glColor3ub(intensity, intensity, intensity);
            glVertex2d(xw(x), yw(y));
        }

        //  three outer triangles
        for(i=0; i<count; i++){
           //  inner vertex
           int in = (i*2)+1; 
           x = buf[in].x;
           y = buf[in].y;
		   enforcePixel(&x, &y);
           intensity = imgA[y][x];
           glColor3ub(intensity, intensity, intensity);
           glVertex2d(xw(x), yw(y));
           //  corner vertex
           x = buf[(in+1)%6].x;
           y = buf[(in+1)%6].y;
		   enforcePixel(&x, &y);
           intensity = imgA[y][x];
           glColor3ub(intensity, intensity, intensity);
           glVertex2d(xw(x), yw(y));
           //  next inner vertex 
           x = buf[(in+2)%6].x;
           y = buf[(in+2)%6].y;
		   enforcePixel(&x, &y);
           intensity = imgA[y][x];
           glColor3ub(intensity, intensity, intensity);
           glVertex2d(xw(x), yw(y));
        }
        glEnd();
    }

    free(buf);
}

void display(){

  glLoadIdentity();
  CvSeqReader reader;
  int total = subdiv->edges->total;
  int tris = 0;
  bCount = 0;
  nonBCount = 0;

  //  iterate through the delaunay triangulation
  cvStartReadSeq((CvSeq *)subdiv->edges, &reader);
  for(int i=0; i<total; i++){
      CvQuadEdge2D * edge = (CvQuadEdge2D *) reader.ptr;
      CvGraphEdge *ge = (CvGraphEdge *)reader.ptr;

      if(CV_IS_SET_ELEM(edge)){
          CvSubdiv2DPoint * org = cvSubdiv2DEdgeOrg( (CvSubdiv2DEdge) edge );
          CvSubdiv2DPoint * dst = cvSubdiv2DEdgeDst( (CvSubdiv2DEdge) edge );
          if(org && dst && CV_SUBDIV2D_POINT_IS_DELAUNAY(org)){
          //   printf("[%f %f] -> [%f %f]\n", org->pt.x, org->pt.y, dst->pt.x, dst->pt.y);
          CvSubdiv2DEdge e =  (CvSubdiv2DEdge)edge;

          tris +=2;
          //  upper left
          draw_subdiv_facet(cvSubdiv2DGetEdge( e, CV_NEXT_AROUND_LEFT), ge);
          //  lower right
          draw_subdiv_facet(cvSubdiv2DGetEdge( e, CV_PREV_AROUND_ORG), ge);
#ifdef INCREMENTAL
          if(firstRender){
              glutSwapBuffers();
              usleep(1000000/FPS);
          }
#endif
          }
      }
      CV_NEXT_SEQ_ELEM(subdiv->edges->elem_size, reader);
  }

#ifdef INCREMENTAL
  firstRender = false;
#endif
  //cout << "total # triangles:" << tris << endl;
  //cout << "boundary edge count: " << bCount << endl;
  //cout << "non-boundary edge count: " << nonBCount << endl;
    glFlush();
    glutSwapBuffers();
    glClear(GL_COLOR_BUFFER_BIT);
}

void onMouseButton(int button, int state, int x, int y) {

    if (button == GLUT_LEFT_BUTTON) {
        if (state == GLUT_DOWN) {
        }
    }
    else if (button == GLUT_RIGHT_BUTTON) {
        if (state == GLUT_DOWN) {
        }
    }
}

//  glut keypress func.
void onKeyPress(unsigned char key, int x, int y) {
    if ( key == 't' ) {      // request transformation from user
    }
    else if ( key == 's' ){  // start the subdivision
        subDivide = !subDivide;
        glutPostRedisplay();
    }
    else if ( key == 'q' ) { // quit the program.
        exit( 0 );
    }
}

void initGlut(){
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB );
    glutInitWindowSize(width,height);
    glutInitWindowPosition(700,100);
    glutCreateWindow("CISC 829 Homework 3");
    glutDisplayFunc(display);
    glutMouseFunc(onMouseButton);
    glutKeyboardFunc(onKeyPress);

    glGetIntegerv(GL_VIEWPORT, viewport);
}

int main(int argc, char *argv[])
{
  img = 0; 
  int step,channels;
  int percent;
  uchar *data;
  int i;

  if(argc<2){
    printf("Usage: main <image-file-name> <%% of pixels to sample>\n\7");
    exit(0);
  }

  // load an image  
  img=cvLoadImage(argv[1], 0);
  if(!img){
    printf("Could not load image file: %s\n",argv[1]);
    exit(0);
  }

  if(argc<3)
	  percent = 10;
  else
	percent = atoi(argv[2]);

  imgA = *(new BwImage(img));

  // get the image data
  height    = img->height;
  width     = img->width;
  step      = img->widthStep;
  channels  = img->nChannels;
  data      = (uchar *)img->imageData;
  printf("Processing a %dx%d %d-step image with %d channels\n",
          width,height,step,channels); 

  // create a window
  cvNamedWindow("original image", CV_WINDOW_AUTOSIZE); 
  cvMoveWindow("original image", 100, 100);

  CvRect r = cvRect(0,0,width,height);
  subdiv = cvCreateSubdivDelaunay2D(r, cvCreateMemStorage());

  //  % of points
  for(i=0; i<(height*width/(100/percent)); i++){
     CvPoint2D32f fp = cvPoint2D32f( (float)(rand()%width),
                                     (float)(rand()%height));
     //imgA[(int)fp.y][(int)fp.x] = 255;
     cvSubdivDelaunay2DInsert( subdiv, fp );
  }
 //cvCalcSubdivVoronoi2D( subdiv );

  glutInit(&argc, argv);
  initGlut();

  // show the image
  cvShowImage("original image", img );

  // wait for key
  cvWaitKey(0);

  cvDestroyWindow("original image");
  // release the image
  // cvReleaseImage(&img );

  glutMainLoop();
  return 0;
}
