#include <GL/glut.h>
#include <cmath>
#include <ctime>
#include <vector>


//  I F S
std::vector<std::vector<float>> vertices;

std::vector<float> map( float red, float x, float y, float z,
	   					std::vector<float>& u)
{
	std::vector<float> v = {
		x + red * u[0], y + red * u[1], z + red * u[2]
	};
	return v;
}

const float sqrt2 = sqrt(2.0f);
const float sqrt3 = sqrt(3.0f);
const float sqrt8 = sqrt(8.0f);

std::vector<float> map0( std::vector<float>& u)
{
	return map( 1.0f/sqrt3, 1.0f, 0.0f, -1.0f/sqrt8, u );
}

std::vector<float> map1( std::vector<float>& u)
{
	return map( 1.0f/sqrt3, -0.5f, sqrt3/2.0f, -1.0f/sqrt8, u );
}

std::vector<float> map2( std::vector<float>& u)
{
	return map( 1.0f/sqrt3, -0.5f, -sqrt3/2.0f, -1.0f/sqrt8, u );
}

std::vector<float> map3( std::vector<float>& u)
{
	return map( 1.0f/sqrt3, 0.0f, 0.0f, sqrt2-1.0f/sqrt8, u );
}

typedef std::vector<float> (*funptr) (std::vector<float>&);
std::vector<funptr> f = { map0, map1, map2, map3 };

float rpoint(void)
{
	const int mo= 1<<24;
	float x= (rand() % mo) / float(mo);
	return 2.0f * x - 1.0f;
}

void ifs(int n, std::vector<float> u )
{
	if (n==0)
		vertices.push_back(u);
	else
		for (int i=0; i<4; ++i) 
			ifs(n-1, f[i](u));
}

void ifs2(int n, int np )
{
	float x, y, z;
	for (int p=0; p<np; ++p) {
		std::vector<float> u;
		do {
			x= rpoint();
			y= rpoint();
			z= rpoint();
		} while ( x*x + y+y + z*z > 1.0f );
		std::vector<float> v= { x, y, z };
		for (int k=0; k<n; ++k)
			v= f[rand() % 4](v);
		vertices.push_back(v);
	}
}

// =============================================
float angleX = 0.0f, angleY = 0.0f;
int lastX, lastY;
bool rotating = false;
GLuint texture;
float radius = 1.0;


void loadTexture() {
    const int size = 64;
    unsigned char checkerTexture[size][size][3];
    for (int i = 0; i < size; ++i) {
        for (int j = 0; j < size; ++j) {
            int color = ((i / 8 + j / 8) % 2) * 255;
            checkerTexture[i][j][0] = checkerTexture[i][j][1] = checkerTexture[i][j][2] = color;
        }
    }
    glGenTextures(1, &texture);
    glBindTexture(GL_TEXTURE_2D, texture);
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, size, size, 0, GL_RGB, GL_UNSIGNED_BYTE, checkerTexture);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
}

void drawSphere(float radius, float x, float y, float z) {
    glPushMatrix();
    glTranslatef(x, y, z);
    GLUquadric* quad = gluNewQuadric();
    gluQuadricTexture(quad, GL_TRUE);
    gluSphere(quad, radius, 32, 32);
    gluDeleteQuadric(quad);
    glPopMatrix();
}

void display() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glLoadIdentity();
    glTranslatef(0.0f, 0.0f, -5.0f);
    glRotatef(angleX, 1, 0, 0);
    glRotatef(angleY, 0, 1, 0);
    
//    glEnable(GL_TEXTURE_2D);
//    glBindTexture(GL_TEXTURE_2D, texture);
    
    for (const auto& v : vertices) {
        drawSphere(radius, v[0], v[1], v[2]);
    }
    
 //   glDisable(GL_TEXTURE_2D);
    glutSwapBuffers();
}

void reshape(int w, int h) {
    glViewport(0, 0, w, h);
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45, (float)w / h, 1, 10);
    glMatrixMode(GL_MODELVIEW);
}

void mouse(int button, int state, int x, int y) {
    if (button == GLUT_LEFT_BUTTON) {
        rotating = (state == GLUT_DOWN);
        lastX = x;
        lastY = y;
    }
}

void motion(int x, int y) {
    if (rotating) {
        angleX += (y - lastY);
        angleY += (x - lastX);
        lastX = x;
        lastY = y;
        glutPostRedisplay();
    }
}

void initGL() {
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_TEXTURE_2D);
    loadTexture();
}

int main(int argc, char** argv) {

	std::srand(time(0));
	int no= 3;
	if (argc > 1) {
		no= atoi(argv[1]);
	}

	for (int k=0; k<no; ++k)
		radius/= sqrt3;

	std::vector<float> u = {0.0f, 0.0f, 0.0f};
	ifs(no, u);

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH);
    glutInitWindowSize(1024, 1024);
    glutCreateWindow("I F S");
    glutDisplayFunc(display);
    glutReshapeFunc(reshape);
    glutMouseFunc(mouse);
    glutMotionFunc(motion);
    initGL();
    glutMainLoop();
    return 0;
}

