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

# include "luametatex.h"

fileio_state_info lmt_fileio_state = {
   .io_buffer        = NULL,
   .io_buffer_data   = {
        .minimum   = min_buffer_size,
        .maximum   = max_buffer_size,
        .size      = siz_buffer_size,
        .step      = stp_buffer_size,
        .allocated = 0,
        .itemsize  = sizeof(unsigned char),
        .top       = 0,
        .ptr       = 0,
        .initial   = memory_data_unset,
        .offset    = 0,
   },
   .io_first         = 0,
   .io_last          = 0,
   .name_in_progress = 0,
   .log_opened       = 0,
   .job_name         = NULL,
   .log_name         = NULL,
   .fmt_name         = NULL
};

/*tex

    Once \TEX\ is working, you should be able to diagnose most errors with the |\show| commands and
    other diagnostic features. Because we have made some internal changes the optional debug interface
    has been removed.

*/

# define reserved_io_buffer_slots 256

void tex_initialize_fileio_state(void)
{
    int size = lmt_fileio_state.io_buffer_data.minimum;
    lmt_fileio_state.io_buffer = aux_allocate_clear_array(sizeof(unsigned char), size, reserved_io_buffer_slots);
    if (lmt_fileio_state.io_buffer) {
        lmt_fileio_state.io_buffer_data.allocated = size;
    } else {
        tex_overflow_error("buffer", size);
    }
}

bool tex_room_in_buffer(int top)
{
    /*tex Beware: |top| can exceed the old size plus the step. */
    if (top > lmt_fileio_state.io_buffer_data.top) {
       lmt_fileio_state.io_buffer_data.top = top;
        if (top > lmt_fileio_state.io_buffer_data.allocated) {
            unsigned char *tmp = NULL;
            if (top <= lmt_fileio_state.io_buffer_data.size) {
                if (lmt_fileio_state.io_buffer_data.allocated + lmt_fileio_state.io_buffer_data.step > top) {
                    top = lmt_fileio_state.io_buffer_data.allocated + lmt_fileio_state.io_buffer_data.step;
                    if (top > lmt_fileio_state.io_buffer_data.size) {
                        top = lmt_fileio_state.io_buffer_data.size;
                    }
                }
                if (top > lmt_fileio_state.io_buffer_data.allocated) {
                    lmt_fileio_state.io_buffer_data.allocated = top;
                    tmp = aux_reallocate_array(lmt_fileio_state.io_buffer, sizeof(unsigned char), top, reserved_io_buffer_slots);
                    lmt_fileio_state.io_buffer = tmp;
                }
            }
            lmt_run_memory_callback("buffer", tmp ? 1 : 0);
            if (! tmp) {
                tex_overflow_error("buffer", top);
                return false;
            }
        }
    }
    return true;
}

static int tex_aux_open_outfile(FILE **f, const char *name, const char *mode)
{
    FILE *res = aux_utf8_fopen(name, mode);
    if (res) {
        *f = res;
        return 1;
    }
    return 0;
}

/*tex

    We conform to the way \WEBC\ does handle trailing tabs and spaces. This decade old behaviour
    was changed in September 2017 and can introduce compatibility issues in existing workflows.
    Because we don't want too many differences with upstream \TEX live we just follow up on that
    patch and it's up to macro packages to deal with possible issues (which can be done via the
    usual callbacks. One can wonder why we then still prune spaces but we leave that to the reader.

    Patched original comment:

    Make last be one past the last non-space character in \quote {buffer}, ignoring line
    terminators (but not, e.g., tabs). This is because we are supposed to treat this like a line of
    TeX input. Although there are pathological cases (|SP CR SC CR|) where this differs from
    input_line below, and from previous behavior of removing all whitespace, the simplicity of
    removing all trailing line terminators seems more in keeping with actual command line
    processing.

    The |IS_SPC_OR_EOL| macro deals with space characters (|SPACE 32|) and newlines (|CR| and |LF|)
    and no longer looks at tabs (|TAB 9|).

*/

