#include "QConsoleWidget.h"
#include "QConsoleIODevice.h"
#include <QMenu>
#include <QMouseEvent>
#include <QKeyEvent>
#include <QApplication>
#include <QTextDocumentFragment>
#include <QTextBlock>
#include <QTextCursor>
#include <QDebug>
#include <QStringListModel>
#include <QScrollBar>
#include <QAbstractItemView>
#include <QClipboard>
#include <QMimeData>



void QConsoleWidget::setModelProcess(ModelProcess* app)
{
    this->application = app;
    // 创建dockerWidget 
    qDebug() << u8"initCommandDocker,等待初始化命令行窗口";
    QDockWidget* dockWidget_command = this->application->getdockWidget_command(u8"命令行窗口");
    dockWidget_command->setHidden(false);
    dockWidget_command->setWidget(this);
    this->qs = new ScriptSession(this); // 创建脚本
    QTimer::singleShot(200,qs, SLOT(REPL()));

    // 绑定application
   //this->qs->settingApplication()
    this->qs->settingApplication(this->application);
    qDebug() << u8"initCommandDocker,结束初始化命令行窗口";
}

QConsoleWidget::QConsoleWidget(QWidget* parent)
    : QPlainTextEdit(parent), mode_(Output), completer_(0)
{
    iodevice_ = new QConsoleIODevice(this,this);

    QTextCharFormat fmt = currentCharFormat();
    for(int i=0; i<nConsoleChannels; i++)
        chanFormat_[i] = fmt;

    chanFormat_[StandardOutput].setForeground(Qt::darkBlue);
    chanFormat_[StandardError].setForeground(Qt::red);

    setTextInteractionFlags(Qt::TextEditorInteraction);
    setUndoRedoEnabled(false);
}

QConsoleWidget::~QConsoleWidget()
{
}

void QConsoleWidget::setMode(ConsoleMode m)
{
    if (m==mode_) return;

    if (m==Input) {
        QTextCursor cursor = textCursor();
        cursor.movePosition(QTextCursor::End);
        setTextCursor(cursor);
        setCurrentCharFormat(chanFormat_[StandardInput]);
        inpos_ = cursor.position();
        mode_ = Input;
    }

    if (m==Output) {
        mode_ = Output;
    }
}

QString QConsoleWidget::getCommandLine()
{
    if (mode_==Output) return QString();
    // select text in edit zone (from the input pos to the end)
    QTextCursor textCursor = this->textCursor();
    textCursor.movePosition(QTextCursor::End);
    textCursor.setPosition(inpos_,QTextCursor::KeepAnchor);
    QString code = textCursor.selectedText();
    code.replace(QChar::ParagraphSeparator,QChar::LineFeed);
    return code;
}

void QConsoleWidget::handleReturnKey()
{
    QString code = getCommandLine();

    // start new block
    appendPlainText(QString());
    setMode(Output);

    QTextCursor textCursor = this->textCursor();
    textCursor.movePosition(QTextCursor::End);
    setTextCursor(textCursor);

    // Update the history
    if (!code.isEmpty()) history_.add(code);

    // append the newline char and
    // send signal / update iodevice
    code += "\n";
    if (iodevice_->isOpen())
        iodevice_->consoleWidgetInput(code);
    else {
        emit consoleCommand(code);
    }
}

void QConsoleWidget::handleTabKey()
{
    QTextCursor tc = this->textCursor();
    int anchor = tc.anchor();
    int position = tc.position();
    tc.setPosition(inpos_);
    tc.setPosition(position, QTextCursor::KeepAnchor);
    QString text = tc.selectedText().trimmed();
    tc.setPosition(anchor, QTextCursor::MoveAnchor);
    tc.setPosition(position, QTextCursor::KeepAnchor);
    if (text.isEmpty())
    {
        tc.insertText("    ");
    }
    else
    {
        updateCompleter();
        if (completer_ && completer_->completionCount() == 1)
        {
            insertCompletion(completer_->currentCompletion());
            completer_->popup()->hide();
        }
    }
}

