//=============================================================================================
// Mintaprogram: Z�ld h�romsz�g. Ervenyes 2019. osztol.
//
// A beadott program csak ebben a fajlban lehet, a fajl 1 byte-os ASCII karaktereket tartalmazhat, BOM kihuzando.
// Tilos:
// - mast "beincludolni", illetve mas konyvtarat hasznalni
// - faljmuveleteket vegezni a printf-et kiveve
// - Mashonnan atvett programresszleteket forrasmegjeloles nelkul felhasznalni es
// - felesleges programsorokat a beadott programban hagyni!!!!!!! 
// - felesleges kommenteket a beadott programba irni a forrasmegjelolest kommentjeit kiveve
// ---------------------------------------------------------------------------------------------
// A feladatot ANSI C++ nyelvu forditoprogrammal ellenorizzuk, a Visual Studio-hoz kepesti elteresekrol
// es a leggyakoribb hibakrol (pl. ideiglenes objektumot nem lehet referencia tipusnak ertekul adni)
// a hazibeado portal ad egy osszefoglalot.
// ---------------------------------------------------------------------------------------------
// A feladatmegoldasokban csak olyan OpenGL fuggvenyek hasznalhatok, amelyek az oran a feladatkiadasig elhangzottak 
// A keretben nem szereplo GLUT fuggvenyek tiltottak.
//
// NYILATKOZAT
// ---------------------------------------------------------------------------------------------
// Nev    : Cseh Viktor
// Neptun : GG2DP5
// ---------------------------------------------------------------------------------------------
// ezennel kijelentem, hogy a feladatot magam keszitettem, es ha barmilyen segitseget igenybe vettem vagy
// mas szellemi termeket felhasznaltam, akkor a forrast es az atvett reszt kommentekben egyertelmuen jeloltem.
// A forrasmegjeloles kotelme vonatkozik az eloadas foliakat es a targy oktatoi, illetve a
// grafhazi doktor tanacsait kiveve barmilyen csatornan (szoban, irasban, Interneten, stb.) erkezo minden egyeb
// informaciora (keplet, program, algoritmus, stb.). Kijelentem, hogy a forrasmegjelolessel atvett reszeket is ertem,
// azok helyessegere matematikai bizonyitast tudok adni. Tisztaban vagyok azzal, hogy az atvett reszek nem szamitanak
// a sajat kontribucioba, igy a feladat elfogadasarol a tobbi resz mennyisege es minosege alapjan szuletik dontes.
// Tudomasul veszem, hogy a forrasmegjeloles kotelmenek megsertese eseten a hazifeladatra adhato pontokat
// negativ elojellel szamoljak el es ezzel parhuzamosan eljaras is indul velem szemben.
//=============================================================================================
#include "framework.h"


// vertex shader in GLSL: It is a Raw string (C++11) since it contains new line characters
const char* const vertexSource = R"(
	#version 330				// Shader 3.3
	precision highp float;		// normal floats, makes no difference on desktop computers

	uniform mat4 MVP;			// uniform variable, the Model-View-Projection transformation matrix
	layout(location = 0) in vec2 vp;	// Varying input: vp = vertex position is expected in attrib array 0

	void main() {
		gl_Position = vec4(vp.x, vp.y, 0, 1) * MVP;		// transform vp from modeling space to normalized device space
	}
)";

// fragment shader in GLSL
const char* const fragmentSource = R"(
	#version 330			// Shader 3.3
	precision highp float;	// normal floats, makes no difference on desktop computers
	
	uniform vec3 color;		// uniform variable, the color of the primitive
	out vec4 outColor;		// computed color of the current pixel

	void main() {
		outColor = vec4(color, 1);	// computed color is the color of the primitive
	}
)";

const int BASECIRCLESEG = 100;  //how many triangle the base stands from

GPUProgram gpuProgram; // vertex and fragment shaders

void MVPInit() {
	float MVPtransf[4][4] = { 1, 0, 0, 0,    // MVP matrix,
							 0, 1, 0, 0,    // row-major!
							 0, 0, 1, 0,
							 0, 0, 0, 1 };

	int location = glGetUniformLocation(gpuProgram.getId(), "MVP");    // Get the GPU location of uniform variable MVP
	glUniformMatrix4fv(location, 1, GL_TRUE,
		&MVPtransf[0][0]);    // Load a 4x4 row-major float matrix to the specified location
}


struct Color {
	float r, g, b;
	Color(int _r = 255, int _g = 179, int _b = 0) {
		r = (float)_r / 255;
		g = (float)_g / 255;
		b = (float)_b / 255;
	}
	Color(float _r, float _g, float _b) {
		r = _r; g = _g; b = _b;
	}
};

