/***************************************************************************
 *   Copyright (c) 2004 Werner Mayer <wmayer[at]users.sourceforge.net>     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/


#include "PreCompiled.h"
#ifndef _PreComp_
# include <QGridLayout>
# include <QApplication>
# include <QMenu>
# include <QContextMenuEvent>
# include <QTextCursor>
# include <QTextStream>
#endif

#include <Base/Interpreter.h>
#include "ReportView.h"
#include "FileDialog.h"
#include "PythonConsole.h"
#include "PythonConsolePy.h"
#include "BitmapFactory.h"

using namespace Gui;
using namespace Gui::DockWnd;

/* TRANSLATOR Gui::DockWnd::ReportView */

/**
 *  Constructs a ReportView which is a child of 'parent', with the 
 *  name 'name' and widget flags set to 'f' 
 */
ReportView::ReportView( QWidget* parent )
  : QWidget(parent)
{
    setObjectName(QLatin1String("ReportOutput"));

    resize( 529, 162 );
    QGridLayout* tabLayout = new QGridLayout( this );
    tabLayout->setSpacing( 0 );
    tabLayout->setMargin( 0 );

    tabWidget = new QTabWidget( this );
    tabWidget->setObjectName(QString::fromUtf8("tabWidget"));
    tabWidget->setTabPosition(QTabWidget::South);
    tabWidget->setTabShape(QTabWidget::Rounded);
    tabLayout->addWidget( tabWidget, 0, 0 );


    // create the output window
    tabOutput = new ReportOutput();
    tabOutput->setWindowTitle(trUtf8("Output"));
    tabOutput->setWindowIcon(BitmapFactory().pixmap("MacroEditor"));
    int output = tabWidget->addTab(tabOutput, tabOutput->windowTitle());
    tabWidget->setTabIcon(output, tabOutput->windowIcon());

    // create the python console
    tabPython = new PythonConsole();
    tabPython->setWordWrapMode(QTextOption::NoWrap);
    tabPython->setWindowTitle(trUtf8("Python console"));
    tabPython->setWindowIcon(BitmapFactory().pixmap("applications-python"));
    int python = tabWidget->addTab(tabPython, tabPython->windowTitle());
    tabWidget->setTabIcon(python, tabPython->windowIcon());
    tabWidget->setCurrentIndex(0);

    // raise the tab page set in the preferences
    ParameterGrp::handle hGrp = WindowParameter::getDefaultParameter()->GetGroup("General");
    int index = hGrp->GetInt("AutoloadTab", 0);
    tabWidget->setCurrentIndex(index);
}

/**
 *  Destroys the object and frees any allocated resources
 */
ReportView::~ReportView()
{
    // no need to delete child widgets, Qt does it all for us
}

void ReportView::changeEvent(QEvent *e)
{
    QWidget::changeEvent(e);
    if (e->type() == QEvent::LanguageChange) {
        tabOutput->setWindowTitle(trUtf8("Output"));
        tabPython->setWindowTitle(trUtf8("Python console"));
        for (int i=0; i<tabWidget->count();i++)
            tabWidget->setTabText(i, tabWidget->widget(i)->windowTitle());
    }
}

// ----------------------------------------------------------

namespace Gui {
struct TextBlockData : public QTextBlockUserData
{
    struct State {
        int length;
        ReportHighlighter::Paragraph type;
    };
    QVector<State> block;
};
}

ReportHighlighter::ReportHighlighter(QTextEdit* edit)
  : QSyntaxHighlighter(edit), type(Message)
{
    QPalette pal = edit->palette();
    txtCol = pal.windowText().color();
    logCol = Qt::blue;
    warnCol = QColor(255, 170, 0);
    errCol = Qt::red;
}

ReportHighlighter::~ReportHighlighter()
{
}

void ReportHighlighter::highlightBlock (const QString & text)
{
    if (text.isEmpty())
        return;
    TextBlockData* ud = static_cast<TextBlockData*>(this->currentBlockUserData());
    if (!ud) {
        ud = new TextBlockData;
        this->setCurrentBlockUserData(ud);
    }

    TextBlockData::State b;
    b.length = text.length();
    b.type = this->type;
    ud->block.append(b);

    QVector<TextBlockData::State> block = ud->block;
    int start = 0;
    for (QVector<TextBlockData::State>::Iterator it = block.begin(); it != block.end(); ++it) {
        switch (it->type)
        {
        case Message:
            setFormat(start, it->length-start, txtCol);
            break;
        case Warning:
            setFormat(start, it->length-start, warnCol);
            break;
        case Error:
            setFormat(start, it->length-start, errCol);
            break;
        case LogText:
            setFormat(start, it->length-start, logCol);
            break;
        default:
            break;
        }

        start = it->length;
    }
}

