/*
 * Pantum M7100/M7012/M7160/M7170 family PostScript CUPS filter.
 *
 * The official PS PPDs in this family use the vendor ptps path and can emit
 * PJL/JCL before the PostScript body. Some devices or queues may print that
 * wrapper as page content, so this replacement normalizes the job stream:
 * strip the leading wrapper, keep the PostScript body, and force simplex
 * unless the CUPS options explicitly request duplex.
 */

#include <errno.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>

#define COPY_BUFFER_SIZE 65536
#define PROLOGUE_SCAN_LIMIT (1024 * 1024)

static const unsigned char SIMPLEX_SETUP[] =
    "%%BeginFeature: *Duplex None\n"
    "<</Duplex false /Tumble false>>setpagedevice\n"
    "%%EndFeature\n";

static bool is_option_separator(char ch)
{
    return ch == ' ' || ch == '\t' || ch == '\n' || ch == '\r';
}

static bool option_value_equals(const char *options,
                                const char *key,
                                const char *value)
{
    size_t key_len = strlen(key);
    size_t value_len = strlen(value);
    const char *cursor = options;

    if (options == NULL) {
        return false;
    }

    while (*cursor != '\0') {
        const char *token_start;
        const char *token_end;
        const char *equals;
        const char *token_value;
        size_t token_key_len;
        size_t token_value_len;

        while (is_option_separator(*cursor)) {
            cursor++;
        }
        if (*cursor == '\0') {
            break;
        }

        token_start = cursor;
        while (*cursor != '\0' && !is_option_separator(*cursor)) {
            cursor++;
        }
        token_end = cursor;

        equals = memchr(token_start, '=', (size_t)(token_end - token_start));
        if (equals == NULL) {
            continue;
        }

        token_key_len = (size_t)(equals - token_start);
        token_value = equals + 1;
        token_value_len = (size_t)(token_end - token_value);

        if (token_value_len >= 2 &&
            ((*token_value == '"' && token_value[token_value_len - 1] == '"') ||
             (*token_value == '\'' && token_value[token_value_len - 1] == '\''))) {
            token_value++;
            token_value_len -= 2;
        }

        if (token_key_len == key_len &&
            token_value_len == value_len &&
            strncasecmp(token_start, key, key_len) == 0 &&
            strncasecmp(token_value, value, value_len) == 0) {
            return true;
        }
    }

    return false;
}

static const char *duplex_mode(const char *options)
{
    if (option_value_equals(options, "Duplex", "DuplexNoTumble") ||
        option_value_equals(options, "sides", "two-sided-long-edge")) {
        return "long-edge";
    }

    if (option_value_equals(options, "Duplex", "DuplexTumble") ||
        option_value_equals(options, "sides", "two-sided-short-edge")) {
        return "short-edge";
    }

    return "simplex";
}

static const unsigned char *find_ps_marker(const unsigned char *data, size_t len)
{
    size_t i;

    for (i = 0; i + 1 < len; i++) {
        if (data[i] == '%' && data[i + 1] == '!') {
            return data + i;
        }
    }

    return NULL;
}

static int write_all(FILE *out, const unsigned char *data, size_t len)
{
    if (len == 0) {
        return 0;
    }

    if (fwrite(data, 1, len, out) != len) {
        fprintf(stderr, "ERROR: failed to write PostScript output: %s\n",
                strerror(errno));
        return 1;
    }

    return 0;
}

static int write_simplex_setup_after_header(FILE *out,
                                            const unsigned char *data,
                                            size_t len)
{
    const unsigned char *line_end;
    size_t header_len;

    line_end = memchr(data, '\n', len);
    if (line_end == NULL) {
        return write_all(out, SIMPLEX_SETUP, sizeof(SIMPLEX_SETUP) - 1) ||
               write_all(out, data, len);
    }

    header_len = (size_t)(line_end - data) + 1;
    return write_all(out, data, header_len) ||
           write_all(out, SIMPLEX_SETUP, sizeof(SIMPLEX_SETUP) - 1) ||
           write_all(out, data + header_len, len - header_len);
}