void QConsoleWidget::updateCompleter()
{
    if (!completer_) return;

    // if the completer is first shown, mark
    // the text position
    QTextCursor textCursor = this->textCursor();
    if (!completer_->popup()->isVisible()) {
        completion_pos_ = textCursor.position();
        // qDebug() << "show completer, pos " << completion_pos_;
    }

    // Get the text between the current cursor position
    // and the start of the input
    textCursor.setPosition(inpos_, QTextCursor::KeepAnchor);
    QString commandText = textCursor.selectedText();
    // qDebug() << "code to complete: " << commandText;

    // Call the completer to update the completion model
    // Place and show the completer if there are available completions
    int count;
    if ((count = completer_->updateCompletionModel(commandText)))
    {
        // qDebug() << "found " << count << " completions";

        // Get a QRect for the cursor at the start of the
        // current word and then translate it down 8 pixels.
        textCursor = this->textCursor();
        textCursor.movePosition(QTextCursor::StartOfWord);
        QRect cr = this->cursorRect(textCursor);
        cr.translate(0, 8);
        cr.setWidth(completer_->popup()->sizeHintForColumn(0) +
                    completer_->popup()->verticalScrollBar()->sizeHint().width());
        completer_->complete(cr);
    }
    else
    {
        // qDebug() << "no completions - hiding";
        completer_->popup()->hide();
    }
}

void QConsoleWidget::checkCompletionTriggers(const QString &txt)
{
    if (!completer_ || completion_triggers_.isEmpty() || txt.isEmpty()) return;

    foreach(const QString& tr, completion_triggers_)
    {
        if (tr.endsWith(txt))
        {
            QTextCursor tc = this->textCursor();
            tc.movePosition(QTextCursor::Left,QTextCursor::KeepAnchor,tr.length());
            if (tc.selectedText()==tr) {
                updateCompleter();
                return;
            }
        }
    }
}

void QConsoleWidget::insertCompletion(const QString& completion)
{    
    QTextCursor tc = textCursor();
    tc.movePosition(QTextCursor::Left, QTextCursor::KeepAnchor,
                    tc.position() - inpos_ - completer_->insertPos());
    tc.insertText(completion,chanFormat_[StandardInput]);
    setTextCursor(tc);
}

void QConsoleWidget::setCompleter(QConsoleWidgetCompleter *c)
{
    if (completer_)
    {
        completer_->setWidget(0);
        QObject::disconnect(completer_, SIGNAL(activated(const QString&)), this,
                            SLOT(insertCompletion(const QString&)));
    }
    completer_ = c;
    if (completer_)
    {
        completer_->setWidget(this);
        QObject::connect(completer_, SIGNAL(activated(const QString&)), this,
                         SLOT(insertCompletion(const QString&)));
    }
}

