// gcc viz.c -o viz -lm -lSDL2 -lGL -lGLEW

#include <stdio.h>
#include <math.h>
#include <SDL2/SDL.h>
#include <GL/glew.h>


const GLfloat verts[6][4] = {
    //  x      y      s      t
    { -1.0f, -1.0f,  0.0f,  1.0f }, // BL
    { -1.0f,  1.0f,  0.0f,  0.0f }, // TL
    {  1.0f,  1.0f,  1.0f,  0.0f }, // TR
    {  1.0f, -1.0f,  1.0f,  1.0f }, // BR
};

const GLint indicies[] = {
    0, 1, 2, 0, 2, 3
};

const char *vert_shader_src = "\
#version 150 core                                                            \n\
in vec2 in_Position;                                                         \n\
void main()                                                                  \n\
{                                                                            \n\
    gl_Position = vec4(in_Position, 0.0, 1.0);                               \n\
}                                                                            \n\
";

const char *frag_shader_src = "\
#version 150 core                                                            \n\
uniform float time;\n\
uniform float zoom;\n\
uniform vec2 z0;\n\
uniform vec2 r;\n\
uniform vec2 mouse;\n\
uniform vec2 resolution;\n\
out vec4 out_Color;                                                          \n\
uniform sampler2D tex;                                                       \n\
#define PI 3.14159265359                                                     \n\
vec2 cmul(vec2 z1, vec2 z2)                                                 \n\
{                                                                           \n\
    return mat2(z1, -z1.y, z1.x)*z2;                                        \n\
}                                                                           \n\
vec2 cdiv(vec2 z1, vec2 z2)                                                 \n\
{                                                                           \n\
    return z1*mat2(z2, -z2.y, z2.x)/dot(z2, z2);                            \n\
}                                                                           \n\
vec2 csin(vec2 z)\n\
{\n\
    return vec2(sin(z.x)*cosh(z.y), cos(z.x)*sinh(z.y));\n\
}\n\
vec2 ccos(vec2 z)\n\
{\n\
    return vec2(cos(z.x)*cosh(z.y), -sin(z.x)*sinh(z.y));\n\
}\n\
vec2 cexp(vec2 z, vec2 c)\n\
{\n\
    float argz, m, a;\n\
    float lnz = dot(z,z);\n\
    lnz = 0.5*log(lnz);\n\
    argz = atan(z.y, z.x);\n\
    m = exp(c.x*lnz - c.y*argz);\n\
    a = c.x*argz + c.y*lnz;\n\
    return m*vec2(cos(a), sin(a));\n\
}\n\
vec3 hrgb(vec2 w)                                                           \n\
{                                                                           \n\
    float c1;\n\
    c1 = 1. - 1.0*pow(abs(sin(2.*PI*w.x))*abs(sin(2.*PI*w.y)), 0.15);\n\
    return vec3(c1, c1, c1);\n\
}                                                                            \n\
vec2 g(vec2 z)\n\
{\n\
    vec2 w;\n\
    w = vec2(1., 0.);\n\
    for (float a=0.; a<2.*PI; a+=2.*PI/5.) {\n\
        w = cdiv(w, z - 1.*sin(r.x)*vec2(cos(a + .05*r.x), sin(a + .05*r.x)) );\n\
    }\n\
    w = csin(w + vec2( 0.05*r.y, 0. ));\n\
    return w;\n\
}\n\
vec2 f(vec2 z)\n\
{\n\
    return g((g((g((g(z)))))));\n\
}\n\
void main()                                                                  \n\
{                                                                            \n\
    vec3 rgb;                                                                \n\
    vec2 z, w;                                                               \n\
    z = (gl_FragCoord.xy - resolution/2.)/zoom + z0;\n\
    //w = cdiv(vec2(1., 0.), z ); \n\
    //w = cmul(w, z - (mouse - resolution/2.)/zoom); \n\
    //w = cdiv(w, z - r); \n\
    //w = cexp(vec2(exp(1.), 0.), w); \n\
    w = f(z);\n\
    rgb = hrgb(w);                                                           \n\
    out_Color = vec4(rgb, 1.0);                                              \n\
}                                                                            \n\
";                                                                                  