void setBackgroundColor(Color color) {
	glClearColor(color.r, color.g, color.b, 1);     // background color
	glClear(GL_COLOR_BUFFER_BIT); // clear frame buffer
}

unsigned int vao;

class drawableBase {
protected:
	
	unsigned int vbo; //vertex buffer obj
	std::vector<vec2> points;
	Color color;
public:

	virtual void draw()  = 0;

	virtual void setColor(Color newColor) = 0;

	void init() {
		glGenVertexArrays(1, &vao); // get 1 vao id
		glBindVertexArray(vao);     // make it active

		glGenBuffers(1, &vbo); // Generate 1 buffer
		glBindBuffer(GL_ARRAY_BUFFER, vbo);
		glBufferData(GL_ARRAY_BUFFER,  // Copy to GPU target
			points.size() * sizeof(vec2), // # bytes
			points.data(),         // address
			GL_DYNAMIC_DRAW);  // we do not change later

		glEnableVertexAttribArray(0); // AttribArray 0
		glVertexAttribPointer(0,      // vbo -> AttribArray 0
			2, GL_FLOAT,
			GL_FALSE, // two floats/attrib, not fixed-point
			0, NULL); // stride, offset: tightly packed
	}
};

float degToRad(float courrentTriangleAngle){
	return (courrentTriangleAngle * M_PI / 180.0);
}



class GraphPoint : drawableBase {
	vec3 p3D; //center of the point in hyperbolic splace
	vec2 p; // center on the base disk
public:
	
	GraphPoint() {
		p = vec2((((float)rand() / (float)(RAND_MAX))*2)-1, (((float)rand() / (float)(RAND_MAX)) * 2) - 1);
		p3D = Vec2ToVec3(p);
		placeCircleToNewCoordinates();
		setColor(Color(244, 164, 96));
	}

	vec2 getPoint() {
		return p;
	}

	void setCoordinates(vec2 _p) {
		p = _p;
		p3D = Vec2ToVec3(p);
		placeCircleToNewCoordinates();
	}

	vec3 Vec2ToVec3(vec2 p) {
		return vec3(p.x, p.y, sqrt(p.x * p.x + p.y * p.y + 1));
	}

	void placeCircleToNewCoordinates() {
		points.clear();
		float piece = 360.0 / BASECIRCLESEG;
		float courrentTriangleAngle = 0;

		for (int i = 0; i < BASECIRCLESEG; i++) {
			points.push_back(vec2(p.x / p3D.z, p.y / p3D.z));

			
			//points.push_back(vec2(
			//	p3D.x + (cos(degToRad(courrentTriangleAngle)) / 30) * (cos((abs(p3D.x) * abs(p3D.x) * abs(p3D.x)) / 3 + (abs(p3D.x) * abs(p3D.y) * abs(p3D.y)) + abs(p3D.x) * abs(p3D.z) * abs(p3D.z) + abs(p3D.x)) /30 ),
			//	p3D.y + (sin(degToRad(courrentTriangleAngle)) / 30) * (sin((abs(p3D.y) * abs(p3D.y) * abs(p3D.y)) / 3 + (abs(p3D.y) * abs(p3D.x) * abs(p3D.x)) + abs(p3D.y) * abs(p3D.z) * abs(p3D.z) + abs(p3D.y)) /30 )));

			//points.push_back(vec2(
			//	p3D.x + (cos(degToRad(courrentTriangleAngle)) / 20) * (cos(degToRad(90 - abs(1/tan(8 * p3D.x + 8 * p3D.y - 8 * p3D.z))))),
			//	p3D.y + (sin(degToRad(courrentTriangleAngle)) / 20) * (sin(degToRad(90 - abs(1/tan(8 * p3D.x + 8 * p3D.y - 8 * p3D.z)))))));

			points.push_back(vec2(
				p3D.x / p3D.z + (cos(degToRad(courrentTriangleAngle)) / 20) / p3D.z,
				p3D.y / p3D.z + (sin(degToRad(courrentTriangleAngle)) / 20) / p3D.z));


			//printf("%f \n", abs(1 / tan(2 * p3D.x + 2 * p3D.y - 2 * p3D.z)));
			// points.push_back(vec2(p.x +(cos(courrentTriangleAngle * M_PI / 180.0) /30),p.y + (sin(courrentTriangleAngle * M_PI / 180.0) /30))); alap volt ami kort rajzolt a pontokba


			courrentTriangleAngle += piece;
			
			points.push_back(vec2(
				p3D.x / p3D.z + (cos(degToRad(courrentTriangleAngle)) / 20) / p3D.z,
				p3D.y / p3D.z + (sin(degToRad(courrentTriangleAngle)) / 20) / p3D.z));
			
		}
	}