void QConsoleWidget::keyPressEvent(QKeyEvent* e)
{
    if (completer_ && completer_->popup()->isVisible())
    {
        // The following keys are forwarded by the completer to the widget
        switch (e->key())
        {
        case Qt::Key_Tab:
        case Qt::Key_Enter:
        case Qt::Key_Return:
        case Qt::Key_Escape:
        case Qt::Key_Backtab:
            e->ignore();
            return; // let the completer do default behavior
        default:
            break;
        }
    }

    QTextCursor textCursor   = this->textCursor();
    bool selectionInEditZone = isSelectionInEditZone();

    // check for user abort request
    if (e->modifiers() & Qt::ControlModifier)
    {
        if (e->key()==Qt::Key_Q) // Ctrl-Q aborts
        {
            emit abortEvaluation();
            e->accept();
            return;
        }
    }

    // Allow copying anywhere in the console ...
    if (e->key() == Qt::Key_C && e->modifiers() == Qt::ControlModifier)
    {
        if (textCursor.hasSelection()) copy();
        e->accept();
        return;
    }

    // the rest of key events are ignored during output mode
    if (mode()!=Input) {
        e->ignore();
        return;
    }

    // Allow cut only if the selection is limited to the interactive area ...
    if (e->key() == Qt::Key_X && e->modifiers() == Qt::ControlModifier)
    {
        if (selectionInEditZone) cut();
        e->accept();
        return;
    }

    // Allow paste only if the selection is in the interactive area ...
    if (e->key() == Qt::Key_V && e->modifiers() == Qt::ControlModifier)
    {
        if (selectionInEditZone || isCursorInEditZone())
        {
            const QMimeData* const clipboard = QApplication::clipboard()->mimeData();
            const QString text = clipboard->text();
            if (!text.isNull())
            {
                textCursor.insertText(text,channelCharFormat(StandardInput));
            }
        }

        e->accept();
        return;
    }


    int key = e->key();
    int shiftMod = e->modifiers() == Qt::ShiftModifier;

    if (history_.isActive() && key!=Qt::Key_Up && key!=Qt::Key_Down)
        history_.deactivate();

    // Force the cursor back to the interactive area
    // for all keys except modifiers
    if (!isCursorInEditZone() &&
            key != Qt::Key_Control &&
            key != Qt::Key_Shift &&
            key != Qt::Key_Alt)
    {
        textCursor.movePosition(QTextCursor::End);
        setTextCursor(textCursor);
    }

    switch (key)
    {
    case Qt::Key_Up:
        // Activate the history and move to the 1st matching history item
        if (!history_.isActive()) history_.activate(getCommandLine());
        if (history_.move(true))
            replaceCommandLine(history_.currentValue());
        else QApplication::beep();
        e->accept();
        break;

    case Qt::Key_Down:
        if (history_.move(false))
            replaceCommandLine(history_.currentValue());
        else QApplication::beep();
        e->accept();

    case Qt::Key_Left:
        if (textCursor.position() > inpos_)
            QPlainTextEdit::keyPressEvent(e);
        else
        {
            QApplication::beep();
            e->accept();
        }
        break;

    case Qt::Key_Delete:
        e->accept();
        if (selectionInEditZone) cut();
        else
        {
            // cursor must be in edit zone
            if (textCursor.position() < inpos_) QApplication::beep();
            else QPlainTextEdit::keyPressEvent(e);
        }
        break;

    case Qt::Key_Backspace:
        e->accept();
        if (selectionInEditZone) cut();
        else
        {
            // cursor must be in edit zone
            if (textCursor.position() <= inpos_) QApplication::beep();
            else QPlainTextEdit::keyPressEvent(e);
        }
        break;

    case Qt::Key_Tab:
        e->accept();
        handleTabKey();
        return;

    case Qt::Key_Home:
        e->accept();
        textCursor.setPosition(inpos_,
                               shiftMod ? QTextCursor::KeepAnchor : QTextCursor::MoveAnchor);
        setTextCursor(textCursor);
        break;

    case Qt::Key_Enter:
    case Qt::Key_Return:
        e->accept();
        handleReturnKey();
        break;


    case Qt::Key_Escape:
        e->accept();
        replaceCommandLine(QString());
        break;


    default:
        e->accept();
        setCurrentCharFormat(chanFormat_[StandardInput]);
        QPlainTextEdit::keyPressEvent(e);
        // check if the last key triggers a completion
        checkCompletionTriggers(e->text());
        break;
    }

    if (completer_ && completer_->popup()->isVisible())
    {
        // if the completer is visible check if it should be updated
        if (this->textCursor().position()<completion_pos_)
            completer_->popup()->hide();
        else
            updateCompleter();
    }
}

