/*
    See license.txt in the root of this project.
*/

# include "luametatex.h"
# include "lmtoptional.h"

# define GS_ARG_ENCODING_UTF8 1

typedef struct gslib_state_info {

    int         initialized;
    int         padding;
    luaL_Buffer outbuffer;
    luaL_Buffer errbuffer;

    int (*gsapi_new_instance) (
        void **pinstance,
        void  *caller_handle
    );

    void (*gsapi_delete_instance) (
        void * instance
    );

    int (*gsapi_set_arg_encoding) (
        void *instance,
        int   encoding
    );

    int (*gsapi_init_with_args) (
        void        *instance,
        int          argc,
        const char **argv
    );

    int (*gsapi_set_stdio) (
        void *instance,
        int (*stdin_fn )(void *caller_handle, char       *buf, int len),
        int (*stdout_fn)(void *caller_handle, const char *str, int len),
        int (*stderr_fn)(void *caller_handle, const char *str, int len)
    );

    /*
    int (*gsapi_run_string_begin)       (void *instance, int user_errors, int *pexit_code);
    int (*gsapi_run_string_continue)    (void *instance, const char *str, unsigned int length, int user_errors, int *pexit_code);
    int (*gsapi_run_string_end)         (void *instance, int user_errors, int *pexit_code);
    int (*gsapi_run_string_with_length) (void *instance, const char *str, unsigned int length, int user_errors, int *pexit_code);
    int (*gsapi_run_string)             (void *instance, const char *str, int user_errors, int *pexit_code);
    int (*gsapi_run_file)               (void *instance, const char *file_name, int user_errors, int *pexit_code);
    int (*gsapi_exit)                   (void *instance);
    */

} gslib_state_info;

static gslib_state_info gslib_state = {

    .initialized                  = 0,
    .padding                      = 0,
 /* .outbuffer                    = NULL, */
 /* .errbuffer                    = NULL, */

    .gsapi_new_instance           = NULL,
    .gsapi_delete_instance        = NULL,
    .gsapi_set_arg_encoding       = NULL,
    .gsapi_init_with_args         = NULL,
    .gsapi_set_stdio              = NULL,

};

static int gslib_initialize(lua_State * L)
{
    if (! gslib_state.initialized) {
        const char *filename = lua_tostring(L, 1);
        if (filename) {

            lmt_library lib = lmt_library_load(filename);

            gslib_state.gsapi_new_instance     = lmt_library_find(lib, "gsapi_new_instance");
            gslib_state.gsapi_delete_instance  = lmt_library_find(lib, "gsapi_delete_instance");
            gslib_state.gsapi_set_arg_encoding = lmt_library_find(lib, "gsapi_set_arg_encoding");
            gslib_state.gsapi_init_with_args   = lmt_library_find(lib, "gsapi_init_with_args");
            gslib_state.gsapi_set_stdio        = lmt_library_find(lib, "gsapi_set_stdio");

            gslib_state.initialized = lmt_library_okay(lib);
        }
    }
    lua_pushboolean(L, gslib_state.initialized);
    return 1;
}

/* We could have a callback for stdout and error. */

static int gslib_stdout(void * caller_handle, const char *str, int len)
{
    (void) caller_handle;
    luaL_addlstring(&gslib_state.outbuffer, str, len);
    return len;
}

static int gslib_stderr(void * caller_handle, const char *str, int len)
{
    (void) caller_handle;
    luaL_addlstring(&gslib_state.errbuffer, str, len);
    return len;
}

static int gslib_execute(lua_State * L)
{
    if (gslib_state.initialized) {
        if (lua_type(L, 1) == LUA_TTABLE) {
            size_t n = (int) lua_rawlen(L, 1);
            if (n > 0) {
                void *instance = NULL;
                int result = gslib_state.gsapi_new_instance(&instance, NULL);
                if (result >= 0) {
                    /*tex
                        Strings are not yet garbage colected. We add some slack. Here MSVC wants
                        |char**| and gcc wants |const char**| i.e.\ doesn't like a castso we just
                        accept the less annoying MSVC warning.
                    */
                    const char** arguments = malloc((n + 2) * sizeof(char*));
                    if (arguments) {
                        int m = 1;
                        /*tex This is a kind of dummy. */
                        arguments[0] = "ghostscript";
                        luaL_buffinit(L, &gslib_state.outbuffer);
                        luaL_buffinit(L, &gslib_state.errbuffer);
                        gslib_state.gsapi_set_stdio(instance, NULL, &gslib_stdout, &gslib_stderr);
                        for (size_t i = 1; i <= n; i++) {
                            lua_rawgeti(L, 1, i);
                            switch (lua_type(L, -1)) {
                                case LUA_TSTRING:
                                case LUA_TNUMBER:
                                {
                                    size_t l = 0;
                                    const char *s = lua_tolstring(L, -1, &l);
                                    if (l > 0) {
                                        arguments[m] = s;
                                        m += 1;
                                    }
                                }
                                break;
                            }
                            lua_pop(L, 1);
                        }
                        arguments[m] = NULL;
                        result = gslib_state.gsapi_set_arg_encoding(instance, GS_ARG_ENCODING_UTF8);
                        result = gslib_state.gsapi_init_with_args(instance, m, arguments);
                        gslib_state.gsapi_delete_instance(instance);
                        /* Nothing done with the array cells! No gc done yet anyway. */
                        free((void *) arguments);
                        lua_pushboolean(L, result >= 0);
                        luaL_pushresult(&gslib_state.outbuffer);
                        luaL_pushresult(&gslib_state.errbuffer);
                        return 3;
                    }
                }
            }
        }
    }
    return 0;
}

static struct luaL_Reg gslib_function_list[] = {
    { "initialize", gslib_initialize },
    { "execute",    gslib_execute    },
    { NULL,         NULL             },
};

int luaopen_ghostscript(lua_State * L)
{
    lmt_library_register(L, "ghostscript", gslib_function_list);
    return 0;
}