/*
    The terminal input code is gone as is the read related code (that had already been nicely
    cleaned up and abstracted but that is the price we pay for stepwise progress. That code is
    still in the git repository of course.

    At some point I might do the same as we do in mplib: four callbacks for open, close, read
    and write (in which case the log goes via write). Part of the management is them moved to
    \LUA\ and we save a lookup.

    When I adapted the code in this module and the one dealing with errors, I decided to delegate
    all interaction to \LUA, also because the sometimes tight integration in the scanning and
    expansion mechanisms. In the 2021 TeX tuneup there have been some patches in the interaction
    code and some remarks ring a bell: especially the relation between offering feedback and
    waiting for input. However, because we delegate to \LUA, the engine is no longer responsible
    for what the macro package lets the user do in case of an error. For instance, in \CONTEXT\ we
    just abort the run: it makes no sense to carry on the wrong way. Computers are fast enough for
    a \quotation {Fix and run again.} approach. But we do offer the message and optional help as
    cue. On the agenda is a further abstraction of error handling. This deviation is fine as we
    obey Don's wish to not call it \TEX\ but instead add some more letters to the name.

*/

int tex_lua_a_open_in(const char *fn)
{
    int callback_id = lmt_callback_defined(open_data_file_callback);
    if (callback_id > 0) {
        int k = lmt_run_and_save_callback(lmt_lua_state.lua_instance, callback_id, "S->", fn);
        lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id = k;
        return k > 0;
    } else {
        tex_emergency_message("startup error", "missing open_data_file callback");
        tex_emergency_exit();
        return 0;
    }
}

void tex_lua_a_close_in()
{
    int k = lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id;
    if (k > 0) {
        lmt_run_saved_callback_close(lmt_lua_state.lua_instance, k);
        lmt_destroy_saved_callback(lmt_lua_state.lua_instance, k);
        lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id = 0;
    }
}

/*tex

    Binary input and output are done with \CCODE's ordinary procedures, so we don't have to make
    any other special arrangements for binary \IO. Text output is also easy to do with standard
    routines. The treatment of text input is more difficult, however, because of the necessary
    translation to |unsigned char| values. \TEX's conventions should be efficient, and they should
    blend nicely with the user's operating environment.

    Input from text files is read one line at a time, using a routine called |lua_input_ln|. This
    function is defined in terms of global variables called |buffer|, |first|, and |last| that will
    be described in detail later; for now, it suffices for us to know that |buffer| is an array of
    |unsigned char| values, and that |first| and |last| are indices into this array representing
    the beginning and ending of a line of text.

    The lines of characters being read: |buffer|, the first unused position in |first|, the end of
    the line just input |last|, the largest index used in |buffer|: |max_buf_stack|.

    The |lua_input_ln| function brings the next line of input from the specified file into available
    positions of the buffer array and returns the value |true|, unless the file has already been
    entirely read, in which case it returns |false| and sets |last:=first|. In general, the
    |unsigned char| numbers that represent the next line of the file are input into |buffer[first]|,
    |buffer[first + 1]|, \dots, |buffer[last - 1]|; and the global variable |last| is set equal to
    |first| plus the length of the line. Trailing blanks are removed from the line; thus, either
    |last = first| (in which case the line was entirely blank) or |buffer[last - 1] <> " "|.

    An overflow error is given, however, if the normal actions of |lua_input_ln| would make |last
    >= buf_size|; this is done so that other parts of \TEX\ can safely look at the contents of
    |buffer[last+1]| without overstepping the bounds of the |buffer| array. Upon entry to
    |lua_input_ln|, the condition |first < buf_size| will always hold, so that there is always room
    for an \quote {empty} line.

    The variable |max_buf_stack|, which is used to keep track of how large the |buf_size| parameter
    must be to accommodate the present job, is also kept up to date by |lua_input_ln|.

    If the |bypass_eoln| parameter is |true|, |lua_input_ln| will do a |get| before looking at the
    first character of the line; this skips over an |eoln| that was in |f^|. The procedure does not
    do a |get| when it reaches the end of the line; therefore it can be used to acquire input from
    the user's terminal as well as from ordinary text files.

    Since the inner loop of |lua_input_ln| is part of \TEX's \quote {inner loop} --- each character
    of input comes in at this place --- it is wise to reduce system overhead by making use of
    special routines that read in an entire array of characters at once, if such routines are
    available.

*/

int tex_lua_input_ln(void) /*tex |bypass_eoln| was not used */
{
    int callback_id = lmt_input_state.in_stack[lmt_input_state.cur_input.index].input_file_callback_id;
    if (callback_id > 0) {
        lua_State *L = lmt_lua_state.lua_instance;
        int last_ptr = 0;
        lmt_fileio_state.io_last = lmt_fileio_state.io_first;
        last_ptr = lmt_run_saved_callback_line(L, callback_id, lmt_fileio_state.io_first);
        if (last_ptr < 0) {
            return 0;
        } else if (last_ptr > 0) {
            lmt_fileio_state.io_last = last_ptr;
            if (last_ptr > lmt_fileio_state.io_buffer_data.top) {
                lmt_fileio_state.io_buffer_data.top = last_ptr;
            }
        }
        return 1;
    } else {
        return 0;
    }
}

