module main;

import core.stdc.stdio;
import core.stdc.stdlib;
import core.stdc.locale;
import core.stdc.signal;
import core.stdc.string;
import core.stdc.stdlib : malloc;
import std.string : toStringz;
import std.conv : to;

import st;
import x;
import config;
import patches;
import arg;

// Import X11 Display type
import deimos.X11.Xlib : Display;

// Xresources types
alias XrmDatabase = void*;
struct XrmValue {
    uint size;
    char* addr;
}

enum ResourceType {
    STRING = 0,
    INTEGER = 1,
    FLOAT = 2
}

struct ResourcePref {
    const(char)* name;
    ResourceType type;
    void* dst;
}

enum VERSION = "0.9.2";

// Global variables
__gshared {
    string opt_alpha;
    string opt_class = "St";
    string opt_dir;
    string opt_font = "";
    string opt_io;
    string opt_line;
    string opt_name = "st";
    string opt_title;
    string opt_embed;
    string[] opt_cmd;
    int cols = 80;
    int rows = 24;
    string usedfont;
}

void usage() {
    string msg = "usage: " ~ argv0 ~ " [-aiv] [-c class]";
    
    static if (isPatchEnabled!"WORKINGDIR_PATCH") {
        msg ~= " [-d path]";
    }
    msg ~= " [-f font] [-g geometry] [-n name] [-o iofile] [-T title] [-t title] [-w windowid]";
    static if (isPatchEnabled!"ALPHA_PATCH") {
        msg ~= " [-A alpha]";
    }
    msg ~= " [[-e] command [args ...]]\n";
    msg ~= "       " ~ argv0 ~ " [-aiv] [-c class]";
    static if (isPatchEnabled!"WORKINGDIR_PATCH") {
        msg ~= " [-d path]";
    }
    msg ~= " [-f font] [-g geometry] [-n name] [-o iofile] [-T title] [-t title] [-w windowid]";
    static if (isPatchEnabled!"ALPHA_PATCH") {
        msg ~= " [-A alpha]";
    }
    msg ~= " -l line [stty_args ...]\n";
    
    die(msg.toStringz);
}

void handleArg(char opt, string delegate() getArg, string delegate(lazy void) requireArg) {
    switch (opt) {
    case 'a':
        config.allowaltscreen = 0;
        break;
    static if (isPatchEnabled!"ALPHA_PATCH") {
    case 'A':
        opt_alpha = requireArg(usage());
        break;
    }
    case 'c':
        opt_class = requireArg(usage());
        break;
    static if (isPatchEnabled!"WORKINGDIR_PATCH") {
    case 'd':
        opt_dir = requireArg(usage());
        break;
    }
    case 'f':
        opt_font = requireArg(usage());
        break;
    case 'g':
        auto geom = requireArg(usage());
        parseGeometry(geom);
        break;
    case 'i':
        xw.isfixed = 1;
        break;
    case 'o':
        opt_io = requireArg(usage());
        break;
    case 'l':
        opt_line = requireArg(usage());
        break;
    case 'n':
        opt_name = requireArg(usage());
        break;
    case 't':
    case 'T':
        opt_title = requireArg(usage());
        break;
    case 'w':
        opt_embed = requireArg(usage());
        break;
    case 'v':
        die("%s %s\n", argv0.toStringz, VERSION.toStringz);
        break;
    default:
        usage();
    }
}

void parseGeometry(string geom) {
    import std.regex;
    import std.conv;
    
    // Parse X11 geometry string: WIDTHxHEIGHT+X+Y
    auto re = regex(r"^(\d+)x(\d+)(?:\+(-?\d+)\+(-?\d+))?$");
    auto m = geom.matchFirst(re);
    
    if (!m.empty) {
        if (m[1].length) cols = m[1].to!int;
        if (m[2].length) rows = m[2].to!int;
        if (m[3].length) xw.l = m[3].to!int;
        if (m[4].length) xw.t = m[4].to!int;
        xw.gm = 1;
    }
}

extern(C) void sigusr1_reload(int) {
    static if ((isPatchEnabled!"XRESOURCES_PATCH" && isPatchEnabled!"XRESOURCES_RELOAD_PATCH") ||
               (isPatchEnabled!"BACKGROUND_IMAGE_PATCH" && isPatchEnabled!"BACKGROUND_IMAGE_RELOAD_PATCH")) {
        // Signal handler for reloading configuration
        static if (isPatchEnabled!"XRESOURCES_PATCH" && isPatchEnabled!"XRESOURCES_RELOAD_PATCH") {
            config_init(xw.dpy);
        }
        static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH" && isPatchEnabled!"BACKGROUND_IMAGE_RELOAD_PATCH") {
            import patch.background_image : reload_image;
            reload_image();
        }
        redraw();
    }
}

