/****************************************************************************
**
** Copyrigh  (C) 2019 alexrayne <alexraynepe196@gmail.com>
** Copyright (C) 2012 Denis Shienkov <denis.shienkov@gmail.com>
** Copyright (C) 2012 Laszlo Papp <lpapp@kde.org>
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtSerialPort module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
**   * Redistributions of source code must retain the above copyright
**     notice, this list of conditions and the following disclaimer.
**   * Redistributions in binary form must reproduce the above copyright
**     notice, this list of conditions and the following disclaimer in
**     the documentation and/or other materials provided with the
**     distribution.
**   * Neither the name of The Qt Company Ltd nor the names of its
**     contributors may be used to endorse or promote products derived
**     from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/

#include "console_ui.h"

#include <QScrollBar>
#include <QTextBlock>

Console::Console(QWidget *parent) :
    QPlainTextEdit(parent)
{
    document()->setMaximumBlockCount(100);

    styles_init();
    style_apply(styleCLIENT);

    //рисовалка терминала считается не имеет своего редатора, и переписывает
    //  экран сверху
    setOverwriteMode(true);

    historyPos = history.length();
}

void Console::styles_init(){
    auto palete = palette();
    palete.setColor(QPalette::Base, Qt::black);
    palete.setColor(QPalette::Text, Qt::green);
    setPalette(palete);

    QFont font;
    font.setFamily("Courier New");
    font.setPointSize(10);
    font.setStyleStrategy(QFont::NoAntialias);
    setFont(font);

    styles[styleCLIENT] = currentCharFormat();
    styles[styleSERVER] = currentCharFormat();

    styles[styleSERVER].setForeground( QBrush(Qt::green) );
    styles[styleCLIENT].setForeground( QBrush(Qt::white) );
}


void Console::putData(const QByteArray data)
{
    input_by_client(false);
    ModeOverScope   savemode(*this);
    setOverwriteMode(true);
    
    auto ei = data.cbegin();
    auto si = data.cend();
    for (; ei != data.cend(); ++ei) {
         if (si > ei)
             si = ei;

         if (*ei == '\r'){
             if (ei > si){
                writeData( data.mid( (si-data.cbegin()), (ei-si) ) );
             }
             //переведем курсор в начало строки
             auto tmp = textCursor();
             tmp.movePosition(QTextCursor::StartOfLine);
             setTextCursor( tmp );
             si = data.cend();
         }
         else 
         if (*ei== '\b'){
             if (ei > si)
                writeData( data.mid( (si-data.cbegin()), (ei-si) ) );
             if (true)
                textCursor().deletePreviousChar();
             else {
                 //отведу курсор назад
                 auto tmp = textCursor();
                 tmp.movePosition(QTextCursor::Left);
                 setTextCursor( tmp );
             }
             si = data.cend();
         }
    }
    if (ei > si){
       writeData( data.mid( (si-data.cbegin()), (ei-si+1) ) );
    }

    QScrollBar *bar = verticalScrollBar();
    bar->setValue(bar->maximum());
}

void Console::writeData(const QByteArray& data){
    input_by_client(false);
    ModeOverScope   savemode(*this);
    setOverwriteMode(true);

    auto cursor = textCursor();
    cursor.clearSelection();
    cursor.movePosition(QTextCursor::Right, QTextCursor::KeepAnchor, data.size());
    cursor.insertText(data);
    cursor.clearSelection();
}

void Console::output(QString s)
{
    ModeOverScope   savemode(*this);
    setOverwriteMode(true);

    textCursor().insertBlock();
    QTextCharFormat format;
    format.setForeground(Qt::white);
    textCursor().setBlockCharFormat(format);
    textCursor().insertText(s);
    insertPrompt();
}

void Console::insertPrompt(bool insertNewBlock)
{
    if(insertNewBlock)
        textCursor().insertBlock();

    QTextCharFormat format;
    format.setForeground(Qt::green);
    textCursor().setBlockCharFormat(format);
    textCursor().insertText(prompt);
    scrollDown();
}

void Console::scrollDown()
{
    QScrollBar *vbar = verticalScrollBar();
    vbar->setValue(vbar->maximum());
}

void Console::mode_insert_toggle(){
    setOverwriteMode( !overwriteMode() );
}

void Console::setLocalEdit(bool set)
{
    localEdit = set;
}

void Console::style_apply(StyleID id){
    auto& s = styles[id];
    setCurrentCharFormat( s );
}

void Console::input_by_client(bool onoff){
    if ( input_cmd != onoff ){
        StyleID id = (onoff)? styleCLIENT : styleSERVER;
        style_apply(id);
    }
    input_cmd = onoff;
}

void Console::keyPressEvent(QKeyEvent *e)
{
    switch (e->key()) {
    case Qt::Key_Insert:
        if (localEdit){
            //keyInput(e);
            mode_insert_toggle();
        }
        return;

    case Qt::Key_Delete:
        if (!localEdit)
            return;
        if (e->modifiers() == Qt::AltModifier)
            historyDrop();
        else
            keyInput(e);
        return;

    case Qt::Key_Home:
    case Qt::Key_End:
        if (e->modifiers() != Qt::NoModifier)
            //ignore Ctrl+home/end
            return;
        // [FALLTHRU] FALLTHRU

    case Qt::Key_Left:
    case Qt::Key_Right:
        if (localEdit)
            keyInput(e);
        return;

    case Qt::Key_Up:
        if (e->modifiers() == Qt::NoModifier)
            historyBack();
        return;

    case Qt::Key_Down:
        if (e->modifiers() == Qt::NoModifier)
            historyForward();
        return;

    case Qt::Key_Return:
    case Qt::Key_Enter:
        if (localEdit){
            //keyInput(e);
            onEnter();
        }
        else
            emit getData(e->text().toLocal8Bit());
        return;

    //case Qt::Key_Backspace:
    default: break;
    }

    //default key action
    if (localEdit)
        keyInput(e);
    else
        emit getData(e->text().toLocal8Bit());
}

void Console::keyInput(QKeyEvent *e){
    if (!input_cmd){
        position_cmd = textCursor().positionInBlock();
    }
    input_by_client(true);
    QPlainTextEdit::keyPressEvent(e);
    
}


void Console::onEnter()
{
    // move cursor to end entered line
    moveCursor(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);

    auto tmp = textCursor();
    auto blok = tmp.block();
    QString txt = blok.text();
    QString cmd = txt.mid(position_cmd);
    
    //tmp.endEditBlock();
    if(!cmd.isEmpty())
        historyAdd(cmd);
    cmd += '\r';
    input_by_client(false);
    // finsh up this block, later input go into next block
    textCursor().insertBlock();
    emit getData(cmd.toLocal8Bit());
}

void Console::mousePressEvent(QMouseEvent *e)
{
    Q_UNUSED(e)
    setFocus();
}

void Console::mouseDoubleClickEvent(QMouseEvent *e)
{
    Q_UNUSED(e)
}

void Console::contextMenuEvent(QContextMenuEvent *e)
{
    Q_UNUSED(e)
}




void Console::assign_history(const QStringList& x){
    history = x;
    historyPos = history.length();
}

void Console::assign_history(QStringList&& x){
    history.swap(x);
    historyPos = history.length();
}

void Console::historyAdd(QString cmd)
{
    history.append(cmd);
    historyPos = history.length();
}

void Console::historyDrop(){
    history.removeAt(historyPos);
    if (historyPos > history.length())
        historyPos = history.length();
}

void Console::historyDrop(QString x){
    history.removeAll(x);
    if (historyPos > history.length())
        historyPos = history.length();
}

void Console::historyBack()
{
    if (historyPos <= 0)
        return;
    input_by_client(true);

    drop_input();

    QTextCursor cursor = textCursor();
    cursor.insertText(history.at(historyPos-1)); //(prompt + 
    setTextCursor(cursor);
    historyPos--;
}

void Console::historyForward()
{
    if(historyPos == history.length())
        return;

    input_by_client(true);

    drop_input();
    QTextCursor cursor = textCursor();
    if(historyPos == history.length() - 1)
        ;//cursor.insertText(prompt);
    else
        cursor.insertText(history.at(historyPos + 1)); //(prompt + 
    setTextCursor(cursor);
    historyPos++;
}

void Console::drop_input(  ){
    StyleScope savestyle(*this);

    QTextCursor cursor = textCursor();
    //cursor.movePosition(QTextCursor::StartOfBlock);
    cursor.setPosition(cursor.block().position() + position_cmd);
    cursor.movePosition(QTextCursor::EndOfBlock, QTextCursor::KeepAnchor);
    cursor.removeSelectedText();
}

