#include <stdio.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <math.h>
#include <stdlib.h>
#include <time.h>

#define BLOBS 4

typedef struct {
    double x, y;
    double vx, vy;
    double phase;
} Blob;

static void termsz(int *w, int *h) {
    struct winsize ws;
    ioctl(1, TIOCGWINSZ, &ws);
    *w = ws.ws_col;
    *h = ws.ws_row;
}

static void hsv2rgb(double h, double s, double v, int *r, int *g, int *b) {
    double c = v * s;
    double x = c * (1 - fabs(fmod(h / 60.0, 2) - 1));
    double m = v - c;
    double rr=0, gg=0, bb=0;
    if (h < 60) { rr=c; gg=x; bb=0; }
    else if (h < 120) { rr=x; gg=c; bb=0; }
    else if (h < 180) { rr=0; gg=c; bb=x; }
    else if (h < 240) { rr=0; gg=x; bb=c; }
    else if (h < 300) { rr=x; gg=0; bb=c; }
    else { rr=c; gg=0; bb=x; }
    *r = (int)((rr+m)*255);
    *g = (int)((gg+m)*255);
    *b = (int)((bb+m)*255);
}

int main() {
    srand(time(NULL));
    int w=80, h=24;
    double t=0;

    Blob blobs[BLOBS];
    for(int i=0;i<BLOBS;i++){
        blobs[i].x = ((rand()/(double)RAND_MAX)-0.5)*1.2;
        blobs[i].y = ((rand()/(double)RAND_MAX)-0.5)*1.2;
        blobs[i].vx = 0;
        blobs[i].vy = 0;
        blobs[i].phase = ((rand()/(double)RAND_MAX)*2-1)*M_PI;
    }

    printf("\x1b[?1049h"); // alternate screen
    printf("\x1b[?25l");   // hide cursor

    for(;;){
        termsz(&w,&h);
        printf("\x1b[H");

        for(int i=0;i<BLOBS;i++){
            blobs[i].vx += ((rand()/(double)RAND_MAX)-0.5)*0.001;
            blobs[i].vy += ((rand()/(double)RAND_MAX)-0.5)*0.001;
            blobs[i].vx *= 0.9998;
            blobs[i].vy *= 0.9998;

            blobs[i].x += blobs[i].vx + sin(t*0.03 + i*0.2)*0.007;
            blobs[i].y += blobs[i].vy + cos(t*0.03 + i*0.2)*0.007;

            if(blobs[i].x>0.7||blobs[i].x<-0.7) blobs[i].vx*=-1;
            if(blobs[i].y>0.7||blobs[i].y<-0.7) blobs[i].vy*=-1;
        }

        for(int y=0;y<h;y++){
            for(int x=0;x<w;x++){
                double nx = (double)x/w - 0.5;
                double ny = (double)y/h - 0.5;
                double v = 0;

                for(int i=0;i<BLOBS;i++){
                    double dx = nx - blobs[i].x;
                    double dy = ny - blobs[i].y;
                    double r = sqrt(dx*dx + dy*dy);
                    double size = 18 + 2*sin(t*0.01 + i);
                    v += 0.5 * sin(r*size - t*0.5 + blobs[i].phase);
                }

                double val = (v + BLOBS*0.5)/BLOBS;
                val = pow(val, 1.3); // increase dark areas
                if(val<0) val=0;
                if(val>1) val=1;

                double phase = atan2(sin(v), cos(v));
                double hue = (phase + M_PI)/(2*M_PI)*360.0;

                int r,g,bv;
                hsv2rgb(hue,1.0,val,&r,&g,&bv);

                const char *ramp = " .:-=+*#%@";
                int idx = (int)(val*9);
                if(idx<0) idx=0;
                if(idx>9) idx=9;

                // bold if green-dominant
                if(g > r && g > bv) {
                    printf("\x1b[1;38;2;%d;%d;%dm%c", r,g,bv, ramp[idx]);
                } else {
                    printf("\x1b[38;2;%d;%d;%dm%c", r,g,bv, ramp[idx]);
                }
            }
            printf("\x1b[0m\n");
        }

        fflush(stdout);
        t += 0.015;
        usleep(30000);
    }

    printf("\x1b[?1049l"); // back to normal buffer
    printf("\x1b[?25h");   // show cursor
}

