#include "out2con.h"

#include <windows.h>
#include <stdio.h>
#include <io.h>

class ConsoleEcho
{
public:
    ConsoleEcho();
    ~ConsoleEcho();

private:
    DWORD ThreadLoop();

    static DWORD WINAPI ThreadFunc(void* param);

    FILE m_originalStdout;
    int m_stdoutfd;
    int m_pipefd;
    HANDLE m_hReadPipe, m_hWritePipe;
    HANDLE m_hThread;

    static const int BUFSIZE=512;
};


ConsoleEcho *
CreateConsoleEcho()
{
    return new ConsoleEcho;
}

void
DestroyConsoleEcho(ConsoleEcho *echo)
{
    delete echo;
}


DWORD WINAPI ConsoleEcho::ThreadFunc(void* param)
{
    return ((ConsoleEcho*)(param))->ThreadLoop();
}


DWORD ConsoleEcho::ThreadLoop()
{
    DWORD dwRead, dwWritten;
    CHAR chBuf[BUFSIZE];
    BOOL bSuccess = FALSE;
    // Note that the following does not work when running in the msvc2010
    // debugger with redirected output; you still get the redirected file
    // handle, not the console:
    //HANDLE hConsoleStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
    // This seems to be more reliable:
    HANDLE hConsoleStdOut = CreateFile("CONOUT$",
                                       GENERIC_WRITE,
                                       FILE_SHARE_WRITE,
                                       NULL, OPEN_EXISTING, 0, 0);
    for (;;) {
        // read from redirected stdout
        bSuccess = ReadFile(m_hReadPipe, chBuf, BUFSIZE, &dwRead, NULL);
        if (!bSuccess || (dwRead == 0))
            break;

        // write to console
        WriteFile(hConsoleStdOut, chBuf, dwRead, &dwWritten, NULL);
        // also write to original stdout
        if (m_stdoutfd>=0) {
            _write(m_stdoutfd, chBuf, dwRead);
            // _commit() causes assert if m_stdoutfd is a device (e.g., console or NUL).
            if (!_isatty(m_stdoutfd))
                _commit(m_stdoutfd);
        }
    }
    CloseHandle(hConsoleStdOut);
    return 0;
}

ConsoleEcho::ConsoleEcho()
{
    // setup console
    AllocConsole();
    // create pipe
    CreatePipe(&m_hReadPipe, &m_hWritePipe, NULL, 0);
    // save original stdout to preserve commandline-specified redirection
    m_stdoutfd = _fileno(stdout);
    // and copy the whole damn FILE structure so we can restore it
    // when we're done.  I don't know any other way to restore the
    // crazy windows gui default '-2' filedesc stdout.
    m_originalStdout = *stdout;
    // hook up the write end of our pipe to stdout
    m_pipefd = _open_osfhandle((intptr_t)m_hWritePipe, 0);
    // take our os file handle and allocate a crt FILE for it
    FILE* fp = _fdopen(m_pipefd, "w");
    // copy to stdout
    *stdout = *fp;
    // fp leaks, but we can't close it without closing the OS file handle

    // disable buffering
    setvbuf(stdout, NULL, _IONBF, 0);

    // Create a thread to process our pipe, forwarding output
    // to both the console and the original stdout
    m_hThread = CreateThread(NULL, 0, &ThreadFunc, this, 0, NULL);
}

ConsoleEcho::~ConsoleEcho()
{
    // fclose() unfortunately immediately invalidates the read pipe before the
    // pipe thread has a chance to flush it, so don't do that.
    //fclose(stdout);

    // instead, just slam the original stdout
    *stdout = m_originalStdout;
    //printf("Safe to printf now and no longer echoed to console.\n");
    // Close write pipe
    _close(m_pipefd);
    // and wait here for pipe thread to exit
    WaitForSingleObject(m_hThread, 1000);
    // now close read pipe as well
    CloseHandle(m_hReadPipe);
}
