#include <iostream>
#include <fstream>
#include <cmath>
#include <cstring>

#include "GL/gl.h"
#include "GL/glu.h"
#include "GL/glut.h"

#include "SeamImg.h"

using namespace std;

#define PI 3.1415926535897931

SeamImg simg;

Img img;
Img newweights;
int width;

double circle_x, circle_y;
int cx, cy;

bool forward = false;
bool edit_weight=0;

bool paint_solid=0;
bool paint_remove=0;
bool paint_empty=0;



void printppm4(Img* img, const char* filename)
{
    ofstream out(filename);
    if(!out)
        cout<<"Cannot open file of this name: "<<filename<<endl;
    else {
        out<<"P3"<<endl;
        out<<img->w<<" "<<img->h<<endl;
        out<<"255"<<endl;

        for(int i = 0; i < 4*img->w*img->h; i+=4)
            out<<(int)img->data[i]<<" "<<(int)img->data[i+1]<<" "<<
                (int)img->data[i+2]<<endl;

        out.close();
    }
}

void glCircle3f(double x, double y, double radius) {
    float angle;
    glPushMatrix();
    glLoadIdentity();
    glDisable(GL_TEXTURE_2D);
    glColor3f(0.f, 1.f, 0.f);
    glLineWidth(1.0f);
    glBegin(GL_LINE_LOOP);
    for(int i = 0; i < 100; i++) {
        angle = i*2*PI/100;
        glVertex2f(x + (cos(angle) * radius)*(simg.buff.h-2)/(simg.buff.w-2), 
                        y + (sin(angle) * radius));
    }
    glEnd();
    glEnable(GL_TEXTURE_2D);
    glPopMatrix();
}  

double dist2d(int x1, int y1, int x2, int y2)
{
    return sqrt(pow((double)(x2-x1),2.) + pow((double)(y2-y1),2.));
}


/** PROTOTYPES **/
void redraw();
void init();
void resize(GLint w, GLint h);
void keyfunc(GLubyte key, GLint x, GLint y);
void keyspecialfunc(int key, int x, int y);
void activeMouseMotion(int x, int y);
void mousePress(int button, int state, int x, int y);

void redraw()
{
    //Clear the buffer.
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);


    if( edit_weight )
    {
        double radius = 0.08;
        int rad = radius/2. * simg.buff.h;

        if( paint_solid || paint_remove || paint_empty)
        {
            for(int i = ((cx-rad<0)?0:(cx-rad)); 
                    i < ((cx+rad>=newweights.w)?newweights.w:(cx+rad)); ++i)
            {
                for(int j = ((cy-rad<0)?0:(cy-rad)); 
                        j < ((cy+rad>=newweights.h)?newweights.h:(cy+rad)); ++j)
                {
                    if((int)dist2d(i,j,cx,cy) <= rad)
                        newweights.data[newweights.ind_col(i,j)] =
                            ((paint_solid)?SOLIDIFY:
                             ((paint_remove)?REMOVE:NOCHANGE));
                }
            }
        }


        Img mix;
        mix.w = img.w-2;
        mix.h = img.h-2;
        mix.Bpp = img.Bpp;
        mix.data = new unsigned char[mix.h*mix.w*mix.Bpp];

        for(int j = 0; j < mix.h; ++j)
        {
            for(int i = 0; i < mix.w; ++i)
            {
                mix.data[mix.ind_col(i,j)] = img.data[img.ind_col(i,j)];
                mix.data[mix.ind_col(i,j)+1] = img.data[img.ind_col(i,j)+1];
                mix.data[mix.ind_col(i,j)+2] = img.data[img.ind_col(i,j)+2];
                mix.data[mix.ind_col(i,j)+3] = img.data[img.ind_col(i,j)+3];

                double alpha = 0.15;
                if(newweights.data[newweights.ind_col(i,j)] == REMOVE)
                {
                    mix.data[mix.ind_col(i,j)+1] = 
                        alpha*mix.data[mix.ind_col(i,j)+1] + (1-alpha)*255;
                }
                if(newweights.data[newweights.ind_col(i,j)] == SOLIDIFY)
                {
                    mix.data[mix.ind_col(i,j)] = 
                        alpha*mix.data[mix.ind_col(i,j)] + (1-alpha)*255;
                }
            }
        }
        glDrawPixels(mix.w, mix.h, GL_RGBA, GL_UNSIGNED_BYTE, mix.data);
        glCircle3f(circle_x,circle_y,radius);

        delete[] mix.data;
    }
    else
    {
        glDrawPixels(img.w, img.h, GL_RGBA, GL_UNSIGNED_BYTE, img.data);
    }

    //Swap this rendered buffer onto screen.
    glutSwapBuffers();
}

/**
 * GLUT calls this function when the window is resized.
 */
