class ConStream : public st::Stream
{
public:
    ConStream();
    ~ConStream() override;
    bool open(
            const std::wstring& aFname,
            const std::wstring& aCmdLine,
            const std::wstring& aLog);
    // Console does not have remainder.
    st::Pos remainder() override { return 0; }
    // Only when we bumped into end.
    size_t eof() override { return fIsEof; }
    size_t readNe (void* aBuf, size_t aSize) override;
    size_t writeNe(const void* aBuf, size_t aSize) override;
    void flush() override;
    bool isOpen() override { return fIsOpen; }
    bool readln(std::string& s);
    void endInput();
    // Shall we implement close?
    //void close() override {}
    // Implemented capabilities
    unsigned long caps() const override
        { return st::cRead | st::cWrite | st::cMultiplex | st::cRealtime
            | st::cSizeUnaware; }
private:
    st::AsyncFile fLog;
    bool fIsOpen = false;
    bool fIsEof = false;
    bool fFoundCr = false;
    STARTUPINFO si;
    PROCESS_INFORMATION pi;
    HANDLE hStdOutInside, hStdOutOutside;
    HANDLE hStdInInside, hStdInOutside;
    //HANDLE hStdErrInside, hStdErrOutside;
    //bool readln(std::string& s);
};
ConStream::ConStream()
{
    SECURITY_ATTRIBUTES sa;
      // Security attributes
    sa.nLength = sizeof(SECURITY_ATTRIBUTES);
    sa.lpSecurityDescriptor = nullptr;
    sa.bInheritHandle = true;  // Help says the handle will be inherited
    SECURITY_DESCRIPTOR sd;
    if (isWinNT()) {       // security initialization for Windows NT
        InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION);
        SetSecurityDescriptorDacl(&sd, true, nullptr, false);
        sa.lpSecurityDescriptor = &sd;
    }
    // Stdout pipe
    // read = outside, write = inside
    CreatePipe(&hStdOutOutside, &hStdOutInside, &sa, 0);
    SetHandleInformation(hStdOutOutside, HANDLE_FLAG_INHERIT, 0);
    // Stderr pipe
    // read = outside, write = inside
    //CreatePipe(&hStdErrOutside, &hStdErrInside, &sa, 1024*64);  // enough for stderr?
    //SetHandleInformation(hStdErrOutside, HANDLE_FLAG_INHERIT, 0);
    // Stdin pipe
    // read = inside, write = outside
    CreatePipe(&hStdInInside, &hStdInOutside, &sa, 1024*10);
    SetHandleInformation(hStdInOutside, HANDLE_FLAG_INHERIT, 0);
}
void ConStream::endInput()
{
    if (hStdInOutside != INVALID_HANDLE_VALUE) {
        CloseHandle(hStdInOutside);
        hStdInOutside = INVALID_HANDLE_VALUE;
    }
}
ConStream::~ConStream()
{
    if (fIsOpen) {
        if (WaitForSingleObject(pi.hProcess, 2000) == WAIT_TIMEOUT) {
            TerminateProcess(pi.hProcess, 0);
        }
        CloseHandle(pi.hThread);
        CloseHandle(pi.hProcess);
    }
    CloseHandle(hStdInInside);
    endInput();
    //CloseHandle(hStdErrOutside);
    //CloseHandle(hStdErrInside);
    CloseHandle(hStdOutOutside);
    CloseHandle(hStdOutInside);
}
bool ConStream::open(
        const std::wstring& aFname,
        const std::wstring& aCmdLine,
        const std::wstring& aLog)
{
    if (fIsOpen)
        return false;
    // Fill startup info
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);
    size_t n = aFname.length() + aCmdLine.length() + 10;
    si.dwFlags |= STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
    si.hStdOutput = hStdOutInside;
    si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
    si.hStdInput = hStdInInside;
    si.wShowWindow = SW_HIDE;
    std::wstring ws;
    ws.reserve(n);
    ws += L'"';
    ws += aFname;
    ws += L"\" ";
    ws += aCmdLine;
    fIsOpen = CreateProcess(
            nullptr,        // lpApplicationName
            &*ws.begin(),   // lpCommandLine
            nullptr,        // lpProcessAttributes
            nullptr,        // lpThreadAttributes
            TRUE,           // bInheritHandles
            0,              // dwCreationFlags
            nullptr,        // lpEnvironment
            nullptr,        // lpCurrentDirectory
            &si,            // lpStartupInfo
            &pi );          // lpProcessInformation
    if (fIsOpen && !aLog.empty())
        fLog.open(aLog.c_str(), st::File::omNew);
    return fIsOpen;
}
void ConStream::flush()
{
}
size_t ConStream::readNe (void* aBuf, size_t aSize)
{
    if (!fIsOpen)
        return 0;
    DWORD nRead;
    if (!ReadFile(hStdOutOutside, aBuf, static_cast<DWORD>(aSize), &nRead, nullptr))
        return 0;
    if (fLog.isOpen())
        fLog.write(aBuf, nRead);
    return nRead;
}
// Implemented write
size_t ConStream::writeNe(const void* aBuf, size_t aSize)
{
    if (!fIsOpen)
        return 0;
    DWORD nWritten;
    if (!WriteFile(hStdInOutside, aBuf, static_cast<DWORD>(aSize), &nWritten, nullptr))
        return 0;
    return nWritten;
}
bool ConStream::readln(std::string& s)
{
    if (!fIsOpen)
        return false;
    s.clear();
    enum { SZ = 256, TIME = 30 };
    char buf[SZ + 2];
    DWORD nRead, nm1, nm2;
    while (true) {
        if (!PeekNamedPipe(hStdOutOutside, buf, SZ, &nRead, &nm1, &nm2))
            throwWinError();
        // Read nothing — maybe, the program has finished?
        if (nRead == 0) {
            if (WaitForSingleObject(pi.hProcess, TIME) == WAIT_OBJECT_0) {
                // Try again
                if (!PeekNamedPipe(hStdOutOutside, buf, SZ, &nRead, &nm1, &nm2))
                    throwWinError();
                if (nRead == 0)
                    return !s.empty();
                // otherwise fall out
            }
        }
        const char* start = buf;
        const char* end = buf + nRead;
        if (fFoundCr && *start == '\n') {
            ++start;
            fFoundCr = false;
        }
        for (const char* p = start; p != end; ++p) {
            switch (*p) {
            case '\r':
                fFoundCr = true;
                // fall through
            case '\n':
                s.append(start, p);
                // Read until CR
                ++p;
                skipRead(p - buf);
                return true;
            default: ;
            }
        }
        // Copy the entire buffer and go on
        s.append(start, end);
        skipRead(end - buf);
    }
}