/*

++++ fdraw +++

reinhard@finalmedia.de
Mi 13. Mai 22:16:19 CEST 2026

fdraw ist ein
ein minimalistisches tool wie fbdraw.
es zeichnet jedoch nicht in das linux
frambuffer device, sondern in einen virtuellen
rgba videostream auf stdout.

dieser ist dann z.B. in ffmpeg an beliebige endgeräte
streambar. auch ein transcodieren, spiegeln und duplizieren
wird damit möglich. du kannst den stream auch durch ssh
remote verteilen, dort encodieren etc. alles in der pipe.

auf stdin werden die gleichen zeichenbefehle erwartet,
wie sie auch bei fbdraw spezifiziert sind.

die folgende enviromentvariablen werden ausgelesen.

	export SCREEN_WIDTH=640
	export SCREEN_HEIGHT=480
	export SCREEN_FPS=30

und dabei dauerhaft ein stream mit angegebener fps
auf stdout erzeugt. schwarzer frame. wenn auf stdin
dann befehle übergeben werden, werden diese
gezeichnet.

Folgende Befehle existieren:

cmd     argumente       name    description
-----------------------------------------------------------------------------------
c       r g b           color   Setzt die Zeichenfarbe nach R G B
m       x y             move    Bewegt den Zeichencursor zu gewünschten Koordinaten
r       dx dy           rect    Zeichnet ein Rechteck
p       x y r g b       pixel   Zeichnet exakt einen Pixel an x y in Farbe r g b
s       n               sleep   Wartet angegebene Anzahl an Microsekunden
o       ox oy           offset  Definiert globalen Offset für Zeichenoperationen

Befehle und Parameter/Argumente werden durch ein Leerzeichen getrennt.
Alle Argumente MÜSSEN postive Ganzzahlen (integer) sein.

Beispiele:

c 140 20 0              Wechselt zur Farbe rot
m 20 80                 Wechselt zu den Koordinaten x=20 und y=80
r 10 10                 Zeichnet ein Quadrat 10x10 px
r 10 40                 Zeichnet ein Rechteck 10px breit und 40px hoch
s 50000                 Wartet 50 Millisekunden (für Animationen)
p 23 42 164 163 255     Zeichnet einen hellblauen Pixel an x=23 y=42
o 50 100                Definiert einen globalen Offset von x=50 y=100


Beispielnutzung:

        export SCREEN_WIDTH=640
        export SCREEN_HEIGHT=480
        export SCREEN_FPS=1
        cat examples/demo.txt | ./fdraw | ffplay -f rawvideo -pixel_format rgba -video_size 640x480 -framerate 1 -i -

Compilieren mittels:

	musl-gcc -O2 -static-pie -fPIE -fstack-protector-strong \
	-D_FORTIFY_SOURCE=2 -Wl,-z,relro -Wl,-z,now \
	fdraw.c -o fdraw -lpthread

*/

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>

//#define RGB32(r, g, b) ((255) | ((b) << 8) | ((g) << 16) | ((r) << 24))
//#define RGB32(r, g, b) ((255) | ((r) << 8) | ((g) << 16) | ((b) << 24))
#define RGB32(r, g, b) (((uint32_t)(255) << 24) | ((uint32_t)(b) << 16) | ((uint32_t)(g) << 8) | (uint32_t)(r))

// Globale Variablen für Thread-Zugriff
uint32_t *draw_buffer = NULL;
uint32_t *send_buffer = NULL;
size_t buffer_size_bytes = 0;
int width = 1920;
int height = 1080;
useconds_t frame_delay_us = 33333; // Standard für 30 FPS

pthread_mutex_t buffer_mutex = PTHREAD_MUTEX_INITIALIZER;

// Thread für die konstante FPS Ausgabe
void* video_output_thread(void* arg) {
    (void)arg; // Unbenutzt

    while (1) {
        // Sicheres Kopieren des Zeichenpuffers (Double Buffering)
        pthread_mutex_lock(&buffer_mutex);
        memcpy(send_buffer, draw_buffer, buffer_size_bytes);
        pthread_mutex_unlock(&buffer_mutex);

        // Rohdaten direkt in stdout schreiben
        size_t written = fwrite(send_buffer, 1, buffer_size_bytes, stdout);
        if (written < buffer_size_bytes) {
            // Falls die nachfolgende Pipe geschlossen wurde, Thread beenden
            break;
        }
        fflush(stdout);

        // Dynamisch berechnete Mikrosekunden warten
        usleep(frame_delay_us);
    }
    return NULL;
}