void resize(GLint w, GLint h)
{
    if (h == 0)
        h = 1;

    width = w;

    if(width <= simg.buff.w-2)
    {
        if(width <= (simg.buff.w-2)/2)
            width = (simg.buff.w-2)/2;
    }
    else
    {
        if(width > (simg.buff.w-2)*3/2)
            width = (simg.buff.w-2)*3/2;
    }
    int p = (100*((double)width/((double)(simg.buff.w-2))));
    if( p == 49 )
        p = 50;
    if( p == 149 )
        p = 150;

    cout<<p<<"%"<<endl;

    if(!edit_weight)
    {
        delete[] img.data;
        simg.build_scaled(width, &img);
    }

    // Reset the current viewport and perspective transformation
    glViewport(0, 0, w, h);

    // Tell GLUT to call redraw()
    glutPostRedisplay();
}

/*
 * GLUT calls this function when any key is pressed while our window has
 * focus.
 */
void keyfunc(GLubyte key, GLint x, GLint y)
{
	//If key is the ESC key.
	if(key == 27)
		exit(0);

        // Compute seams
        if(key == 'c' || key == 'C')
        {
            if(edit_weight) 
            {
                simg.compute_seams((simg.buff.w-2)/2, forward, &newweights);
                edit_weight = false;
            }
            glutPostRedisplay();
        }

        // Dump current image.
	if(key == 'd' || key == 'D')
        {
            cout<<"Input filename to dump current image to. Will output to filename.ppm"<<endl;
            string filename;
            cin>>filename;
            filename += ".ppm";
            simg.build_scaled(width, &img);
            printppm4(&img, filename.c_str());
            cout<<"...Written."<<endl;
        }


}

//Mouse press method. This gets called when a mouse button is pressed.
void mousePress(int button, int state, int x, int y)
{
    if( edit_weight )
    {
        cx = x;
        cy = y;

        if( state == GLUT_DOWN )
        {
            if ( (button == GLUT_LEFT_BUTTON) && !paint_remove && !paint_empty) 
                paint_solid = true;
            else if ( (button == GLUT_RIGHT_BUTTON) && !paint_solid && 
                                                        !paint_empty)
                paint_remove = true;
            else if ( (button == GLUT_MIDDLE_BUTTON) && !paint_solid && 
                                                        !paint_remove)
                paint_empty = true;
        }

        if( state == GLUT_UP )
        {
            if (button == GLUT_LEFT_BUTTON)
                paint_solid = false;
            else if (button == GLUT_RIGHT_BUTTON)
                paint_remove = false;
            else if (button == GLUT_MIDDLE_BUTTON)
                paint_empty = false;
        }
        glutPostRedisplay();
    }
}

//Active mouse motion method.
void mouseMotion(int x, int y)
{
    if( edit_weight )
    {
        cx = x;
        cy = y;
        circle_x = 2.*((double)x)/((double)simg.buff.w-2.) - 1.;
        circle_y = 2.*((double)(simg.buff.h-2-y))/((double)simg.buff.h-2.) - 1.;
        glutPostRedisplay();
    }
}


void init()
{
    // set up GLUT callbacks.
    glutDisplayFunc(redraw);
    glutReshapeFunc(resize);
    glutKeyboardFunc(keyfunc);
    glutMouseFunc(mousePress);
    glutPassiveMotionFunc(mouseMotion);
    glutMotionFunc(mouseMotion);
    glPixelZoom(1.,-1.);
    glRasterPos2d(-1, 1);
}

/**
 * Main entrance point, obviously.
 * Sets up some stuff then passes control to glutMainLoop() which never
 * returns.
 */
int main(int argc, char* argv[])
{

    img = readpng(argv[1]);
    simg.init(img);

    for(int i=2; i < argc; ++i)
    {
        if(strcmp(argv[i],"--forward") == 0)
            forward = true;
        if(strcmp(argv[i],"--editweights") == 0)
            edit_weight = true;
    }

    int xres=simg.buff.w-2;
    int yres=simg.buff.h-2;

    newweights.w = xres;
    newweights.h = yres;
    newweights.Bpp = 1;
    newweights.data = new unsigned char[xres*yres];
    for(int i=0; i < xres*yres; ++i)
        newweights.data[i] = NOCHANGE;
    
    width = xres;

    if( !edit_weight )
        simg.compute_seams((simg.buff.w-2)/2, forward, &newweights);

    // Init image.
    delete[] img.data;
    simg.build_scaled(width, &img);


    // OpenGL will take out any arguments intended for its use here.
    // Useful ones are -display and -gldebug.
    glutInit(&argc, argv);

    // Get a double-buffered, depth-buffer-enabled window, with an
    // alpha channel.
    // These options aren't really necessary but are here for examples.
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);

    glutInitWindowSize(xres, yres);
    glutInitWindowPosition(300, 100);

    glutCreateWindow("CS176 Seam Carving");

    //Setup callbacks and other scene initializations.
    init();

    // From here on, GLUT has control,
    glutMainLoop();

}