/*tex

    We need a special routine to read the first line of \TEX\ input from the user's terminal.
    This line is different because it is read before we have opened the transcript file; there is
    sort of a \quote {chicken and egg} problem here. If the user types |\input paper| on the first
    line, or if some macro invoked by that line does such an |\input|, the transcript file will be
    named |paper.log|; but if no |\input| commands are performed during the first line of terminal
    input, the transcript file will acquire its default name |texput.log|. (The transcript file
    will not contain error messages generated by the first line before the first |\input| command.)

    The first line is special also because it may be read before \TEX\ has input a format file. In
    such cases, normal error messages cannot yet be given. The following code uses concepts that
    will be explained later.

    Different systems have different ways to get started. But regardless of what conventions are
    adopted, the routine that initializes the terminal should satisfy the following specifications:

    \startitemize[n]

        \startitem
            It should open file |term_in| for input from the terminal.
        \stopitem

        \startitem
            If the user has given a command line, this line should be considered the first line of
            terminal input. Otherwise the user should be prompted with |**|, and the first line of
            input should be whatever is typed in response.
        \stopitem

        \startitem
            The first line of input, which might or might not be a command line, should appear in
            locations |first| to |last-1| of the |buffer| array.
        \stopitem

        \startitem
            The global variable |loc| should be set so that the character to be read next by \TEX\
            is in |buffer[loc]|. This character should not be blank, and we should have |loc < last|.
        \stopitem

    \stopitemize

    It may be necessary to prompt the user several times before a non-blank line comes in. The
    prompt is |**| instead of the later |*| because the meaning is slightly different: |\input|
    need not be typed immediately after |**|.)

    The following code does the required initialization. If anything has been specified on the
    command line, then |t_open_in| will return with |last > first|.

    This code has been adapted and we no longer ask for a name. It makes no sense because one needs
    to initialize the primitives and backend anyway and no one is going to do that interactively.
    Of course one can implement a session in \LUA. We keep the \TEX\ trick to push the name into
    the input buffer and then exercise an |\input| which ensures proper housekeeping. There is a
    bit overkill in the next function but for now we keep it (as reference).

    For a while copying the argument to th ebuffer lived in the engine lib but it made no sense
    to duplicate code, so now it's here. Anyway, the following does no longer apply:

    \startquotation
    This is supposed to open the terminal for input, but what we really do is copy command line
    arguments into \TEX's buffer, so it can handle them. If nothing is available, or we've been
    called already (and hence, |argc == 0|), we return with |last = first|.
    \stopquotation

    In \LUAMETATEX\ we don't really have a terminal. In the \LUATEX\ precursor we used to append
    all the remaining arguments but now we just take the first one. If one wants filenames with
    spaces \unknown\ use quotes. Keep in mind that original \TEX\ permits this:

    \starttyping
    tex ... filename \\hbox{!} \\end
    \stoptyping

    But we don't follow that route in the situation where \LUA\ is mostly in charge of passing
    input from files and the console.

    In the end I went for an easier solution: just pass the name to the file reader. But we keep
    this as nostalgic reference to how \TEX\ originally kin dof did these things.

    \starttyping
    int input_file_name_pushed(void)
    {
        const char *ptr = engine_input_filename();
        if (ptr) {
            int len = strlen(ptr);
            fileio_state.io_buffer[fileio_state.io_first] = 0;
            if (len > 0 && room_in_buffer(len + 1)) {
                // We cannot use strcat, because we have multibyte UTF-8 input. Hm, why not.
                fileio_state.io_last= fileio_state.io_first;
                while (*ptr) {
                    fileio_state.io_buffer[fileio_state.io_last++] = (unsigned char) * (ptr++);
                }
                // Backtrack over spaces and newlines.
                for (
                    --fileio_state.io_last;
                    fileio_state.io_last >= fileio_state.io_first && IS_SPC_OR_EOL(fileio_state.io_buffer[fileio_state.io_last]);
                    --fileio_state.io_last
                );
                // Terminate the string.
                fileio_state.io_buffer[++fileio_state.io_last] = 0;
                // One more time, this time converting to \TEX's internal character representation.
                if (fileio_state.io_last > fileio_state.io_first) {
                    input_state.cur_input.loc = fileio_state.io_first;
                    while ((input_state.cur_input.loc < fileio_state.io_last) && (fileio_state.io_buffer[input_state.cur_input.loc] == ' ')) {
                        ++input_state.cur_input.loc;
                    }
                    if (input_state.cur_input.loc < fileio_state.io_last) {
                        input_state.cur_input.limit = fileio_state.io_last;
                        fileio_state.io_first = fileio_state.io_last + 1;
                    }
                    if (input_state.cur_input.loc < input_state.cur_input.limit) {
                        return 1;
                    }
                }
            }
        }
        fileio_state.io_first = 1;
        fileio_state.io_last = 1;
        return 0;
    }
    \stopttyping

    It's this kind of magic that can take lots of time to play with and figure out, also because
    we cannot break expectations too much.

*/