extern(C) int main(int argc, char** argv) {
    import std.logger : globalLogLevel, LogLevel, FileLogger, sharedLog, trace;
    import std.stdio : stderr;
    
    // Set global log level to trace for debugging
    globalLogLevel = LogLevel.trace;
    
    // Create a FileLogger that outputs to stderr with trace level
    auto stderrLogger = new FileLogger(stderr, LogLevel.trace);
    
    // Set it as the shared logger
    sharedLog = cast(shared)stderrLogger;
    
    // Log application startup
    trace("ST terminal starting up...");
    
    // Initialize shell pointer
    config.shell_ptr = config._shell_array.ptr;
    
    // Parse command line arguments
    string[] args;
    args.length = argc;
    foreach (i; 0..argc) {
        args[i] = to!string(argv[i]);
    }
    
    // Handle -e option specially in handleArg
    bool hasEOption = false;
    
    parseArgs(args, (char opt, string delegate() getArg, string delegate(lazy void) requireArg) {
        if (opt == 'e') {
            hasEOption = true;
            // Skip the -e handling, parseArgs will leave remaining args in args array
            return;
        }
        handleArg(opt, getArg, requireArg);
    });
    
    // After parseArgs, args contains only unparsed arguments
    if (hasEOption || args.length > 0) {
        opt_cmd = args;
    }
    
    // Set default title if not specified
    if (!opt_title) {
        opt_title = (opt_line || opt_cmd.length == 0) ? "st" : opt_cmd[0];
    }
    
    // Set locale
    setlocale(LC_CTYPE, "");
    
    // Set locale modifiers for X11
    setXLocaleModifiers();
    
    // Setup signal handlers
    static if ((isPatchEnabled!"XRESOURCES_PATCH" && isPatchEnabled!"XRESOURCES_RELOAD_PATCH") ||
               (isPatchEnabled!"BACKGROUND_IMAGE_PATCH" && isPatchEnabled!"BACKGROUND_IMAGE_RELOAD_PATCH")) {
        signal(SIGUSR1, &sigusr1_reload);
    }
    
    // Open X display for xresources
    static if (isPatchEnabled!"XRESOURCES_PATCH") {
        import deimos.X11.Xlib : XOpenDisplay;
        xw.dpy = XOpenDisplay(null);
        if (!xw.dpy) {
            die("Can't open display\n");
        }
        config_init(xw.dpy);
    }
    
    // Initialize harfbuzz if needed
    static if (isPatchEnabled!"LIGATURES_PATCH") {
        hbcreatebuffer();
    }
    
    // Ensure minimum size
    cols = max(cols, 1);
    rows = max(rows, 1);
    
    // Set default background for alpha focus
    static if (isPatchEnabled!"ALPHA_PATCH" && isPatchEnabled!"ALPHA_FOCUS_HIGHLIGHT_PATCH") {
        import std.algorithm : max;
        config.defaultbg = max(colorname.length, 256);
    }
    
    // Initialize terminal
    tnew(cols, rows);
    xinit(cols, rows);
    
    // Initialize window position and fixed state
    xw.l = xw.t = 0;
    xw.isfixed = 0;
    
    // Set cursor style
    static if (isPatchEnabled!"BLINKING_CURSOR_PATCH") {
        xsetcursor(cursorstyle);
    } else {
        xsetcursor(cursorshape);
    }
    
    // Start terminal process
    if (ttynew(opt_line ? opt_line.toStringz : null, 
               config.shell_ptr ? config.shell_ptr[0] : null,  // shell
               opt_io ? opt_io.toStringz : null,
               opt_cmd.length > 0 ? toCStringArray(opt_cmd) : null) < 0) {
        die("Failed to start terminal process\n");
    }
    
    // Force initial draw
    redraw();
    
    // Initialize background image if enabled
    static if (isPatchEnabled!"BACKGROUND_IMAGE_PATCH") {
        bginit();
    }
    
    // Set environment variables
    xsetenv();
    
    // Initialize selection
    selinit();
    
    // Change working directory if specified
    static if (isPatchEnabled!"WORKINGDIR_PATCH") {
        if (opt_dir) {
            import core.sys.posix.unistd : chdir;
            if (chdir(opt_dir.toStringz) != 0) {
                die("Can't change to working directory %s\n", opt_dir.toStringz);
            }
        }
    }
    
    // Run the main event loop
    run();
    
    // Cleanup
    static if (isPatchEnabled!"LIGATURES_PATCH") {
        hbdestroybuffer();
    }
    
    return 0;
}

// External X11 function
extern(C) char* XSetLocaleModifiers(const(char)*);

// Set X locale modifiers
void setXLocaleModifiers() {
    XSetLocaleModifiers("".toStringz);
}

