/*
  reinhard@finalmedia.de
  So 31. Mai 20:51:20 CEST 2026
  Public Domain

  fvoxel liest von stdin und schreibt nach stdout
  erwartet fvoxel befehlszeilen auf stdin
  schreibt fdraw befehlszeilen auf stdout

  - color cycling Support

  musl-gcc -Q -O3 -static-pie -fPIE -fstack-protector-strong -D_FORTIFY_SOURCE=2 -Wl,-z,relro -Wl,-z,now -o fvoxel -lm fvoxel.c
  strip fvoxel

*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdint.h>
#include <sys/time.h>

#define MAX_CYCLING_COLORS 16
#define MAX_PALETTE_SIZE   256

// Definition eines einzelnen Kartenpunktes
typedef struct {
    uint8_t h, r, g, b;
} Voxel;

// Struktur für den internen Bild-Buffer (Backbuffer)
typedef struct {
    uint8_t r, g, b;
} RGB;

// Struktur für eine Color-Cycling-Regel
typedef struct {
    uint8_t active;
    uint32_t delay_ms;
    uint64_t last_update;
    int current_idx;
    int max_idx;
    uint8_t orig_r, orig_g, orig_b;
    RGB palette[MAX_PALETTE_SIZE];
    uint8_t palette_defined[MAX_PALETTE_SIZE];
} ColorCycle;


int sw = 320;
int sh = 240;
int map_size = 1024;
int distance = 500;
int base_horizon = 60;
int scale_h = 120;

double px = 512.0;
double py = 512.0;
double cam_h = 100.0;
int deg_rot = 0;
int deg_pitch = 180;
int raw_zoom = 150;

Voxel *worldmap = NULL;
int *ybuffer = NULL;
RGB *screen_buffer = NULL;

ColorCycle cycle_rules[MAX_CYCLING_COLORS];
int num_cycle_rules = 0;

const double PI = 3.14159265358979323846;

uint64_t get_time_ms() {
    struct timeval tv;
    gettimeofday(&tv, NULL);
    return (uint64_t)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000);
}

void update_color_cycling() {
    uint64_t now = get_time_ms();
    for (int j = 0; j < num_cycle_rules; j++) {
        if (!cycle_rules[j].active || cycle_rules[j].max_idx < 0) continue;
        if (now - cycle_rules[j].last_update >= cycle_rules[j].delay_ms) {
            int next_idx = (cycle_rules[j].current_idx + 1) % (cycle_rules[j].max_idx + 1);
            int safety = 0;
            while (!cycle_rules[j].palette_defined[next_idx] && safety <= MAX_PALETTE_SIZE) {
                next_idx = (next_idx + 1) % (cycle_rules[j].max_idx + 1);
                safety++;
            }
            cycle_rules[j].current_idx = next_idx;
            cycle_rules[j].last_update = now;
        }
    }
}

void render_frame() {
    update_color_cycling();

    for (int i = 0; i < sw; i++) {
        ybuffer[i] = sh;
    }

    for (int i = 0; i < sw * sh; i++) {
        screen_buffer[i].r = 0;
        screen_buffer[i].g = 0;
        screen_buffer[i].b = 0;
    }

    int pitch_delta = deg_pitch - 180;
    int horizon = base_horizon + (int)(pitch_delta * 1.5);

    double phi = deg_rot * (PI / 180.0);
    double sinphi = sin(phi);
    double cosphi = cos(phi);

    double dz = 1.0;
    double z = 1.0;

    double cam_zoom = (double)raw_zoom / 100.0;
    if (cam_zoom <= 0.1) cam_zoom = 1.0;

    while (z < distance) {
        double z_zoomed = z / cam_zoom;

        double pleft_x = (-cosphi * z_zoomed - sinphi * z_zoomed) + px;
        double pleft_y = (-sinphi * z_zoomed + cosphi * z_zoomed) + py;
        double pright_x = (cosphi * z_zoomed - sinphi * z_zoomed) + px;
        double pright_y = (sinphi * z_zoomed + cosphi * z_zoomed) + py;

        double dx = (pright_x - pleft_x) / (double)sw;
        double dy = (pright_y - pleft_y) / (double)sw;

        double rx = pleft_x;
        double ry = pleft_y;
        double inv_z = (double)scale_h / z;

        for (int i = 0; i < sw; i++) {
            long offset = map_size * 1000;
            int map_x = ((int)floor(rx) + offset) % map_size;
            int map_y = ((int)floor(ry) + offset) % map_size;

            Voxel v = worldmap[map_y * map_size + map_x];

            uint8_t final_r = v.r;
            uint8_t final_g = v.g;
            uint8_t final_b = v.b;

            // Farb-Ersetzung prüfen
            for (int j = 0; j < num_cycle_rules; j++) {
                if (cycle_rules[j].active &&
                    v.r == cycle_rules[j].orig_r &&
                    v.g == cycle_rules[j].orig_g &&
                    v.b == cycle_rules[j].orig_b) {

                    int idx = cycle_rules[j].current_idx;
                    if (cycle_rules[j].palette_defined[idx]) {
                        final_r = cycle_rules[j].palette[idx].r;
                        final_g = cycle_rules[j].palette[idx].g;
                        final_b = cycle_rules[j].palette[idx].b;
                    }
                    break;
                }
            }

            int height_on_screen = (int)(horizon + (cam_h - v.h) * inv_z);
            if (height_on_screen < 0) height_on_screen = 0;

            if (height_on_screen < ybuffer[i]) {
                int start_y = height_on_screen;
                int end_y = ybuffer[i];
                if (end_y > sh) end_y = sh;

                for (int y = start_y; y < end_y; y++) {
                    int pixel_idx = y * sw + i;
                    screen_buffer[pixel_idx].r = final_r;
                    screen_buffer[pixel_idx].g = final_g;
                    screen_buffer[pixel_idx].b = final_b;
                }
                ybuffer[i] = height_on_screen;
            }
            rx += dx;
            ry += dy;
        }
        z += dz;
        dz += 0.04;
    }

    // 3. FLUSH-PHASE
    for (int x = 0; x < sw; x++) {
        int y = 0;
        while (y < sh) {
            uint8_t r = screen_buffer[y * sw + x].r;
            uint8_t g = screen_buffer[y * sw + x].g;
            uint8_t b = screen_buffer[y * sw + x].b;

            int start_y = y;
            while (y < sh &&
                   screen_buffer[y * sw + x].r == r &&
                   screen_buffer[y * sw + x].g == g &&
                   screen_buffer[y * sw + x].b == b) {
                y++;
            }
            int h = y - start_y;
            printf("c %d %d %d\nm %d %d\nr 1 %d\n", r, g, b, x, start_y, h);
        }
    }

    printf("s 16666\n");
    fflush(stdout);
}

int main(int argc, char **argv) {
    char *env_sw = getenv("SCREEN_WIDTH");
    char *env_sh = getenv("SCREEN_HEIGHT");
    char *env_ms = getenv("MAP_SIZE");

    if (env_sw) sw = atoi(env_sw);
    if (env_sh) sh = atoi(env_sh);
    if (env_ms) map_size = atoi(env_ms);

    px = map_size / 2.0;
    py = map_size / 2.0;

    worldmap = (Voxel *)calloc(map_size * map_size, sizeof(Voxel));
    ybuffer = (int *)malloc(sw * sizeof(int));
    screen_buffer = (RGB *)malloc(sw * sh * sizeof(RGB));

    for (int i = 0; i < map_size * map_size; i++) {
        worldmap[i].h = 0; worldmap[i].r = 100; worldmap[i].g = 100; worldmap[i].b = 100;
    }

    // Struktur-Reset
    for (int i = 0; i < MAX_CYCLING_COLORS; i++) {
        cycle_rules[i].active = 0;
        cycle_rules[i].max_idx = -1;
        memset(cycle_rules[i].palette_defined, 0, MAX_PALETTE_SIZE);
    }

    fprintf(stderr, "Engine aktiv. Erwarte befehle auf stdin...\n");

    char line[256];
    while (fgets(line, sizeof(line), stdin)) {
        char cmd[16];
        if (sscanf(line, "%15s", cmd) != 1) continue;

        if (strcmp(cmd, "M") == 0) {
            int x, y, h, r, g, b;
            if (sscanf(line, "%*s %d %d %d %d %d %d", &x, &y, &h, &r, &g, &b) == 6) {
                if (x >= 0 && x < map_size && y >= 0 && y < map_size) {
                    int idx = y * map_size + x;
                    worldmap[idx].h = h;
                    worldmap[idx].r = r;
                    worldmap[idx].g = g;
                    worldmap[idx].b = b;
                }
            }
        }
        else if (strcmp(cmd, "P") == 0) {
            int new_px, new_py, new_cam_h;
            int new_rot, new_pitch, new_zoom;
            int parsed = sscanf(line, "%*s %d %d %d %d %d %d", &new_px, &new_py, &new_cam_h, &new_rot, &new_pitch, &new_zoom);

            if (parsed >= 4) {
                px = (double)new_px; py = (double)new_py; cam_h = (double)new_cam_h; deg_rot = new_rot;
                if (parsed >= 5) deg_pitch = new_pitch; else deg_pitch = 180;
                if (parsed == 6) raw_zoom = new_zoom; else raw_zoom = 150;
                render_frame();
            }
        }
        else if (strcmp(cmd, "C") == 0) {
            int or, og, ob, t, idx, ir, ig, ib;
            int parsed = sscanf(line, "%*s %d %d %d %d %d %d %d %d", &or, &og, &ob, &t, &idx, &ir, &ig, &ib);

            if (parsed == 8 && idx >= 0 && idx < MAX_PALETTE_SIZE) {
                int rule_idx = num_cycle_rules;

                // Prüfen, ob für diese Originalfarbe bereits ein Eintrag existiert
                for(int j = 0; j < num_cycle_rules; j++) {
                    if (cycle_rules[j].orig_r == or && cycle_rules[j].orig_g == og && cycle_rules[j].orig_b == ob) {
                        rule_idx = j;
                        break;
                    }
                }

                // Werte abspeichern
                cycle_rules[rule_idx].orig_r = or;
                cycle_rules[rule_idx].orig_g = og;
                cycle_rules[rule_idx].orig_b = ob;
                cycle_rules[rule_idx].delay_ms = t;

                // Farbe im Paletten-Index registrieren
                cycle_rules[rule_idx].palette[idx].r = ir;
                cycle_rules[rule_idx].palette[idx].g = ig;
                cycle_rules[rule_idx].palette[idx].b = ib;
                cycle_rules[rule_idx].palette_defined[idx] = 1;

                // Höchsten Index dynamisch mitskalieren
                if (idx > cycle_rules[rule_idx].max_idx) {
                    cycle_rules[rule_idx].max_idx = idx;
                }

                if (!cycle_rules[rule_idx].active) {
		   cycle_rules[rule_idx].last_update = get_time_ms();
		   cycle_rules[rule_idx].current_idx = 0;
		   cycle_rules[rule_idx].active = 1;
		}
		if (rule_idx == num_cycle_rules && num_cycle_rules < MAX_CYCLING_COLORS) {
		  num_cycle_rules++;
		}
                }
}
}
free(worldmap);
free(ybuffer);
free(screen_buffer);
return 0;
}