int main(int argc, char *argv[])
{
    int w = 800, h = 600;
    int dx, dy, mx, my;
    int end = 0, redraw = 1, key, drag = 0, rbutton = 0;

    double zoom = 150; 
    double x0 = 0, y0 = 0, rx = 9.155, ry = 9.155;
    int rrx, rry;
    double tx, ty, ox0, oy0;

    SDL_Window *window;
    SDL_Surface *surface;
    SDL_Event event;

    //gl -> SDL related parts
    SDL_GLContext context;
    GLuint vao, vbo, ebo, tex;
    GLuint vert_shader;
    GLuint frag_shader;
    GLuint shader_prog;




    SDL_Init(SDL_INIT_EVERYTHING);

    window = SDL_CreateWindow("cplot",
            SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, w, h,
            SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_MAXIMIZED);


    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK,
                        SDL_GL_CONTEXT_PROFILE_CORE);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);


    context = SDL_GL_CreateContext(window);
    SDL_GL_SetSwapInterval(1);

    GLenum err;
    glewExperimental = GL_TRUE;
    err = glewInit();

    //gl init shaders
    GLint status;
    char err_buf[512];

    glGenVertexArrays(1, &vao);
    glBindVertexArray(vao);

    vert_shader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vert_shader, 1, &vert_shader_src, NULL);
    glCompileShader(vert_shader);
    glGetShaderiv(vert_shader, GL_COMPILE_STATUS, &status);
    if (status != GL_TRUE) {
        glGetShaderInfoLog(vert_shader, sizeof(err_buf), NULL, err_buf);
        err_buf[sizeof(err_buf)-1] = '\0';
        fprintf(stderr, "Vertex shader compilation failed: %s\n", err_buf);
        return 1;
    }

    frag_shader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(frag_shader, 1, &frag_shader_src, NULL);
    glCompileShader(frag_shader);
    glGetShaderiv(frag_shader, GL_COMPILE_STATUS, &status);
    if (status != GL_TRUE) {
        glGetShaderInfoLog(frag_shader, sizeof(err_buf), NULL, err_buf);
        err_buf[sizeof(err_buf)-1] = '\0';
        fprintf(stderr, "Fragment shader compilation failed: %s\n", err_buf);
        return 1;
    }

    shader_prog = glCreateProgram();
    glAttachShader(shader_prog, vert_shader);
    glAttachShader(shader_prog, frag_shader);
    glBindFragDataLocation(shader_prog, 0, "out_Color");
    glLinkProgram(shader_prog);
    glUseProgram(shader_prog);

    //gl init geometry
    glGenBuffers(1, &vbo);
    glBindBuffer(GL_ARRAY_BUFFER, vbo);
    glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_DRAW);

    glGenBuffers(1, &ebo);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ebo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW);

    GLint pos_attr_loc = glGetAttribLocation(shader_prog, "in_Position");
    glVertexAttribPointer(pos_attr_loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)0);
    glEnableVertexAttribArray(pos_attr_loc);

    glUniform2f(glGetUniformLocation(shader_prog, "resolution"), (float)w, (float)h);
    glUniform1f(glGetUniformLocation(shader_prog, "zoom"), (float)zoom);
    glUniform2f(glGetUniformLocation(shader_prog, "z0"), (float)x0, (float)y0);
    glUniform2f(glGetUniformLocation(shader_prog, "r"), (float)rx, (float)ry);

    //surface = SDL_GetWindowSurface(window);

    while(!end) {
        if (redraw) {
            /*
            Uint32 *pixmem = surface->pixels;
            int ptr = 0;
            double x, y, u, v, uu, vv;

            SDL_LockSurface(surface);
            for (int yy=0; yy<h; yy++) {
                y = (h/2 - yy)/zoom + y0;
                for (int xx=0; xx<w; xx++) {
                    x = (xx - w/2)/zoom + x0;

                    cdiv(&u, &v, 1, 0, x - rx, y - ry);
                    cdiv(&u, &v, u, v, x, y);
                    cexp(&u, &v, exp(1), 0, u, v);

                    //pixmem[ptr++] = checker(u, v);
                    pixmem[ptr++] = hrgb(u, v);
                }
            }
            SDL_UnlockSurface(surface);

            SDL_UpdateWindowSurface(window);
            */

            glClearColor(1.0f, 0.0f, 0.0f, 1.0f);
            glClear(GL_COLOR_BUFFER_BIT);
            glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, NULL);
            SDL_GL_SwapWindow(window);

            redraw = 0;
        }

        while (SDL_PollEvent(&event)) {
            switch(event.type) {
            case SDL_QUIT:
                end = 1;
                break;

            case SDL_KEYDOWN:
                key = event.key.keysym.sym;
                if (key == 27) end = 1;
                break;

            case SDL_MOUSEWHEEL:
                SDL_GetMouseState(&mx, &my);
                tx = (mx - w/2)/zoom;
                ty = (h/2 - my)/zoom;

                zoom += 10*event.wheel.y;
                if (zoom < 1) zoom = 1;

                x0 += tx - (mx - w/2)/zoom;
                y0 += ty - (h/2 - my)/zoom;

                glUniform1f(glGetUniformLocation(shader_prog, "zoom"), (float)zoom);
                glUniform2f(glGetUniformLocation(shader_prog, "z0"), (float)x0, (float)y0);

                redraw = 1;
                break;

            case SDL_MOUSEBUTTONUP:
                drag = 0;
                rbutton = 0;
                break; 

            case SDL_MOUSEBUTTONDOWN:
                if (event.button.button == SDL_BUTTON_LEFT) {
                    drag = 1;
                    SDL_GetMouseState(&dx, &dy);
                    ox0 = x0;
                    oy0 = y0;

                } else if (event.button.button == SDL_BUTTON_RIGHT) {
                    rbutton = 1;
                    SDL_GetMouseState(&rrx, &rry);

                    glUniform2f(glGetUniformLocation(shader_prog, "r"), (float)rx, (float)ry);
                    redraw = 1;
                }

                break; 

            case SDL_MOUSEMOTION:
                if (drag) {
                    SDL_GetMouseState(&mx, &my);
                    x0 = ox0 + (dx - mx)/zoom;
                    y0 = oy0 + (my - dy)/zoom;
                    glUniform2f(glGetUniformLocation(shader_prog, "z0"), (float)x0, (float)y0);
                    redraw = 1;
                }

                if (rbutton) {
                    SDL_GetMouseState(&mx, &my);
                    //rx = (mx - w/2)/zoom + x0;
                    //ry = (h/2 - my)/zoom + y0;

                    if (mx > rrx) {
                        rx += 0.005;

                    } else if (mx < rrx) {
                        rx -= 0.005;
                    }
                    rrx = mx;

                    if (my > rry) {
                        ry += 0.005;

                    } else if (my < rry) {
                        ry -= 0.005;
                    }
                    rry = my;
                    glUniform2f(glGetUniformLocation(shader_prog, "r"), (float)rx, (float)ry);
                    redraw = 1;
                }


                //SDL_GetMouseState(&mx, &my);
                //glUniform2f(glGetUniformLocation(shader_prog, "mouse"), (float)mx, (float)(h - my) );
                redraw = 1;

                break; 

            case SDL_WINDOWEVENT:
                SDL_UpdateWindowSurface(window);
                SDL_GL_SwapWindow(window);
                if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
                    w = event.window.data1;
                    h = event.window.data2;
                    //surface = SDL_GetWindowSurface(window);

                    glUniform2f(glGetUniformLocation(shader_prog, "resolution"), (float)w, (float)h);
                    glViewport(0, 0, w, h);
                    redraw = 1;
                }
                break;
            }
        }
    }

    //clean
    glUseProgram(0);
    glDisableVertexAttribArray(0);
    glDetachShader(shader_prog, vert_shader);
    glDetachShader(shader_prog, frag_shader);
    glDeleteProgram(shader_prog);
    glDeleteShader(vert_shader);
    glDeleteShader(frag_shader);
    glDeleteBuffers(1, &ebo);
    glDeleteBuffers(1, &vbo);
    glDeleteVertexArrays(1, &vao);
    SDL_GL_DeleteContext(context);
    SDL_DestroyWindow(window);
    SDL_Quit();

    return 0;
}