int main() {

    // Standard-Umgebungsvariablen auslesen oder Fallbacks setzen
    if (getenv("SCREEN_WIDTH")) width = atoi(getenv("SCREEN_WIDTH"));
    if (getenv("SCREEN_HEIGHT")) height = atoi(getenv("SCREEN_HEIGHT"));

    // FPS aus Umgebungsvariable auslesen (Standard: 30)
    int fps = 30;
    if (getenv("SCREEN_FPS")) {
        fps = atoi(getenv("SCREEN_FPS"));
        if (fps < 1) fps = 1;
        if (fps > 300) fps = 300;
    }

    // Berechne Mikrosekunden pro Frame (1.000.000 / FPS)
    frame_delay_us = (useconds_t)(1000000 / fps);

    // Protokollierung auf stderr ausgeben, um stdout für Video frei zu halten
    fprintf(stderr, "[Info] Starte Stream mit %dx%d bei %d FPS (Intervall: %u us)\n",
            width, height, fps, frame_delay_us);

    buffer_size_bytes = (size_t)width * height * 4; // 4 Bytes pro Pixel (RGBA)

    // Speicher allozieren
    draw_buffer = calloc(width * height, 4);
    send_buffer = malloc(buffer_size_bytes);
    if (!draw_buffer || !send_buffer) {
        fprintf(stderr, "Fehler: Speicher konnte nicht alloziert werden.\n");
        return EXIT_FAILURE;
    }

    // Video-Ausgabe-Thread starten
    pthread_t thread_id;
    if (pthread_create(&thread_id, NULL, video_output_thread, NULL) != 0) {
        fprintf(stderr, "Fehler beim Erstellen des Video-Threads.\n");
        free(draw_buffer);
        free(send_buffer);
        return EXIT_FAILURE;
    }

    char cmd[66]; // Platz für 64 Zeichen + Newline + Nullterminator
    int arg1, arg2, arg3, arg4, arg5;
    int x = 0, y = 0;
    int ox = 0, oy = 0;
    int r = 255, g = 255, b = 255;

    // Hauptschleife verarbeitet Befehle von stdin
    while (fgets(cmd, sizeof(cmd), stdin) != NULL) {
        fputs(cmd, stderr); // Klonen an stderr

        if (sscanf(cmd, "s %d", &arg1) == 1) {
            usleep(arg1);
        }
        else if (sscanf(cmd, "c %d %d %d", &arg1, &arg2, &arg3) == 3) {
            r = arg1; g = arg2; b = arg3;
        }
        else if (sscanf(cmd, "m %d %d", &arg1, &arg2) == 2) {
            x = arg1; y = arg2;
        }
        else if (sscanf(cmd, "o %d %d", &arg1, &arg2) == 2) {
            ox = arg1; oy = arg2;
        }
        else if (sscanf(cmd, "p %d %d %d %d %d", &arg1, &arg2, &arg3, &arg4, &arg5) == 5) {
            x = arg1; y = arg2;
            r = arg3; g = arg4; b = arg5;
            int mx = ox + x;
            int my = oy + y;
            if (mx >= 0 && mx < width && my >= 0 && my < height) {
                pthread_mutex_lock(&buffer_mutex);
                draw_buffer[my * width + mx] = RGB32(r, g, b);
                pthread_mutex_unlock(&buffer_mutex);
            }
        }
        else if (sscanf(cmd, "r %d %d", &arg1, &arg2) == 2) {
            pthread_mutex_lock(&buffer_mutex);
            for (int dx = 0; dx < arg1; dx++) {
                for (int dy = 0; dy < arg2; dy++) {
                    int mx = ox + x + dx;
                    int my = oy + y + dy;
                    if (mx >= 0 && mx < width && my >= 0 && my < height) {
                       draw_buffer[my * width + mx] = RGB32(r, g, b);
                    }
                }
            }
            pthread_mutex_unlock(&buffer_mutex);
        }
    }

    pthread_cancel(thread_id);
    pthread_join(thread_id, NULL);
    free(draw_buffer);
    free(send_buffer);
    return EXIT_SUCCESS;
}