/*tex

    Per June 22 2020 the terminal code is gone. See |texlegacy.c| for the old, already adapted
    long ago, code. It was already shedulded for removal a while. We only keep the update.

*/

void tex_terminal_update(void) /* renamed, else conflict in |lmplib|. */
{
    fflush(stdout);
}

/*tex

    It's time now to fret about file names. Besides the fact that different operating systems treat
    files in different ways, we must cope with the fact that completely different naming conventions
    are used by different groups of people. The following programs show what is required for one
    particular operating system; similar routines for other systems are not difficult to devise.

    \TEX\ assumes that a file name has three parts: the name proper; its \quote {extension}; and a
    \quote {file area} where it is found in an external file system. The extension of an input file
    or a write file is assumed to be |.tex| unless otherwise specified; it is |transcript_extension|
    on the transcript file that records each run of \TEX; it is |.tfm| on the font metric files that
    describe characters in the fonts \TEX\ uses; it is |.dvi| on the output files that specify
    typesetting information; and it is |format_extension| on the format files written by \INITEX\
    to initialize \TEX. The file area can be arbitrary on input files, but files are usually output
    to the user's current area.

    Simple uses of \TEX\ refer only to file names that have no explicit extension or area. For
    example, a person usually says |\input paper| or |\font \tenrm = helvetica| instead of |\input
    {paper.new}| or |\font \tenrm = {test}|. Simple file names are best, because they make the \TEX\
    source files portable; whenever a file name consists entirely of letters and digits, it should be
    treated in the same way by all implementations of \TEX. However, users need the ability to refer
    to other files in their environment, especially when responding to error messages concerning
    unopenable files; therefore we want to let them use the syntax that appears in their favorite
    operating system.

    The following procedures don't allow spaces to be part of file names; but some users seem to like
    names that are spaced-out. System-dependent changes to allow such things should probably be made
    with reluctance, and only when an entire file name that includes spaces is \quote {quoted} somehow.

    Here are the global values that file names will be scanned into.

    \starttyping
    strnumber cur_name;
    strnumber cur_area;
    strnumber cur_ext;
    \stoptyping

    The file names we shall deal with have the following structure: If the name contains |/| or |:|
    (for Amiga only), the file area consists of all characters up to and including the final such
    character; otherwise the file area is null. If the remaining file name contains |.|, the file
    extension consists of all such characters from the last |.| to the end, otherwise the file
    extension is null.

    We can scan such file names easily by using two global variables that keep track of the
    occurrences of area and extension delimiters:

    Input files that can't be found in the user's area may appear in a standard system area called
    |TEX_area|. Font metric files whose areas are not given explicitly are assumed to appear in a
    standard system area called |TEX_font_area|. These system area names will, of course, vary from
    place to place.

    This whole model has been adapted a little but we do keep the |area|, |name|, |ext| distinction
    for now although we don't use the string pool.

*/

static char *tex_aux_pack_file_name(char *s, int l, const char *name, const char *ext)
{
    const char *fn = (char *) s;
    if ((! fn) || (l <= 0)) {
        fn = name;
    }
    if (! fn) {
        return NULL;
    } else if (! ext) {
        return lmt_memory_strdup(fn);
    } else {
        int e = -1;
        for (int i = 0; i < l; i++) {
            if (IS_DIR_SEP(fn[i])) {
                e = -1;
            } else if (fn[i] == '.') {
                e = i;
            }
        }
        if (e >= 0) {
            return lmt_memory_strdup(fn);
        } else {
            char *f = lmt_memory_malloc(strlen(fn) + strlen(ext) + 1);
            if (f) {
                sprintf(f, "%s%s", fn, ext);
            }
            return f;
        }
    }
}

