/*****************************************************************************
 * $CAMITK_LICENCE_BEGIN$
 *
 * CamiTK - Computer Assisted Medical Intervention ToolKit
 * (c) 2001-2025 Univ. Grenoble Alpes, CNRS, Grenoble INP - UGA, TIMC, 38000 Grenoble, France
 *
 * 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>
#include <mutex>

// -- QT stuff
#include <QObject>
#include <QTextEdit>
#include <QMetaObject>

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 QObject, public std::basic_streambuf<char> {
public:
    /// default constructor, init(..) have to be called later, before first use
    ConsoleStream(QObject *parent = nullptr)
        : QObject(parent),
          myStream(nullptr),
          previousBuffer(nullptr),
          logTextEdit(nullptr) {}

    /// destructor: use free() to restore previous stream output buffer
    ~ConsoleStream() override {
        flushPending();
        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* textEdit) {
        logTextEdit = textEdit;
    }

    /// 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 != nullptr && myStream != nullptr) {
            myStream->rdbuf(previousBuffer);
            myStream = nullptr;
            previousBuffer = nullptr;
        }
    }

signals:
    /// use a signal to print safely even if cout/cerr are written from other thread
    void appendText(QString text);

protected:

    /// rewriting of the inherited method overflow
    int_type overflow(int_type v) override {
        if (v == '\n') {
            flushPending();
        }
        else {
            buffer += static_cast<char>(v);
        }
        return v;
    }

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

        std::string::size_type pos = 0;
        while ((pos = buffer.find('\n')) != std::string::npos) {
            std::string line = buffer.substr(0, pos);
            buffer.erase(0, pos + 1);
            emitText(QString::fromLocal8Bit(line.c_str()));
        }

        return n;
    }

private:
    void emitText(const QString &text) {
        if (!logTextEdit)
            return;

        // emit across threads safely
        QMetaObject::invokeMethod(logTextEdit, [text, this]() {
            logTextEdit->moveCursor(QTextCursor::End);
            logTextEdit->insertPlainText(text + "\n");
            logTextEdit->moveCursor(QTextCursor::End);
        }, Qt::QueuedConnection);
    }

    void flushPending() {
        if (!buffer.empty()) {
            emitText(QString::fromLocal8Bit(buffer.c_str()));
            buffer.clear();
        }
    }

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

}



#endif