// External declarations needed
extern(C) {
    void xsetcursor(int);
    void xinit(int, int);
    void xsetenv();
    void run();
    void bginit();
    void hbcreatebuffer();
    void hbdestroybuffer();
    void redraw();
    int ttynew(const(char)* line, char* cmd, const(char)* outfile, char** args);
    
    // Xresources functions
    void XrmInitialize();
    char* XResourceManagerString(Display* dpy);
    XrmDatabase XrmGetStringDatabase(const(char)* data);
    int XrmGetResource(XrmDatabase db, const(char)* str_name, const(char)* str_class,
                      char** str_type_return, XrmValue* value_return);
    
    // Additional C library functions
    uint strtoul(const(char)* nptr, char** endptr, int base);
    float strtof(const(char)* nptr, char** endptr);
    
    __gshared {
        extern int cursorstyle;
        extern const(char)*[] colorname;
    }
}

// Helper function to convert D string array to C string array
char** toCStringArray(string[] arr) {
    if (arr.length == 0) return null;
    
    char** result = cast(char**)malloc((arr.length + 1) * (char*).sizeof);
    foreach (i, s; arr) {
        result[i] = cast(char*)s.toStringz;
    }
    result[arr.length] = null;
    return result;
}

// Xresources implementation
static if (isPatchEnabled!"XRESOURCES_PATCH") {
    ResourcePref[] get_resources() {
        ResourcePref[] resources;
        
        resources ~= ResourcePref("font", ResourceType.STRING, cast(void*)&config.font);
        
        // Add color resources
        import config : colorname;
        for (int i = 0; i < 16; i++) {
            static char[16][16] color_names;
            snprintf(color_names[i].ptr, 16, "color%d", i);
            resources ~= ResourcePref(color_names[i].ptr, ResourceType.STRING, cast(void*)&colorname[i]);
        }
        
        resources ~= ResourcePref("background", ResourceType.STRING, cast(void*)&colorname[258]);
        resources ~= ResourcePref("foreground", ResourceType.STRING, cast(void*)&colorname[259]);
        resources ~= ResourcePref("cursorColor", ResourceType.STRING, cast(void*)&colorname[256]);
        resources ~= ResourcePref("termname", ResourceType.STRING, cast(void*)&config.termname);
        resources ~= ResourcePref("shell", ResourceType.STRING, cast(void*)&config.shell);
        resources ~= ResourcePref("minlatency", ResourceType.INTEGER, cast(void*)&config.minlatency);
        resources ~= ResourcePref("maxlatency", ResourceType.INTEGER, cast(void*)&config.maxlatency);
        resources ~= ResourcePref("blinktimeout", ResourceType.INTEGER, cast(void*)&config.blinktimeout);
        resources ~= ResourcePref("bellvolume", ResourceType.INTEGER, cast(void*)&config.bellvolume);
        resources ~= ResourcePref("tabspaces", ResourceType.INTEGER, cast(void*)&config.tabspaces);
        resources ~= ResourcePref("borderpx", ResourceType.INTEGER, cast(void*)&config.borderpx);
        
        return resources;
    }
    
    int resource_load(XrmDatabase db, const(char)* name, ResourceType rtype, void* dst) {
        import std.string : fromStringz;
        
        char[256] fullname;
        char[256] fullclass;
        char* type;
        XrmValue ret;
        
        const(char)* opt_name_str = (opt_name.length > 0 ? opt_name : "st").toStringz;
        const(char)* opt_class_str = (opt_class.length > 0 ? opt_class : "St").toStringz;
        
        snprintf(fullname.ptr, fullname.sizeof, "%s.%s", opt_name_str, name);
        snprintf(fullclass.ptr, fullclass.sizeof, "%s.%s", opt_class_str, name);
        fullname[$-1] = fullclass[$-1] = '\0';
        
        if (!XrmGetResource(db, fullname.ptr, fullclass.ptr, &type, &ret))
            return 1;
            
        if (ret.addr is null || strncmp("String", type, 64) != 0)
            return 1;
            
        switch (rtype) {
        case ResourceType.STRING:
            *cast(char**)dst = ret.addr;
            break;
        case ResourceType.INTEGER:
            *cast(int*)dst = cast(int)strtoul(ret.addr, null, 10);
            break;
        case ResourceType.FLOAT:
            *cast(float*)dst = strtof(ret.addr, null);
            break;
        default:
            return 1;
        }
        return 0;
    }
    
    extern(C) void config_init(Display* dpy) {
        char* resm;
        XrmDatabase db;
        
        XrmInitialize();
        resm = XResourceManagerString(dpy);
        if (!resm) {
            return;
        }
        
        db = XrmGetStringDatabase(resm);
        
        auto resources = get_resources();
        foreach (ref resource; resources) {
            resource_load(db, resource.name, resource.type, resource.dst);
        }
    }
} else {
    extern(C) void config_init(Display* dpy) {
        // No-op when xresources is disabled
    }
}