/*tex

    Here is a routine that manufactures the output file names, assuming that |job_name <> 0|. It
    ignores and changes the current settings of |cur_area| and |cur_ext|; |s = transcript_extension|,
    |".dvi"|, or |format_extension|

    The packer does split the basename every time but isn't called that often so we can use it in
    the checker too.

*/

static char *tex_aux_pack_job_name(const char *e, int keeppath, int keepsuffix)
{
    char *n = lmt_fileio_state.job_name;
    int ln = (n) ? (int) strlen(n) : 0;
    if (! ln) {
        tex_fatal_error("bad jobname");
        return NULL;
    } else {
        int le = (e) ? (int) strlen(e) : 0;
        int f = -1; /* first */
        int l = -1; /* last */
        char *fn = NULL;
        int k = 0;
        for (int i = 0; i < ln; i++) {
            if (IS_DIR_SEP(n[i])) {
                f = i;
                l = -1;
            } else if (n[i] == '.') {
                l = i;
            }
        }
        if (keeppath) {
            f = 0;
        } else if (f < 0) {
            f = 0;
        } else {
            f += 1;
        }
        if (keepsuffix || l < 0) {
            l = ln;
        }
        fn = (char*) lmt_memory_malloc((l - f) + le + 2); /* a bit too much */
        if (fn) {
            for (int i = f; i < l; i++) {
                fn[k++] = n[i];
            }
            for (int i = 0; i < le; i++) {
                fn[k++] = e[i];
            }
            fn[k] = 0;
        }
        return fn;
    }
}

/*tex

    The following comment is obsolete but we keep it as reference because it tells some history.

    \startquotation
    Because the format is zipped we read and write dump files through zlib. Earlier versions recast
    |*f| from |FILE *| to |gzFile|, but there is no guarantee that these have the same size, so a
    static variable is needed.

    We no longer do byte-swapping so formats are generated for the system and not shared. It
    actually slowed down loading of the format on the majority of used platforms (intel).

    A \CONTEXT\ format is uncompressed some 16 MB but that used to be over 30MB due to more
    (preallocated) memory usage. A compressed format is 11 MB so the saving is not that much. If
    we were in lua I'd load the whole file in one go and use a fast decompression after which we
    could access the bytes in memory. But it's not worth the trouble.

    Tests has shown that a level 3 compression is the most optimal tradeoff between file size and
    load time.

    So, in principle we can undefine |FMT_COMPRESSION| below and experiment a bit with it. With
    SSD's it makes no dent, but on a network it still might.

    Per end May 2019 the |FMT_COMPRESSION| branch is gone so that we can simplify the opener and
    closer.
    \stopquotation

*/

void tex_check_fmt_name(void)
{
    if (lmt_engine_state.dump_name) {
        char *tmp = lmt_fileio_state.job_name;
        lmt_fileio_state.job_name = lmt_engine_state.dump_name;
        lmt_fileio_state.fmt_name = tex_aux_pack_job_name(format_extension, 1, 0);
        lmt_fileio_state.job_name = tmp;
    } else if (lmt_main_state.run_state != initializing_state) {
        /*tex For |dump_name| to be NULL is a bug. */
        tex_emergency_message("startup error", "no format file given, quitting");
        tex_emergency_exit();
    }
}

void tex_check_job_name(char * fn)
{
    if (! lmt_fileio_state.job_name) {
        if (lmt_engine_state.startup_jobname) {
            lmt_fileio_state.job_name = lmt_engine_state.startup_jobname; /* not freed here */
            lmt_fileio_state.job_name = tex_aux_pack_job_name(NULL, 0, 0);
        } else if (fn) {
            lmt_fileio_state.job_name = fn;
            lmt_fileio_state.job_name = tex_aux_pack_job_name(NULL, 0, 0); /* not freed here */
        } else {
            tex_emergency_message("startup warning", "using fallback jobname 'texput', continuing");
            lmt_fileio_state.job_name = lmt_memory_strdup("texput");
        }
    }
    if (! lmt_fileio_state.log_name) {
        lmt_fileio_state.log_name = tex_aux_pack_job_name(transcript_extension, 0, 1);
    }
    if (! lmt_fileio_state.fmt_name) {
        lmt_fileio_state.fmt_name = tex_aux_pack_job_name(format_extension, 0, 1);
    }
}