void ReportHighlighter::setParagraphType(ReportHighlighter::Paragraph t)
{
    type = t;
}

void ReportHighlighter::setTextColor( const QColor& col )
{
    txtCol = col;
}

void ReportHighlighter::setLogColor( const QColor& col )
{
    logCol = col;
}

void ReportHighlighter::setWarningColor( const QColor& col )
{
    warnCol = col;
}

void ReportHighlighter::setErrorColor( const QColor& col )
{
    errCol = col;
}

// ----------------------------------------------------------

/**
 * The CustomReportEvent class is used to send report events in the methods Log(), 
 * Error(), Warning() and Message() of the ReportOutput class to itself instead of
 * printing the messages directly in its text view.
 *
 * This makes the methods Log(), Error(), Warning() and Message() thread-safe.
 * @author Werner Mayer
 */
class CustomReportEvent : public QEvent
{
public:
    CustomReportEvent(ReportHighlighter::Paragraph p, const QString& s)
    : QEvent(QEvent::Type(QEvent::User)) 
    { par = p; msg = s;}
    ~CustomReportEvent()
    { }
    const QString& message() const
    { return msg; }
    ReportHighlighter::Paragraph messageType() const
    { return par; }
private:
    ReportHighlighter::Paragraph par;
    QString msg;
};

// ----------------------------------------------------------

class ReportOutput::Data
{
public:
    Data()
    {
        if (!default_stdout) {
            Base::PyGILStateLocker lock;
            default_stdout = PySys_GetObject(const_cast<char*>("stdout"));
            replace_stdout = new OutputStdout();
            redirected_stdout = false;
        }

        if (!default_stderr) {
            Base::PyGILStateLocker lock;
            default_stderr = PySys_GetObject(const_cast<char*>("stderr"));
            replace_stderr = new OutputStderr();
            redirected_stderr = false;
        }
    }
    ~Data()
    {
        if (replace_stdout) {
            Py_DECREF(replace_stdout);
            replace_stdout = 0;
        }

        if (replace_stderr) {
            Py_DECREF(replace_stderr);
            replace_stderr = 0;
        }
    }

    // make them static because redirection should done only once
    static bool redirected_stdout;
    static PyObject* default_stdout;
    static PyObject* replace_stdout;

    static bool redirected_stderr;
    static PyObject* default_stderr;
    static PyObject* replace_stderr;
};

bool ReportOutput::Data::redirected_stdout = false;
PyObject* ReportOutput::Data::default_stdout = 0;
PyObject* ReportOutput::Data::replace_stdout = 0;

bool ReportOutput::Data::redirected_stderr = false;
PyObject* ReportOutput::Data::default_stderr = 0;
PyObject* ReportOutput::Data::replace_stderr = 0;

/* TRANSLATOR Gui::DockWnd::ReportOutput */

/**
 *  Constructs a ReportOutput which is a child of 'parent', with the 
 *  name 'name' and widget flags set to 'f' 
 */
ReportOutput::ReportOutput(QWidget* parent)
  : QTextEdit(parent), WindowParameter("OutputWindow"), d(new Data), gotoEnd(false)
{
    bLog = false;
    reportHl = new ReportHighlighter(this);

    restoreFont();
    setReadOnly(true);
    clear();
    setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);

    Base::Console().AttachObserver(this);
    getWindowParameter()->Attach(this);

    getWindowParameter()->NotifyAll();
    _prefs = WindowParameter::getDefaultParameter()->GetGroup("Editor");
    _prefs->Attach(this);
    _prefs->Notify("FontSize");

    // scroll to bottom at startup to make sure that last appended text is visible
    ensureCursorVisible();
}

/**
 *  Destroys the object and frees any allocated resources
 */
ReportOutput::~ReportOutput()
{
    getWindowParameter()->Detach(this);
    _prefs->Detach(this);
    Base::Console().DetachObserver(this);
    delete reportHl;
    delete d;
}

void ReportOutput::restoreFont()
{
    QFont serifFont(QLatin1String("Courier"), 10, QFont::Normal);
    setFont(serifFont);
}

void ReportOutput::Warning(const char * s)
{
    // Send the event to itself to allow thread-safety. Qt will delete it when done.
    CustomReportEvent* ev = new CustomReportEvent(ReportHighlighter::Warning, QString::fromUtf8(s));
    QApplication::postEvent(this, ev);
}

