/*
 * File: echoinputstreambuf.h
 * --------------------------
 * This file defines the <code>EchoInputStreambuf</code> class, which
 * represents a stream buffer that echoes to stdout any user input.
 * We mostly use this to display console input when redirecting in from a file.
 *
 * @version 2016/10/30
 * - added constructor that takes a string of input
 * @version 2016/10/04
 * - initial version
 */

#ifndef _echoinputstreambuf_h
#define _echoinputstreambuf_h

#include <iostream>
#include <sstream>
#include <streambuf>

namespace stanfordcpplib {

/*
 * An input stream buffer that echoes characters as they are read.
 * Used to echo cin console input for capturing for test case diffing
 * when fixed input has been fed to cin by autograders.
 *
 * inspired by: http://gabisoft.free.fr/articles/fltrsbf1.html
 */
class EchoInputStreambuf : public std::streambuf {
public:
    EchoInputStreambuf(std::streambuf* source) : m_source(source), m_buffer('\0') {
        // empty
    }

    EchoInputStreambuf(const std::string& input) : m_buffer('\0') {
        std::istringstream* istream = new std::istringstream(input);
        m_source = istream->rdbuf();
    }

    virtual ~EchoInputStreambuf() {
        sync();
    }

    virtual int overflow(int) {
        return EOF;
    }

    /*
     * This is the crucial function; called to read a character from the
     * underlying stream buffer (cin).  We capture it in a one-char m_buffer
     * so we can return it later.
     */
    virtual int underflow() {
        int result(EOF);
        if (gptr() < egptr()) {
            result = *gptr();
        } else {
            result = m_source->sbumpc();
            if (result != EOF) {
                m_buffer = result;
                setg(&m_buffer, &m_buffer, &m_buffer + 1);

                // echo the character to stdout
                std::cout.put(result);
                std::cout.flush();
            }
        }
        return result;
    }

    virtual int sync() {
        int result(0);
        if (gptr() < egptr()) {
            result = m_source->sputbackc(*gptr());
            setg(nullptr, nullptr, nullptr);
        }
        if (m_source->pubsync() == EOF) {
            result = EOF;
        }
        return result;
    }

    virtual std::streambuf* setbuf(char* p, std::streamsize len) {
        return m_source->pubsetbuf(p, len);
    }

private:
    std::streambuf* m_source;
    char m_buffer;
};

} // namespace stanfordcpplib

#endif // _echoinputstreambuf_h