/*tex

    A messier routine is also needed, since format file names must be scanned before \TEX's
    string mechanism has been initialized. We shall use the global variable |TEX_format_default|
    to supply the text for default system areas and extensions related to format files.

    Under \UNIX\ we don't give the area part, instead depending on the path searching that will
    happen during file opening. Also, the length will be set in the main program.

    \starttyping
    char *TEX_format_default;
    \stoptyping

    This part of the program becomes active when a \quote {virgin} \TEX\ is trying to get going,
    just after the preliminary initialization, or when the user is substituting another format file
    by typing |&| after the initial |**| prompt. The buffer contains the first line of input in
    |buffer[loc .. (last - 1)]|, where |loc < last| and |buffer[loc] <> " "|.

*/

dumpstream tex_open_fmt_file(int writemode)
{
    dumpstream f = NULL;
    if (! lmt_fileio_state.fmt_name) {
        /* this can't happen */
        tex_emergency_message("startup error", "no format output file '%s' given, quitting", emergency_fmt_name);
        tex_emergency_exit();
    } else if (writemode) {
        f = aux_utf8_fopen(lmt_fileio_state.fmt_name, FOPEN_WBIN_MODE);
        if (! f) {
            tex_emergency_message("startup error", "invalid format output file '%s' given, quitting", lmt_fileio_state.fmt_name);
            tex_emergency_exit();
        }
    } else {
        int callbackid = lmt_callback_defined(find_format_file_callback);
        if (callbackid > 0) {
            char *fnam = NULL;
            int test = lmt_run_callback(lmt_lua_state.lua_instance, callbackid, "S->R", lmt_fileio_state.fmt_name, &fnam);
            if (test && fnam && strlen(fnam) > 0) {
                lmt_memory_free(lmt_fileio_state.fmt_name);
                lmt_fileio_state.fmt_name = fnam;
            } else {
                lmt_memory_free(fnam);
            }
            f = aux_utf8_fopen(lmt_fileio_state.fmt_name, FOPEN_RBIN_MODE);
            if (! f) {
                tex_emergency_message("startup error", "invalid format input file '%s' given, quitting", emergency_fmt_name);
                tex_emergency_exit();
            }
        } else {
            /*tex For the moment we make this mandate! */
            tex_emergency_message("startup error", "missing find_format_file callback");
            tex_emergency_exit();
        }
    }
    return f;
}

void tex_close_fmt_file(dumpstream f)
{
    if (f) {
        fclose(f);
    }
}

/*tex

    The variable |name_in_progress| is used to prevent recursive use of |scan_file_name|, since the
    |begin_name| and other procedures communicate via global variables. Recursion would arise only
    by devious tricks like |\input \input f|; such attempts at sabotage must be thwarted.
    Furthermore, |name_in_progress| prevents |\input| from being initiated when a font size
    specification is being scanned.

    Another variable, |job_name|, contains the file name that was first |\input| by the user. This
    name is extended by |transcript_extension| and |.dvi| and |format_extension| in the names of
    \TEX's output files. The fact if the transcript file been opened is registered in
    |log_opened_global|.

    Initially |job_name = 0|; it becomes nonzero as soon as the true name is known. We have
    |job_name = 0| if and only if the |log| file has not been opened, except of course for a short
    time just after |job_name| has become nonzero.

    The full name of the log file is stored in |log_name|. The |open_log_file| routine is used to
    open the transcript file and to help it catch up to what has previously been printed on the
    terminal.

*/

