/*
 * color_printf.h
 *
 * printf() cagrilarindaki her degeri (mesaj metnini degil) kendi
 * "kimligine" gore renklendirir:
 *
 *   - Ayni degisken (ornegin j) her printf'te ayni renkte basilir.
 *   - &j gibi adres alma ifadeleri, j ile AYNI renk tonunu (hue) kullanir
 *     ama "dim" (soluk) yapilir; boylece "j'nin degeri" ile "j'nin adresi"
 *     birbiriyle iliskili ama ayirt edilebilir gorunur.
 *   - Her farkli degisken/ifade icin paletten yeni bir renk atanir, 16
 *     farkli tondan sonra tekrar dolasmaya baslar.
 *
 * Kullanim:
 *   gcc -g -include color_printf.h main.c -o main && ./main
 *
 * NOT: Bu bir preprocessor + calisma-zamani hile'sidir; C tip sistemini
 * gercekten anlamaz, printf argumanlarinin KAYNAK METNINI (# ile
 * stringize edilmis halini) okuyarak degisken ismini tahmin eder.
 * Yani "arr[i]", "s->field", "(int)x" gibi ifadelerde ilk identifier'i
 * (arr, s, x) baz alir. %n desteklenmez. Genis/wchar specifier'lar
 * (%ls, %lc) desteklenmez. GCC/Clang'a ozgu ##__VA_ARGS__ genisletmesini
 * kullanir.
 */
#ifndef COLOR_PRINTF_H
#define COLOR_PRINTF_H
 
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>
 