void ReportOutput::Message(const char * s)
{
    // Send the event to itself to allow thread-safety. Qt will delete it when done.
    CustomReportEvent* ev = new CustomReportEvent(ReportHighlighter::Message, QString::fromUtf8(s));
    QApplication::postEvent(this, ev);
}

void ReportOutput::Error  (const char * s)
{
    // Send the event to itself to allow thread-safety. Qt will delete it when done.
    CustomReportEvent* ev = new CustomReportEvent(ReportHighlighter::Error, QString::fromUtf8(s));
    QApplication::postEvent(this, ev);
}

void ReportOutput::Log (const char * s)
{
    QString msg = QString::fromUtf8(s);
    if (msg.length() < 1000){
        // Send the event to itself to allow thread-safety. Qt will delete it when done.
        CustomReportEvent* ev = new CustomReportEvent(ReportHighlighter::LogText, msg);
        QApplication::postEvent(this, ev);
    }
}

void ReportOutput::customEvent ( QEvent* ev )
{
    // Appends the text stored in the event to the text view
    if ( ev->type() ==  QEvent::User ) {
        CustomReportEvent* ce = (CustomReportEvent*)ev;
        reportHl->setParagraphType(ce->messageType());

        QTextCursor cursor(this->document());
        cursor.beginEditBlock();
        cursor.movePosition(QTextCursor::End);
        cursor.insertText(ce->message());
        cursor.endEditBlock();
        if (gotoEnd) {
            setTextCursor(cursor);
        }
        ensureCursorVisible();
    }
}

void ReportOutput::changeEvent(QEvent *ev)
{
    if (ev->type() == QEvent::StyleChange) {
        QPalette pal = palette();
        QColor color = pal.windowText().color();
        unsigned long text = (color.red() << 24) | (color.green() << 16) | (color.blue() << 8);
        // if this parameter is not already set use the style's window text color
        text = getWindowParameter()->GetUnsigned("colorText", text);
        getWindowParameter()->SetUnsigned("colorText", text);
    }
    QTextEdit::changeEvent(ev);
}

void ReportOutput::contextMenuEvent ( QContextMenuEvent * e )
{
    QMenu* menu = createStandardContextMenu();
    QAction* first = menu->actions().front();

    QMenu* submenu = new QMenu( menu );
    QAction* logAct = submenu->addAction(tr("Logging"), this, SLOT(onToggleLogging()));
    logAct->setCheckable(true);
    logAct->setChecked(bLog);

    QAction* wrnAct = submenu->addAction(tr("Warning"), this, SLOT(onToggleWarning()));
    wrnAct->setCheckable(true);
    wrnAct->setChecked(bWrn);

    QAction* errAct = submenu->addAction(tr("Error"), this, SLOT(onToggleError()));
    errAct->setCheckable(true);
    errAct->setChecked(bErr);

    submenu->addSeparator();

    QAction* stdoutAct = submenu->addAction(tr("Redirect Python output"), this, SLOT(onToggleRedirectPythonStdout()));
    stdoutAct->setCheckable(true);
    stdoutAct->setChecked(d->redirected_stdout);

    QAction* stderrAct = submenu->addAction(tr("Redirect Python errors"), this, SLOT(onToggleRedirectPythonStderr()));
    stderrAct->setCheckable(true);
    stderrAct->setChecked(d->redirected_stderr);

    submenu->addSeparator();
    QAction* botAct = submenu->addAction(tr("Go to end"), this, SLOT(onToggleGoToEnd()));
    botAct->setCheckable(true);
    botAct->setChecked(gotoEnd);

    submenu->setTitle(tr("Options"));
    menu->insertMenu(first, submenu);
    menu->insertSeparator(first);

    menu->addAction(tr("Clear"), this, SLOT(clear()));
    menu->addSeparator();
    menu->addAction(tr("Save As..."), this, SLOT(onSaveAs()));

    menu->exec(e->globalPos());
    delete menu;
}

void ReportOutput::onSaveAs()
{
    QString fn = QFileDialog::getSaveFileName(this, tr("Save Report Output"), QString(), tr("Plain Text Files (*.txt *.log)"));
    if (!fn.isEmpty()) {
        QFileInfo fi(fn);
        if (fi.completeSuffix().isEmpty())
            fn += QLatin1String(".log");
        QFile f(fn);
        if (f.open(QIODevice::WriteOnly)) {
            QTextStream t (&f);
            t << toPlainText();
            f.close();
        }
    }
}

bool ReportOutput::isError() const
{
    return bErr;
}