void tex_open_log_file(void)
{
    if (! lmt_fileio_state.log_opened) {
        int callback_id = lmt_callback_defined(find_log_file_callback);
        if (callback_id > 0) {
            char *filename = NULL;
            int okay = 0;
            tex_check_job_name(NULL);
            okay = lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "S->R", lmt_fileio_state.log_name, &filename);
            if (okay && filename && (strlen(filename) > 0)) {
                lmt_memory_free(lmt_fileio_state.log_name);
                lmt_fileio_state.log_name = filename;
            } else {
                lmt_memory_free(filename);
            }
        } else {
            /*tex For the moment we make this mandate! */
            tex_emergency_message("startup error", "missing find_log_file callback");
            tex_emergency_exit();
        }
        if (tex_aux_open_outfile(&lmt_print_state.logfile, lmt_fileio_state.log_name, FOPEN_W_MODE)) {
            /*tex The previous |selector| setting is saved:*/
            int saved_selector = lmt_print_state.selector;
            lmt_print_state.selector = logfile_selector_code;
            lmt_fileio_state.log_opened = 1;
            /*tex Again we resolve a callback id: */
            callback_id = lmt_callback_defined(start_run_callback);
            /*tex There is no need to free |fn|! */
            if (callback_id == 0) {
                tex_print_banner();
                /*tex Print the banner line, including current date and time. */
                tex_print_log_banner();
                /*tex Make sure bottom level is in memory. */
                lmt_input_state.input_stack[lmt_input_state.input_stack_data.ptr] = lmt_input_state.cur_input;
                /*tex We don't have a first line so that code is gone. */
                tex_print_ln();
            } else if (callback_id > 0) {
                lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
            } else {
                tex_print_banner();
            }
            /*tex should be done always */
            if (lmt_print_state.loggable_info) {
                fprintf(lmt_print_state.logfile, "%s\n", lmt_print_state.loggable_info);
                lmt_memory_free(lmt_print_state.loggable_info);
                lmt_print_state.loggable_info = NULL;
            }
            switch (saved_selector) {
                case no_print_selector_code : lmt_print_state.selector = logfile_selector_code; break;
                case terminal_selector_code : lmt_print_state.selector = terminal_and_logfile_selector_code; break;
                default                     : lmt_print_state.selector = saved_selector; break;
            }
        } else {
            tex_emergency_message("startup error", "log file '%s' cannot be opened, quitting", emergency_log_name);
            tex_emergency_exit();
        }
    }
}

void tex_close_log_file(void)
{
    fclose(lmt_print_state.logfile);
    lmt_fileio_state.log_opened = 0;
}

/*tex

    Let's turn now to the procedure that is used to initiate file reading when an |\input| command
    is being processed. This function is used with |\\input| as well as in the start up.

*/

void tex_start_input(char *fn, halfword at_end_of_file)
{
    /*tex Set up |cur_file| and new level of input. */
    tex_begin_file_reading();
    if (! tex_lua_a_open_in(fn)) {
        /*tex
            Normally this is catched earler, as we have lookup callbacks but the first file, the
            one passed on the command line can fall though this checking.
        */
        tex_end_file_reading();
        tex_emergency_message("runtime error", "input file '%s' is not found, quitting", fn);
        tex_emergency_exit();
    }
    lmt_input_state.in_stack[lmt_input_state.in_stack_data.ptr].full_source_filename = fn;
    lmt_input_state.cur_input.name = io_file_input_code;
    lmt_input_state.in_stack[lmt_input_state.cur_input.index].at_end_of_file = at_end_of_file;
    /*tex
        |open_log_file| doesn't |show_context|, so |limit| and |loc| needn't be set to meaningful
        values yet.
    */
    tex_report_start_file((unsigned char *) fn);
    ++lmt_input_state.open_files;
    tex_terminal_update();
    lmt_input_state.cur_input.state = new_line_state;
    /*tex

        Read the first line of the new file. Here we have to remember to tell the |lua_input_ln|
        routine not to start with a |get|. If the file is empty, it is considered to contain a
        single blank line.

    */
    lmt_input_state.input_line = 1;
    tex_lua_input_ln();
    lmt_input_state.cur_input.limit = lmt_fileio_state.io_last; /*tex Was |firm_up_the_line();|. */
    if (end_line_char_inactive) {
        --lmt_input_state.cur_input.limit;
    } else {
        lmt_fileio_state.io_buffer[lmt_input_state.cur_input.limit] = (unsigned char) end_line_char_par;
    }
    lmt_fileio_state.io_first = lmt_input_state.cur_input.limit + 1;
    lmt_input_state.cur_input.loc = lmt_input_state.cur_input.start;
}

