/*
 * OpenComal -- a free Comal implementation
 *
 * This file is part of the OpenComal package.
 * (c) Copyright 1992-2024 Jos Visser <josvisser66@gmail.com>
 *
 * The OpenComal package is covered by the GNU General Public
 * License. See doc/LICENSE for more information.
 */

/* OpenComal SYS routine implementations for Posix operating systems */

#include "pdcglob.h"
#include "pdccmd.h"
#include "pdcdef.h"
#include "pdcexec.h"
#include "pdcext.h"
#include "pdcmisc.h"

#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>
#include <dirent.h>

#include <emscripten.h>

/* @formatter:off */

EM_JS(void, js_alert, (const char *s), {
    alert(UTF8ToString(s));
})

EM_JS(void, js_print, (const char *s), {
    term_print(UTF8ToString(s));
})

EM_JS(int, js_confirm, (char *msg), {
    return confirm(UTF8ToString(msg));
})

EM_JS(char *, js_prompt, (char *msg), {
    return stringToNewUTF8(prompt(UTF8ToString(msg)) + '\n');
})

EM_ASYNC_JS(char *, js_read, (char *line, char *prompt), {
    return stringToNewUTF8(await term_read(UTF8ToString(line), UTF8ToString(prompt)) + '\n');
})

EM_ASYNC_JS(char *, js_key, (int delay), {
    return stringToNewUTF8(await term_inkey(delay));
})

EM_JS(void, js_log, (char *msg), {
    console.log(UTF8ToString(msg));
})

EM_JS(int, js_escape, (), {
    return term_escape();
})

EM_JS(void, js_page, (), {
    term_page();
})

EM_JS(void, js_cursor, (int x, int y), {
    term_cursor(x,y);
})

/* @formatter:on */

PUBLIC void sys_rand(long *result, long *scale) {
    *result = rand();
    *scale = RAND_MAX;
}

PUBLIC void *sys_alloc(long size) {
    if (size > MAXUNSIGNED)
        return NULL;

    return calloc(1, (unsigned) size);
}


PUBLIC void *sys_realloc(void *block, long newsize) {
    if (newsize > MAXUNSIGNED)
        return NULL;

    return realloc(block, (unsigned) newsize);
}


PUBLIC void sys_free(void *p) {
    free(p);
}

PUBLIC char *sys_dir_string() {
    static int buf_size = 1024;
    static char *buf = 0;

    while (1 == 1) {
        if (!buf) buf = (char *) malloc(buf_size);

        if (getcwd(buf, buf_size) != NULL) return buf;

        if (errno == ERANGE) { /* buffer too small */
            buf_size += 1024;
            free(buf);
            buf = 0;
        } else
            run_error(DIRS_ERR, strerror(errno));
    }
}

PUBLIC char *sys_unit_string() {
    return strdup("C:"); /* :-) */
}

PUBLIC void sys_unit(char *unit) {
}

PUBLIC void sys_chdir(char *dir) {
    if (chdir(dir) < 0)
        run_error(DIR_ERR, strerror(errno));
}

PUBLIC void sys_mkdir(char *dir) {
    if (mkdir(dir, 0777) < 0)
        run_error(DIR_ERR, strerror(errno));
}

PUBLIC void sys_rmdir(char *dir) {
    if (rmdir(dir) < 0)
        run_error(DIR_ERR, strerror(errno));
}

PUBLIC void sys_opencomalrc() {
    const char *homedir = getenv("HOME");

    if (!homedir)
        return;

    char filename[PATH_MAX];

    strcpy(filename, homedir);
    strcat(filename, "/.opencomalrc");

    if (access(filename, F_OK) == 0)
        do_enter(filename);
}


PUBLIC void sys_init() {
    ext_init();
    js_alert("Welcome to the OpenComal playground. It is somewhat limited and might have bugs :-)");
}


PUBLIC void sys_tini() {
    ext_tini();
}


PUBLIC int sys_escape() {
    if (curenv->escallowed) {
        emscripten_sleep(0);
        return js_escape();
    }

    return 0;
}


PUBLIC void sys_put(int stream, char *buf, long len) {
    js_print(buf);
}


PUBLIC void sys_page(FILE *f) {
    js_page();
}

PUBLIC void sys_cursor(FILE *f, long x, long y) {
    js_cursor(x, y);
}


PUBLIC void sys_nl(int stream) {
    ext_nl();
    js_print("\n");
}


PUBLIC void sys_screen_readjust() {
}


PUBLIC int sys_yn(int stream, char *s) {
    return js_confirm(s);
}


PRIVATE int do_get(int stream, char *line, int maxlen, char *prompt, int edit) {
    char *s = js_read(line ? line : "", prompt ? prompt : "");

    strncpy(line, s, maxlen);
    line[maxlen - 1] = 0;
    free(s);

    return 0;
}


PUBLIC int sys_get(int stream, char *line, int maxlen, char *prompt) {
    line[0] = '\0';
    return do_get(stream, line, maxlen, prompt, 0);
}


PUBLIC int sys_edit(int stream, char *line, int maxlen, int cursor) {
    return do_get(stream, line, maxlen, "E", 1);
}

PUBLIC int sys_system(char *cmd) {
    run_error(NIMP_ERR, "Not implemented");
}

PUBLIC void sys_dir(char *pattern) {
    char *dir = pattern ? (*pattern ? pattern : ".") : ".";
    DIR *fd = opendir(dir);
    struct dirent *de;
    char buf[300];
    char c;

    if (fd == NULL) {
        run_error(DIR_ERR, "Cannot open directory");
    }

    do {
        de = readdir(fd);

        if (de == NULL) {
            closedir(fd);
            return;
        }

        switch (de->d_type) {
            case DT_FIFO:
                c = 'p';
                break;
            case DT_CHR:
                c = 'c';
                break;
            case DT_DIR:
                c = 'd';
                break;
            case DT_REG:
                c = ' ';
                break;
            case DT_BLK:
                c = 'b';
                break;
            case DT_LNK:
                c = 'l';
                break;
            case DT_SOCK:
                c = 's';
                break;
            default:
                c = '?';
                break;
        }

        sprintf(buf, "%c %s\n", c, de->d_name);
        js_print(buf);
    } while (1);
}

PUBLIC void sys_setpaged(int n) {}

PUBLIC char *sys_key(long delay) {
    return js_key(delay);
}

PUBLIC int sys_quit_allowed() {
    return 0;
}

PUBLIC int sys_call_scan(struct id_rec *id, struct exp_list *exproot, char *errtext) {
    int rc = ext_call_scan(id, exproot, errtext);

    if (rc == -1) {
        sprintf(errtext, "PROCedure %s not found", id->id_name);
        return 0;
    }

    return rc;
}

PUBLIC int sys_call(struct id_rec *id, struct exp_list *exproot, int calltype, void **result, enum VAL_TYPE *type) {
    return ext_call(id, exproot, calltype, result, type);
}

PUBLIC void sys_sys_exp(struct exp_list *exproot, void **result, enum VAL_TYPE *type) {
    if (ext_sys_exp(exproot, result, type) == -1)
        run_error(SYS_ERR, "Unknown SYS() command");
}

PUBLIC void sys_syss_exp(struct exp_list *exproot, struct string **result, enum VAL_TYPE *type) {
    if (ext_syss_exp(exproot, result, type) == -1)
        run_error(SYS_ERR, "Unknown SYS$() command");
}

PUBLIC int sys_sys_stat(struct exp_list *exproot) {
    int rc = ext_sys_stat(exproot);

    if (rc == -1)
        run_error(SYS_ERR, "Unknown SYS statement command");

    return rc;
}