#ifdef __cplusplus
extern "C" {
#endif
 
/* printf'i makro ile yakala: format metni + argumanlarin kaynak-kodu
 * (stringize edilmis) + gercek argumanlar birlikte gonderilir. */
#ifdef printf
#undef printf
#endif
#define printf(fmt, ...) \
    __color_printf(fmt, "" #__VA_ARGS__, ##__VA_ARGS__)
 
int __color_printf(const char *fmt, const char *argtext, ...);
 
/* ------------------------------------------------------------------ */
/* Dahili uygulama                                                     */
/* ------------------------------------------------------------------ */
 
#define __CP_MAX_NAMES 64
#define __CP_NAME_LEN  48
 
static char __cp_names[__CP_MAX_NAMES][__CP_NAME_LEN];
static int  __cp_color_idx[__CP_MAX_NAMES];
static int  __cp_name_count = 0;
static int  __cp_next_color = 0;
 
/* 16 birbirinden belirgin xterm-256 renk kodu */
static const int __cp_palette[] = {
    203, 208, 220, 190, 118, 85, 80, 45,
    75,  111, 141, 177, 213, 205, 174, 150
};
#define __CP_PALETTE_SIZE (int)(sizeof(__cp_palette) / sizeof(__cp_palette[0]))
 
/* base isim icin renk indeksi bul/olustur */
static int __cp_color_for(const char *name) {
    for (int i = 0; i < __cp_name_count; i++) {
        if (strcmp(__cp_names[i], name) == 0)
            return __cp_color_idx[i];
    }
    int idx = __cp_next_color % __CP_PALETTE_SIZE;
    __cp_next_color++;
    if (__cp_name_count < __CP_MAX_NAMES) {
        strncpy(__cp_names[__cp_name_count], name, __CP_NAME_LEN - 1);
        __cp_names[__cp_name_count][__CP_NAME_LEN - 1] = '\0';
        __cp_color_idx[__cp_name_count] = idx;
        __cp_name_count++;
    }
    return idx;
}
 
/* argtext icindeki N'inci top-level virgul ile ayrilmis token'i
 * out'a kopyalar (parantez derinligini takip eder). basari -> 1 */
static int __cp_nth_token(const char *argtext, int n, char *out, size_t outsz) {
    int depth = 0;
    int cur = 0;
    const char *tok_start = argtext;
    const char *p = argtext;
 
    if (argtext == NULL || argtext[0] == '\0') return 0;
 
    for (;; p++) {
        char c = *p;
        int is_end = (c == '\0');
        if (c == '(' || c == '[') depth++;
        else if (c == ')' || c == ']') depth--;
 
        if ((c == ',' && depth == 0) || is_end) {
            if (cur == n) {
                size_t len = (size_t)(p - tok_start);
                if (len >= outsz) len = outsz - 1;
                /* basta/sonda bosluk kirp */
                while (len > 0 && isspace((unsigned char)tok_start[0])) { tok_start++; len--; }
                while (len > 0 && isspace((unsigned char)tok_start[len - 1])) len--;
                memcpy(out, tok_start, len);
                out[len] = '\0';
                return 1;
            }
            cur++;
            tok_start = p + 1;
            if (is_end) break;
        }
    }
    return 0;
}
 
/* token'i temizler: onde parantezli cast'leri ((void *), (int) vb.)
 * ve tek bir '&' ya da '*' onekini soyar. base_name = ilk identifier.
 * is_addr = '&' onekiyle basladiysa ya da tur olarak adres gibi
 * kullanildiysa 1 doner. */
static void __cp_clean(const char *tok, char *base_name, size_t bn_sz, int *is_addr) {
    *is_addr = 0;
    const char *p = tok;
    while (isspace((unsigned char)*p)) p++;
 
    /* onde bir ya da daha fazla "(...)" cast varsa atla */
    while (*p == '(') {
        int depth = 1;
        const char *q = p + 1;
        while (*q && depth > 0) {
            if (*q == '(') depth++;
            else if (*q == ')') depth--;
            q++;
        }
        p = q;
        while (isspace((unsigned char)*p)) p++;
    }
 
    if (*p == '&') { *is_addr = 1; p++; }
    else if (*p == '*') { p++; }
    while (isspace((unsigned char)*p)) p++;
 
    size_t i = 0;
    while (p[i] && (isalnum((unsigned char)p[i]) || p[i] == '_') && i < bn_sz - 1) {
        base_name[i] = p[i];
        i++;
    }
    base_name[i] = '\0';
    if (i == 0) {
        /* identifier bulunamadi (literal, ifade vb.) -> tum token'i
         * anahtar olarak kullan ki en azindan tutarli olsun */
        strncpy(base_name, tok, bn_sz - 1);
        base_name[bn_sz - 1] = '\0';
    }
}
 
/* '%'den baslayan spec'i cikarir, conv karakterini ve uzunluk
 * belirtecini raporlar. donen deger spec'ten SONRAKI karaktere isaret eder. */
static const char *__cp_parse_spec(const char *p, char *spec_out, size_t spec_sz,
                                    char *conv_out, char *len_out) {
    const char *start = p;
    p++; /* '%' */
    while (*p && strchr("-+ 0#", *p)) p++;           /* flags */
    while (*p && isdigit((unsigned char)*p)) p++;    /* width */
    if (*p == '*') p++;
    if (*p == '.') {
        p++;
        while (*p && isdigit((unsigned char)*p)) p++; /* precision */
        if (*p == '*') p++;
    }
    len_out[0] = '\0';
    int li = 0;
    while (*p && strchr("hlLzjt", *p) && li < 3) { len_out[li++] = *p; p++; }
    len_out[li] = '\0';
 
    if (*p) {
        *conv_out = *p;
        p++;
    } else {
        *conv_out = '\0';
    }
    size_t len = (size_t)(p - start);
    if (len >= spec_sz) len = spec_sz - 1;
    memcpy(spec_out, start, len);
    spec_out[len] = '\0';
    return p;
}
 
int __color_printf(const char *fmt, const char *argtext, ...) {
    va_list ap;
    va_start(ap, argtext);
 
    int arg_index = 0;
    const char *p = fmt;
    int total = 0;
 
    while (*p) {
        if (*p != '%') {
            fputc(*p, stdout);
            p++;
            total++;
            continue;
        }
        if (*(p + 1) == '%') {
            fputc('%', stdout);
            p += 2;
            total++;
            continue;
        }
 
        char spec[32];
        char conv = 0;
        char lenmod[4];
        p = __cp_parse_spec(p, spec, sizeof spec, &conv, lenmod);
        if (conv == '\0') break; /* bozuk spec, dur */
 
        char token[256] = {0};
        char base_name[__CP_NAME_LEN] = {0};
        int is_addr = 0;
        if (__cp_nth_token(argtext, arg_index, token, sizeof token)) {
            __cp_clean(token, base_name, sizeof base_name, &is_addr);
        } else {
            snprintf(base_name, sizeof base_name, "?%d", arg_index);
        }
        if (conv == 'p') is_addr = 1; /* %p her zaman adres gibi davran */
 
        char valbuf[512];
        valbuf[0] = '\0';
 
        switch (conv) {
            case 'd': case 'i': {
                if (strcmp(lenmod, "ll") == 0) {
                    long long v = va_arg(ap, long long);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else if (strcmp(lenmod, "l") == 0) {
                    long v = va_arg(ap, long);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else if (strcmp(lenmod, "z") == 0 || strcmp(lenmod, "j") == 0 || strcmp(lenmod, "t") == 0) {
                    long v = va_arg(ap, long);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else {
                    int v = va_arg(ap, int);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                }
                break;
            }
            case 'u': case 'o': case 'x': case 'X': {
                if (strcmp(lenmod, "ll") == 0) {
                    unsigned long long v = va_arg(ap, unsigned long long);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else if (strcmp(lenmod, "l") == 0) {
                    unsigned long v = va_arg(ap, unsigned long);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else if (strcmp(lenmod, "z") == 0 || strcmp(lenmod, "j") == 0 || strcmp(lenmod, "t") == 0) {
                    unsigned long v = va_arg(ap, unsigned long);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else {
                    unsigned int v = va_arg(ap, unsigned int);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                }
                break;
            }
            case 'c': {
                int v = va_arg(ap, int);
                snprintf(valbuf, sizeof valbuf, spec, v);
                break;
            }
            case 's': {
                char *v = va_arg(ap, char *);
                int needed = snprintf(NULL, 0, spec, v);
                if (needed > 0 && (size_t)needed < sizeof valbuf) {
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else if (needed >= (int)sizeof valbuf) {
                    char *tmp = (char *)malloc((size_t)needed + 1);
                    if (tmp) {
                        snprintf(tmp, (size_t)needed + 1, spec, v);
                        strncpy(valbuf, tmp, sizeof valbuf - 1);
                        valbuf[sizeof valbuf - 1] = '\0';
                        free(tmp);
                    }
                }
                break;
            }
            case 'p': {
                void *v = va_arg(ap, void *);
                snprintf(valbuf, sizeof valbuf, spec, v);
                break;
            }
            case 'f': case 'F': case 'e': case 'E': case 'g': case 'G': case 'a': case 'A': {
                if (strcmp(lenmod, "L") == 0) {
                    long double v = va_arg(ap, long double);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                } else {
                    double v = va_arg(ap, double);
                    snprintf(valbuf, sizeof valbuf, spec, v);
                }
                break;
            }
            default: {
                /* bilinmeyen conv: elimizden geldigince spec'i oldugu
                 * gibi yazdir, argument tuketmeye calisma */
                snprintf(valbuf, sizeof valbuf, "%s", spec);
                break;
            }
        }
 
        int color = __cp_color_for(base_name);
        int code = __cp_palette[color];
        if (is_addr) {
            /* ayni ton, soluk (dim) */
            fprintf(stdout, "\x1b[2;38;5;%dm%s\x1b[0m", code, valbuf);
        } else {
            fprintf(stdout, "\x1b[1;38;5;%dm%s\x1b[0m", code, valbuf);
        }
        total += (int)strlen(valbuf);
        arg_index++;
    }
 
    va_end(ap);
    return total;
}
 
#ifdef __cplusplus
}
#endif
 
#endif /* COLOR_PRINTF_H */
 
# compile.fish
function compile --description 'gcc -include color_printf.h ile derle ve calistir'
    if test (count $argv) -lt 1
        echo "kullanim: compile <dosya.c> [ekstra gcc bayraklari...]"
        return 1
    end
 
    set -l src $argv[1]
    set -l extra_args $argv[2..-1]
 
    if not test -f $src
        echo "compile: '$src' bulunamadi"
        return 1
    end
 
    # color_printf.h nerede? once ayni klasor, sonra $COLOR_PRINTF_H, sonra ~/.local/include
    set -l src_dir (path dirname $src)
    set -l header ""
    if test -f $src_dir/color_printf.h
        set header $src_dir/color_printf.h
    else if set -q COLOR_PRINTF_H; and test -f $COLOR_PRINTF_H
        set header $COLOR_PRINTF_H
    else if test -f ~/.local/include/color_printf.h
        set header ~/.local/include/color_printf.h
    else
        echo "compile: color_printf.h bulunamadi (ne $src_dir/, ne \$COLOR_PRINTF_H, ne ~/.local/include/)"
        return 1
    end
 
    set -l out (path change-extension '' (path basename $src))
    if test -z "$out"
        set out a.out
    end
    set -l out_path $src_dir/$out
 
    echo "gcc -g -include $header $src -o $out_path $extra_args"
    gcc -g -include $header $src -o $out_path $extra_args
    or return 1
 
    $out_path
end