/*tex

    In order to isolate the system-dependent aspects of file names, the system-independent parts of
    \TEX\ are expressed in terms of three system-dependent procedures called |begin_name|,
    |more_name|, and |end_name|. In essence, if the user-specified characters of the file name are
    |c_1|\unknown|c_n|, the system-independent driver program does the operations

    \starttyping
    |begin_name|;
    |more_name|(c_1);
    .....
    |more_name|(c_n);
    |end_name|
    \stoptyping

    These three procedures communicate with each other via global variables. Afterwards the file
    name will appear in the string pool as three strings called |cur_name|, |cur_area|, and
    |cur_ext|; the latter two are null (i.e., |""|), unless they were explicitly specified by the
    user.

    Actually the situation is slightly more complicated, because \TEX\ needs to know when the file
    name ends. The |more_name| routine is a function (with side effects) that returns |true| on the
    calls |more_name (c_1)|, \dots, |more_name (c_{n - 1})|. The final call |more_name(c_n)| returns
    |false|; or, it returns |true| and the token following |c_n| is something like |\hbox| (i.e.,
    not a character). In other words, |more_name| is supposed to return |true| unless it is sure that
    the file name has been completely scanned; and |end_name| is supposed to be able to finish the
    assembly of |cur_name|, |cur_area|, and |cur_ext| regardless of whether |more_name (c_n)|
    returned |true| or |false|.

    This code has been adapted and the string pool is no longer used. We also don't ask for another
    name on the console.

*/

/*tex

    And here's the second. The string pool might change as the file name is being scanned, since a
    new |\csname| might be entered; therefore we keep |area_delimiter| and |ext_delimiter| relative
    to the beginning of the current string, instead of assigning an absolute address like |pool_ptr|
    to them.

    Now let's consider the \quote {driver} routines by which \TEX\ deals with file names in a
    system-independent manner. First comes a procedure that looks for a file name in the input by
    calling |get_x_token| for the information.

*/

char *tex_read_file_name(int optionalequal, const char * name, const char* ext)
{
    halfword result;
    if (optionalequal) {
        tex_scan_optional_equals();
    }
    do {
        tex_get_x_token();
    } while (cur_cmd == spacer_cmd || cur_cmd == relax_cmd);
    if (cur_cmd == left_brace_cmd) {
        result = tex_scan_toks_expand(1, NULL, 0, 0);
    } else {
        char quote = 0;
        halfword p = get_reference_token();
        result = p;
        while (1) {
            switch (cur_cmd) {
                case escape_cmd:
                case left_brace_cmd:
                case right_brace_cmd:
                case math_shift_cmd:
                case alignment_tab_cmd:
                case parameter_cmd:
                case superscript_cmd:
                case subscript_cmd:
                case letter_cmd:
                case other_char_cmd:
                    switch (cur_chr) { 
                        case double_quote:
                            if (quote == double_quote) {
                                goto DONE;
                            } else {
                                quote = double_quote;
                            }
                            break;
                        case single_quote:
                            if (quote == single_quote) {
                                goto DONE;
                            } else {
                                quote = single_quote;
                            }
                            break;
                        default:
                            p = tex_store_new_token(p, cur_tok);
                    }
                    break;
                case spacer_cmd:
                case end_line_cmd:
                    if (quote) {
                        p = tex_store_new_token(p, token_val(spacer_cmd, ' '));
                    } else {
                        goto DONE;
                    }
                case ignore_cmd:
                    break;
                default:
                    tex_back_input(cur_tok);
                    goto DONE;
            }
            tex_get_x_token();
        }
    }
  DONE:
    {
        int l = 0;
        char *s = tex_tokenlist_to_tstring(result, 1, &l, 0, 0, 0, 1, 1); /* single hashes */
        char *fn = s ? tex_aux_pack_file_name(s, l, name, ext) : NULL;
        return fn;
    }
}

void tex_print_file_name(unsigned char *name)
{
    int must_quote = 0;
    if (name) {
        unsigned char *j = name;
        while (*j) {
            if (*j == ' ') {
                must_quote = 1;
                break;
            } else {
                j++;
            }
        }
    }
    if (must_quote) {
        /* initial quote */
        tex_print_char('"');
    }
    if (name) {
        unsigned char *j = name;
        while (*j) {
            if (*j == '"') {
                /* skip embedded quote, maybe escape */
            } else {
                tex_print_char(*j);
            }
            j++;
        }
    }
    if (must_quote) {
        /* final quote */
        tex_print_char('"');
    }
}

void tex_report_start_file(unsigned char *name)
{
    int callback_id = lmt_callback_defined(start_file_callback);
    if (callback_id) {
        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "S->", name);
    } else {
        tex_print_char('(');
        tex_print_file_name((unsigned char *) name);
    }
}

void tex_report_stop_file(void)
{
    int callback_id = lmt_callback_defined(stop_file_callback);
    if (callback_id) {
        lmt_run_callback(lmt_lua_state.lua_instance, callback_id, "->");
    } else {
        tex_print_char(')');
    }
}