bool ReportOutput::isWarning() const
{
    return bWrn;
}

bool ReportOutput::isLogging() const
{
    return bLog;
}

void ReportOutput::onToggleError()
{
    bErr = bErr ? false : true;
    getWindowParameter()->SetBool( "checkError", bErr );
}

void ReportOutput::onToggleWarning()
{
    bWrn = bWrn ? false : true;
    getWindowParameter()->SetBool( "checkWarning", bWrn );
}

void ReportOutput::onToggleLogging()
{
    bLog = bLog ? false : true;
    getWindowParameter()->SetBool( "checkLogging", bLog );
}

void ReportOutput::onToggleRedirectPythonStdout()
{
    if (d->redirected_stdout) {
        d->redirected_stdout = false;
        Base::PyGILStateLocker lock;
        PySys_SetObject(const_cast<char*>("stdout"), d->default_stdout);
    }
    else {
        d->redirected_stdout = true;
        Base::PyGILStateLocker lock;
        PySys_SetObject(const_cast<char*>("stdout"), d->replace_stdout);
    }

    getWindowParameter()->SetBool("RedirectPythonOutput", d->redirected_stdout);
}

void ReportOutput::onToggleRedirectPythonStderr()
{
    if (d->redirected_stderr) {
        d->redirected_stderr = false;
        Base::PyGILStateLocker lock;
        PySys_SetObject(const_cast<char*>("stderr"), d->default_stderr);
    }
    else {
        d->redirected_stderr = true;
        Base::PyGILStateLocker lock;
        PySys_SetObject(const_cast<char*>("stderr"), d->replace_stderr);
    }

    getWindowParameter()->SetBool("RedirectPythonErrors", d->redirected_stderr);
}

void ReportOutput::onToggleGoToEnd()
{
    gotoEnd = gotoEnd ? false : true;
    getWindowParameter()->SetBool( "checkGoToEnd", gotoEnd );
}

void ReportOutput::OnChange(Base::Subject<const char*> &rCaller, const char * sReason)
{
    ParameterGrp& rclGrp = ((ParameterGrp&)rCaller);
    if (strcmp(sReason, "checkLogging") == 0) {
        bLog = rclGrp.GetBool( sReason, bLog );
    }
    else if (strcmp(sReason, "checkWarning") == 0) {
        bWrn = rclGrp.GetBool( sReason, bWrn );
    }
    else if (strcmp(sReason, "checkError") == 0) {
        bErr = rclGrp.GetBool( sReason, bErr );
    }
    else if (strcmp(sReason, "colorText") == 0) {
        unsigned long col = rclGrp.GetUnsigned( sReason );
        reportHl->setTextColor( QColor( (col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff) );
    }
    else if (strcmp(sReason, "colorLogging") == 0) {
        unsigned long col = rclGrp.GetUnsigned( sReason );
        reportHl->setLogColor( QColor( (col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff) );
    }
    else if (strcmp(sReason, "colorWarning") == 0) {
        unsigned long col = rclGrp.GetUnsigned( sReason );
        reportHl->setWarningColor( QColor( (col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff) );
    }
    else if (strcmp(sReason, "colorError") == 0) {
        unsigned long col = rclGrp.GetUnsigned( sReason );
        reportHl->setErrorColor( QColor( (col >> 24) & 0xff,(col >> 16) & 0xff,(col >> 8) & 0xff) );
    }
    else if (strcmp(sReason, "checkGoToEnd") == 0) {
        gotoEnd = rclGrp.GetBool(sReason, gotoEnd);
    }
    else if (strcmp(sReason, "FontSize") == 0 || strcmp(sReason, "Font") == 0) {
        int fontSize = rclGrp.GetInt("FontSize", 10);
        QString fontFamily = QString::fromAscii(rclGrp.GetASCII("Font", "Courier").c_str());
        
        QFont font(fontFamily, fontSize);
        setFont(font);
        QFontMetrics metric(font);
        int width = metric.width(QLatin1String("0000"));
        setTabStopWidth(width);
    }
    else if (strcmp(sReason, "RedirectPythonOutput") == 0) {
        bool checked = rclGrp.GetBool(sReason);
        if (checked != d->redirected_stdout)
            onToggleRedirectPythonStdout();
    }
    else if (strcmp(sReason, "RedirectPythonErrors") == 0) {
        bool checked = rclGrp.GetBool(sReason);
        if (checked != d->redirected_stderr)
            onToggleRedirectPythonStderr();
    }
}

#include "moc_ReportView.cpp"
