module patch.externalpipe;

import core.sys.posix.signal : signal, SIGPIPE, SIG_IGN;
import core.sys.posix.unistd : pipe, fork, dup2, close, execvp, STDIN_FILENO, STDOUT_FILENO;
import core.stdc.stdio : fprintf, stderr, perror;
import core.stdc.stdlib : exit;

import st : Arg, term, Glyph, GlyphAttribute, UTF_SIZ, tlinelen, xwrite, utf8encode;
import patches : isPatchEnabled;
import std.algorithm : min;

static if (isPatchEnabled!"EXTERNALPIPEIN_PATCH") {
    // csdfd is the slave fd of the pty
    import st : csdfd;
}

static if (isPatchEnabled!"REFLOW_PATCH") {
    // TLINE is defined in st module
    import st : TLINE;
}

__gshared int extpipeactive = 0;

static if (isPatchEnabled!"EXTERNALPIPEIN_PATCH") {
    void extpipe(const(Arg)* arg, int in_) {
        int[2] to;
        char[UTF_SIZ] buf;
        extern(C) void function(int) nothrow @nogc oldsigpipe;
        Glyph* bp, end;
        int lastpos, n, newline;
        
        if (pipe(to) == -1)
            return;
            
        switch (fork()) {
        case -1:
            close(to[0]);
            close(to[1]);
            return;
        case 0:
            dup2(to[0], STDIN_FILENO);
            close(to[0]);
            close(to[1]);
            if (in_)
                dup2(csdfd, STDOUT_FILENO);
            close(csdfd);
            execvp((cast(char**)arg.v)[0], cast(char**)arg.v);
            fprintf(stderr, "st: execvp %s\n", (cast(char**)arg.v)[0]);
            perror("failed");
            exit(0);
        default:
            break;
        }
        
        close(to[0]);
        // ignore sigpipe for now, in case child exists early
        oldsigpipe = signal(SIGPIPE, SIG_IGN);
        newline = 0;
        for (n = 0; n < term.row; n++) {
            bp = term.line[n];
            lastpos = min(tlinelen(n) + 1, term.col) - 1;
            if (lastpos < 0)
                break;
            end = &bp[lastpos + 1];
            for (; bp < end; ++bp)
                if (xwrite(to[1], buf.ptr, utf8encode(bp.u, buf.ptr)) < 0)
                    break;
            if ((newline = term.line[n][lastpos].mode & GlyphAttribute.WRAP) != 0)
                continue;
            if (xwrite(to[1], "\n".ptr, 1) < 0)
                break;
            newline = 0;
        }
        if (newline)
            xwrite(to[1], "\n".ptr, 1);
        close(to[1]);
        // restore
        signal(SIGPIPE, oldsigpipe);
        extpipeactive = 1;
    }
    
    void externalpipe(const(Arg)* arg) {
        extpipe(arg, 0);
    }
    
    void externalpipein(const(Arg)* arg) {
        extpipe(arg, 1);
    }
} else {
    void externalpipe(const(Arg)* arg) {
        int[2] to;
        char[UTF_SIZ] buf;
        extern(C) void function(int) nothrow @nogc oldsigpipe;
        Glyph* bp, end;
        int lastpos, n, newline;
        
        if (pipe(to) == -1)
            return;
            
        switch (fork()) {
        case -1:
            close(to[0]);
            close(to[1]);
            return;
        case 0:
            dup2(to[0], STDIN_FILENO);
            close(to[0]);
            close(to[1]);
            execvp((cast(char**)arg.v)[0], cast(char**)arg.v);
            fprintf(stderr, "st: execvp %s\n", (cast(char**)arg.v)[0]);
            perror("failed");
            exit(0);
        default:
            break;
        }
        
        close(to[0]);
        // ignore sigpipe for now, in case child exists early
        oldsigpipe = signal(SIGPIPE, SIG_IGN);
        newline = 0;
        for (n = 0; n < term.row; n++) {
            bp = term.line[n];
            lastpos = min(tlinelen(n) + 1, term.col) - 1;
            if (lastpos < 0)
                break;
            end = &bp[lastpos + 1];
            for (; bp < end; ++bp)
                if (xwrite(to[1], buf.ptr, utf8encode(bp.u, buf.ptr)) < 0)
                    break;
            if ((newline = term.line[n][lastpos].mode & GlyphAttribute.WRAP) != 0)
                continue;
            if (xwrite(to[1], "\n".ptr, 1) < 0)
                break;
            newline = 0;
        }
        if (newline)
            xwrite(to[1], "\n".ptr, 1);
        close(to[1]);
        // restore
        signal(SIGPIPE, oldsigpipe);
        extpipeactive = 1;
    }
}