void QConsoleWidget::contextMenuEvent(QContextMenuEvent *event)
{
    QMenu *menu = createStandardContextMenu();

    QAction* a;
    if ((a = menu->findChild<QAction*>("edit-cut")))
        a->setEnabled(canCut());
    if ((a = menu->findChild<QAction*>("edit-delete")))
        a->setEnabled(canCut());
    if ((a = menu->findChild<QAction*>("edit-paste")))
        a->setEnabled(canPaste());

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

bool QConsoleWidget::isSelectionInEditZone() const
{
    QTextCursor textCursor = this->textCursor();
    if (!textCursor.hasSelection()) return false;

    int selectionStart    = textCursor.selectionStart();
    int selectionEnd      = textCursor.selectionEnd();

    return selectionStart >= inpos_ && selectionEnd >= inpos_;
}

bool QConsoleWidget::isCursorInEditZone() const
{
    return textCursor().position()>=inpos_;
}

bool QConsoleWidget::canPaste() const
{
    QTextCursor textCursor = this->textCursor();
    if (textCursor.position()<inpos_) return false;
    if (textCursor.anchor()<inpos_) return false;
    return true;
}

void QConsoleWidget::replaceCommandLine(const QString& str) {

    // Select the text after the last command prompt ...
    QTextCursor textCursor = this->textCursor();
    textCursor.movePosition(QTextCursor::End);
    textCursor.setPosition(inpos_, QTextCursor::KeepAnchor);

    // ... and replace it with new string.
    textCursor.insertText(str,chanFormat_[StandardInput]);

    // move to the end of the document
    textCursor.movePosition(QTextCursor::End);
    setTextCursor(textCursor);
}

void QConsoleWidget::write(const QString & message, const QTextCharFormat& fmt)
{
    QTextCharFormat currfmt = currentCharFormat();
    QTextCursor tc = textCursor();

    if (mode()==Input)
    {
        // in Input mode output messages are inserted
        // before the edit block

        // get offset of current pos from the end
        int editpos = tc.position();
        tc.movePosition(QTextCursor::End);
        editpos = tc.position() - editpos;

        // convert the input pos as relative from the end
        inpos_ = tc.position() - inpos_;

        // insert block
        tc.movePosition(QTextCursor::StartOfBlock);
        tc.insertBlock();
        tc.movePosition(QTextCursor::PreviousBlock);

        tc.insertText(message,fmt);
        tc.movePosition(QTextCursor::End);
        // restore input pos
        inpos_ = tc.position() - inpos_;
        // restore the edit pos
        tc.movePosition(QTextCursor::Left,QTextCursor::MoveAnchor,editpos);
        setTextCursor(tc);
        setCurrentCharFormat(currfmt);
    }
    else
    {
        // in output mode messages are appended
        QTextCursor tc1 = tc;
        tc1.movePosition(QTextCursor::End);

        // check is cursor was not at the end
        // (e.g. had been moved per mouse action)
        bool needsRestore = tc1.position()!=tc.position();

        // insert text
        setTextCursor(tc1);
        textCursor().insertText(message,fmt);
        ensureCursorVisible();

        // restore cursor if needed
        if (needsRestore) setTextCursor(tc);
    }


}

void QConsoleWidget::writeStdOut(const QString& s)
{
    write(s,chanFormat_[StandardOutput]);
}

void QConsoleWidget::writeStdErr(const QString& s)
{
    write(s,chanFormat_[StandardError]);
}

/////////////////// QConsoleWidget::History /////////////////////

#define HISTORY_FILE ".command_history.lst"

QConsoleWidget::History QConsoleWidget::history_;

QConsoleWidget::History::History(void) : pos_(0), active_(false), maxsize_(10000)
{
    QFile f(HISTORY_FILE);
    if (f.open(QFile::ReadOnly)) {
        QTextStream is(&f);
        while(!is.atEnd()) add(is.readLine());
    }
}
QConsoleWidget::History::~History(void)
{
    QFile f(HISTORY_FILE);
    if (f.open(QFile::WriteOnly | QFile::Truncate)) {
        QTextStream os(&f);
        int n = strings_.size();
        while(n>0) os << strings_.at(--n) << endl;
    }
}

void QConsoleWidget::History::add(const QString& str)
{
    active_ = false;
    //if (strings_.contains(str)) return;
    if (strings_.size() == maxsize_) strings_.pop_back();
    strings_.push_front(str);
}

void QConsoleWidget::History::activate(const QString& tk)
{
    active_ = true;
    token_ = tk;
    pos_ = -1;
}

bool QConsoleWidget::History::move(bool dir)
{
    if (active_)
    {
        int next = indexOf ( dir, pos_ );
        if (pos_!=next)
        {
            pos_=next;
            return true;
        }
        else return false;
    }
    else return false;
}

int QConsoleWidget::History::indexOf(bool dir, int from) const
{
    int i = from, to = from;
    if (dir)
    {
        while(i < strings_.size()-1)
        {
            const QString& si = strings_.at(++i);
            if (si.startsWith(token_)) { return i; }
        }
    }
    else
    {
        while(i > 0)
        {
            const QString& si = strings_.at(--i);
            if (si.startsWith(token_)) { return i; }
        }
        return -1;
    }
    return to;
}

/////////////////// Stream manipulators /////////////////////

QTextStream &waitForInput(QTextStream &s)
{
    QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
    if (d) d->waitForReadyRead(-1);
    return s;
}

QTextStream &inputMode(QTextStream &s)
{
    QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
    if (d && d->widget()) d->widget()->setMode(QConsoleWidget::Input);
    return s;
}
QTextStream &outChannel(QTextStream &s)
{
    QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
    if (d) d->setCurrentWriteChannel(QConsoleWidget::StandardOutput);
    return s;
}
QTextStream &errChannel(QTextStream &s)
{
    QConsoleIODevice* d = qobject_cast<QConsoleIODevice*>(s.device());
    if (d) d->setCurrentWriteChannel(QConsoleWidget::StandardError);
    return s;
}