static int copy_ps_stream(FILE *in, FILE *out, bool force_simplex)
{
    unsigned char buffer[COPY_BUFFER_SIZE];
    unsigned char prologue[PROLOGUE_SCAN_LIMIT + 2];
    size_t prologue_len = 0;
    bool started = false;

    for (;;) {
        size_t got = fread(buffer, 1, sizeof(buffer), in);

        if (got > 0 && !started) {
            const unsigned char *marker;
            size_t remaining = sizeof(prologue) - prologue_len;
            size_t copy_len = got < remaining ? got : remaining;

            memcpy(prologue + prologue_len, buffer, copy_len);
            prologue_len += copy_len;
            marker = find_ps_marker(prologue, prologue_len);

            if (marker != NULL) {
                size_t marker_offset = (size_t)(marker - prologue);
                size_t ps_len = prologue_len - marker_offset;

                if (marker_offset > 0) {
                    fprintf(stderr,
                            "INFO: stripped %zu bytes of leading PJL/JCL wrapper before PostScript marker\n",
                            marker_offset);
                }

                if (force_simplex) {
                    if (write_simplex_setup_after_header(out, marker, ps_len) != 0) {
                        return 1;
                    }
                } else if (write_all(out, marker, ps_len) != 0) {
                    return 1;
                }

                if (copy_len < got &&
                    write_all(out, buffer + copy_len, got - copy_len) != 0) {
                    return 1;
                }

                started = true;
            } else if (prologue_len >= PROLOGUE_SCAN_LIMIT) {
                fprintf(stderr,
                        "WARNING: no PostScript marker found in first %d bytes; passing job through unchanged\n",
                        PROLOGUE_SCAN_LIMIT);
                if (write_all(out, prologue, prologue_len) != 0 ||
                    (copy_len < got &&
                     write_all(out, buffer + copy_len, got - copy_len) != 0)) {
                    return 1;
                }
                started = true;
            }
        } else if (got > 0) {
            if (write_all(out, buffer, got) != 0) {
                return 1;
            }
        }

        if (got < sizeof(buffer)) {
            if (ferror(in)) {
                fprintf(stderr, "ERROR: failed to read PostScript input: %s\n",
                        strerror(errno));
                return 1;
            }
            break;
        }
    }

    if (!started && prologue_len > 0) {
        const unsigned char *marker = find_ps_marker(prologue, prologue_len);

        if (marker != NULL) {
            size_t marker_offset = (size_t)(marker - prologue);
            size_t ps_len = prologue_len - marker_offset;

            if (marker_offset > 0) {
                fprintf(stderr,
                        "INFO: stripped %zu bytes of leading PJL/JCL wrapper before PostScript marker\n",
                        marker_offset);
            }

            if (force_simplex) {
                if (write_simplex_setup_after_header(out, marker, ps_len) != 0) {
                    return 1;
                }
            } else if (write_all(out, marker, ps_len) != 0) {
                return 1;
            }
        } else if (write_all(out, prologue, prologue_len) != 0) {
            return 1;
        }
    }

    if (fflush(out) != 0) {
        fprintf(stderr, "ERROR: failed to flush PostScript output: %s\n",
                strerror(errno));
        return 1;
    }

    return 0;
}

int main(int argc, char **argv)
{
    FILE *input = stdin;
    const char *input_name = "-";
    const char *duplex;
    bool force_simplex;

    if (argc != 6 && argc != 7) {
        fprintf(stderr,
                "ERROR: usage: %s job-id user title copies options [file]\n",
                argv[0]);
        return 1;
    }

    if (argc == 7) {
        input_name = argv[6];
        input = fopen(input_name, "rb");
        if (input == NULL) {
            fprintf(stderr, "ERROR: failed to open %s: %s\n",
                    input_name, strerror(errno));
            return 1;
        }
    }

    duplex = duplex_mode(argv[5]);
    force_simplex = strcmp(duplex, "simplex") == 0;

    fprintf(stderr,
            "INFO: pantum-m7100dw-ps-filter family=Pantum-M7-PS protocol=postscript action=ps-normalize copies=%s duplex=%s options=\"%s\" input=%s\n",
            argv[4], duplex, argv[5], input_name);

    int result = copy_ps_stream(input, stdout, force_simplex);

    if (input != stdin && fclose(input) != 0) {
        fprintf(stderr, "ERROR: failed to close %s: %s\n",
                input_name, strerror(errno));
        return 1;
    }

    return result;
}
