/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2016 Univ. Grenoble Alpes, CNRS, TIMC-IMAG UMR 5525 (GMCAO)
 *
 * Visit http://camitk.imag.fr for more information
 *
 * This file is part of CamiTK.
 *
 * CamiTK is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License version 3
 * only, as published by the Free Software Foundation.
 *
 * CamiTK is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License version 3 for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * version 3 along with CamiTK.  If not, see <http://www.gnu.org/licenses/>.
 *
 * $CAMITK_LICENCE_END$
 ****************************************************************************/

#ifndef ConsoleStream_H
#define ConsoleStream_H

// -- stl stuff
#include <iostream>
#include <streambuf>
#include <string>

// -- QT stuff
#include <QTextEdit>

namespace camitk {
/**
 *
 * @ingroup group_sdk_libraries_core_utils
 *
 * @brief
 * Provides a console windows, within the CamiTK application.
 *
 * \image html libraries/console.png "The console widget."
 *
 *
Usage:
\code
#include <ConsoleStream.h>

...

// create your application
QApplication app(argc, argv);

// these redirect both cout/cerr
ConsoleStream cout(std::cout);
ConsoleStream cerr(std::cerr);

// now start using cout and cerr normally

std::cerr << "Oops"; // this goes to your debugger output
\endcode

Potential problem on windows (see thread)
- std::string::clear() and std::string::push_back(...) don't exist, but myString.clear() can be substituted by myString.erase(myString.begin(), myString.end()) and myString.push_back(v) can be replaced by myString += v.
- The usage of int_type seems to require a using std::ios::int_type statement.
*/

class ConsoleStream : public std::basic_streambuf<char> {
public:
    /// constructor to use when you are sure about both paramaters
    ConsoleStream(std::ostream *stream, QTextEdit* textEdit) {
        init(stream, textEdit);
    }

    /// default constructor, init(..) have to be called later, before first use
    ConsoleStream() {
        previousBuffer = NULL;
        logTextEdit = NULL;
        myStream = NULL;
    }

    /// destructor: use free() to restore previous stream output buffer
    ~ConsoleStream() {
        // output anything that is left
        if (!myString.empty())
            logTextEdit->append(myString.c_str());

        free();
    }

    /// set the value for the buffer to be replaced by the ConsoleStream
    void setStream(std::ostream *stream) {
        free();
        myStream = stream;
        previousBuffer = stream->rdbuf();
        stream->rdbuf(this);
    }

    /// set the log QTextEdit
    void setTextEdit(QTextEdit* text_edit) {
        logTextEdit = text_edit;
    }

    /// initialize ConsoleStream using both input stream and output text edit
    void init(std::ostream *stream, QTextEdit* textEdit) {
        setTextEdit(textEdit);
        setStream(stream);
    }

    /// reset the state as it was before (stream use the old buffer again)
    void free() {
        if (previousBuffer != NULL && myStream != NULL)
            myStream->rdbuf(previousBuffer);
    }
protected:
    /// rewriting of the inherited method overflow
    virtual int_type overflow(int_type v) {
        if (v == '\n') {
            logTextEdit->append(myString.c_str());
            myString.erase(myString.begin(), myString.end());
        } else
            myString += v;

        return v;
    }

    /// rewriting of the inherited method xsputn
    virtual std::streamsize xsputn(const char *p, std::streamsize n) {
        myString.append(p, p + n);

        std::string::size_type pos = 0;
        while (pos != std::string::npos) {
            pos = myString.find('\n');
            if (pos != std::string::npos) {
                std::string tmp(myString.begin(), myString.begin() + pos);
                logTextEdit->append(tmp.c_str());
                myString.erase(myString.begin(), myString.begin() + pos + 1);
            }
        }

        return n;
    }

private:
    std::ostream *myStream;
    std::streambuf *previousBuffer;
    std::string myString;
    QTextEdit* logTextEdit;
};

}



#endif