	vec2 vec3tovec2(vec3 c) {
		vec2 temp;
		temp.x = c.x;
		temp.y = c.y;
		return temp;
	}

	void reColor() const {
		int location = glGetUniformLocation(gpuProgram.getId(), "color");
		glUniform3f(location, color.r, color.g, color.b); // 3 floats
	}

	void setColor(Color newColor) override{
		color = newColor;
	}

	void draw() override
	{
		init();
		reColor();
		MVPInit();
		glBindVertexArray(vao);  // Draw call
		glDrawArrays(GL_TRIANGLES, 0, points.size() /*# Elements*/);

	}
};

struct pairs {
	GraphPoint p1;
	GraphPoint p2;
};

class Line : drawableBase {
	

public:
	Line(vec2 _p1, vec2 _p2) {
		setColor(Color(254, 254, 0));
		points.push_back(_p1 / sqrt(_p1.x * _p1.x + _p1.y * _p1.y + 1));
		points.push_back(_p2 / sqrt(_p2.x * _p2.x + _p2.y * _p2.y + 1));
	}

	void reColor() const {
		int location = glGetUniformLocation(gpuProgram.getId(), "color");
		glUniform3f(location, color.r, color.g, color.b); // 3 floats
	}
	void setColor(Color newColor) override {
		color = newColor;
	}

	void draw() override {
		init();
		reColor();
		glBindVertexArray(vao);  // Draw call
		glDrawArrays(GL_LINES, 0, points.size() /*# Elements*/);
	}
};

class graphManager {

	GraphPoint* gps = new GraphPoint[50];
	std::vector<pairs> pair;
	std::vector<Line> line;
public:

	graphManager() {

		for (int i = 0; i < 50; i++) {
			for (int j = i+1; j < 50; j++) {
				int rndm = rand() % 100;
				if (rndm < 5) {
					pairs p; p.p1 = gps[i]; p.p2 = gps[j];
					pair.push_back(p);
				}
			}
		}
	}

	void draw() {
		for (int i = 0; i < 50; i++) {
			gps[i].draw();
		}

		for (int i = 0; i < pair.size(); i++) {
			Line temp(pair[i].p1.getPoint(), pair[i].p2.getPoint());
			temp.draw();
			line.push_back(temp);
		}
	}
};

graphManager gm;
// Initialization, create an OpenGL context
void onInitialization() {
	glViewport(0, 0, windowWidth, windowHeight);
	
	// create program for the GPU
	gpuProgram.create(vertexSource, fragmentSource, "outColor");
}

// Window has become invalid: Redraw
void onDisplay() {
	MVPInit();
	setBackgroundColor(Color(0,0,0)); //set the background color to black
	gm.draw();

	glutSwapBuffers(); // exchange buffers for double buffering
}

// Key of ASCII code pressed
void onKeyboard(unsigned char key, int pX, int pY) {
	if (key == 'd') glutPostRedisplay();         // if d, invalidate display, i.e. redraw
}

// Key of ASCII code released
void onKeyboardUp(unsigned char key, int pX, int pY) {
}

// Move mouse with key pressed
void onMouseMotion(int pX, int pY) {	// pX, pY are the pixel coordinates of the cursor in the coordinate system of the operation system
	// Convert to normalized device space
	float cX = 2.0f * pX / windowWidth - 1;	// flip y axis
	float cY = 1.0f - 2.0f * pY / windowHeight;
	printf("Mouse moved to (%3.2f, %3.2f)\n", cX, cY);
}

// Mouse click event
void onMouse(int button, int state, int pX, int pY) { // pX, pY are the pixel coordinates of the cursor in the coordinate system of the operation system
	// Convert to normalized device space
	float cX = 2.0f * pX / windowWidth - 1;	// flip y axis
	float cY = 1.0f - 2.0f * pY / windowHeight;

	char* buttonStat;
	switch (state) {
	case GLUT_DOWN: buttonStat = "pressed"; break;
	case GLUT_UP:   buttonStat = "released"; break;
	}

	switch (button) {
	case GLUT_LEFT_BUTTON:   printf("Left button %s at (%3.2f, %3.2f)\n", buttonStat, cX, cY);   break;
	case GLUT_MIDDLE_BUTTON: printf("Middle button %s at (%3.2f, %3.2f)\n", buttonStat, cX, cY); break;
	case GLUT_RIGHT_BUTTON:  printf("Right button %s at (%3.2f, %3.2f)\n", buttonStat, cX, cY);  break;
	}
}

// Idle event indicating that some time elapsed: do animation here
void onIdle() {
	long time = glutGet(GLUT_ELAPSED_TIME); // elapsed time since the start of the program
}