#include "CPUSideBar.h"
#include "Configuration.h"
#include "Breakpoints.h"
#include "CPUDisassembly.h"
#include "CachedFontMetrics.h"
#include <QToolTip>

CPUSideBar::CPUSideBar(CPUDisassembly* Ptr, QWidget* parent) : QAbstractScrollArea(parent)
{
    setWindowTitle("SideBar");
    topVA = -1;
    selectedVA = -1;
    viewableRows = 0;
    mFontMetrics = nullptr;

    mDisas = Ptr;

    mInstrBuffer = mDisas->instructionsBuffer();

    memset(&regDump, 0, sizeof(REGDUMP));

    updateSlots();

    this->setMouseTracking(true);

}

CPUSideBar::~CPUSideBar()
{
    delete mFontMetrics;
}

void CPUSideBar::updateSlots()
{
    connect(Config(), SIGNAL(colorsUpdated()), this, SLOT(updateColors()));
    connect(Config(), SIGNAL(fontsUpdated()), this, SLOT(updateFonts()));
    connect(Bridge::getBridge(), SIGNAL(foldDisassembly(duint, duint)), this, SLOT(foldDisassembly(duint, duint)));

    // Init all other updates once
    updateColors();
    updateFonts();
}

void CPUSideBar::updateColors()
{
    mBackgroundColor = ConfigColor("SideBarBackgroundColor");

    mConditionalJumpLineFalseColor = ConfigColor("SideBarConditionalJumpLineFalseColor");
    mUnconditionalJumpLineFalseColor = ConfigColor("SideBarUnconditionalJumpLineFalseColor");
    mConditionalJumpLineTrueColor = ConfigColor("SideBarConditionalJumpLineTrueColor");
    mUnconditionalJumpLineTrueColor = ConfigColor("SideBarUnconditionalJumpLineTrueColor");
    mConditionalJumpLineFalseBackwardsColor = ConfigColor("SideBarConditionalJumpLineFalseBackwardsColor");
    mUnconditionalJumpLineFalseBackwardsColor = ConfigColor("SideBarUnconditionalJumpLineFalseBackwardsColor");
    mConditionalJumpLineTrueBackwardsColor = ConfigColor("SideBarConditionalJumpLineTrueBackwardsColor");
    mUnconditionalJumpLineTrueBackwardsColor = ConfigColor("SideBarUnconditionalJumpLineTrueBackwardsColor");

    mBulletBreakpointColor = ConfigColor("SideBarBulletBreakpointColor");
    mBulletBookmarkColor = ConfigColor("SideBarBulletBookmarkColor");
    mBulletColor = ConfigColor("SideBarBulletColor");
    mBulletDisabledBreakpointColor = ConfigColor("SideBarBulletDisabledBreakpointColor");

    mCipLabelColor = ConfigColor("SideBarCipLabelColor");
    mCipLabelBackgroundColor = ConfigColor("SideBarCipLabelBackgroundColor");

    mChkBoxForeColor = ConfigColor("SideBarCheckBoxForeColor");
    mChkBoxBackColor = ConfigColor("SideBarCheckBoxBackColor");

    mUnconditionalPen = QPen(mUnconditionalJumpLineFalseColor, 1, Qt::SolidLine);
    mConditionalPen = QPen(mConditionalJumpLineFalseColor, 1, Qt::DashLine);
    mUnconditionalBackwardsPen = QPen(mUnconditionalJumpLineFalseBackwardsColor, 1, Qt::SolidLine);
    mConditionalBackwardsPen = QPen(mConditionalJumpLineFalseBackwardsColor, 1, Qt::DashLine);
}

void CPUSideBar::updateFonts()
{
    mDefaultFont = mDisas->font();
    this->setFont(mDefaultFont);

    delete mFontMetrics;
    mFontMetrics = new CachedFontMetrics(this, mDefaultFont);
    fontWidth  = mFontMetrics->width(' ');
    fontHeight = mFontMetrics->height();

    mBulletYOffset = 2;
    mBulletRadius = fontHeight - 2 * mBulletYOffset;
}

QSize CPUSideBar::sizeHint() const
{
    return QSize(40, this->viewport()->height());
}

void CPUSideBar::debugStateChangedSlot(DBGSTATE state)
{
    if(state == stopped)
    {
        reload(); //clear
    }
}

void CPUSideBar::reload()
{
    fontHeight = mDisas->getRowHeight();
    viewport()->update();
}

void CPUSideBar::changeTopmostAddress(dsint i)
{
    topVA = i;
    DbgGetRegDumpEx(&regDump, sizeof(REGDUMP));
    reload();
}

void CPUSideBar::setViewableRows(int rows)
{
    viewableRows = rows;
}

void CPUSideBar::setSelection(dsint selVA)
{
    if(selVA != selectedVA)
    {
        selectedVA = selVA;
        reload();
    }
}

bool CPUSideBar::isJump(int i) const
{
    if(i < 0 || i >= mInstrBuffer->size())
        return false;

    const Instruction_t & instr = mInstrBuffer->at(i);
    Instruction_t::BranchType branchType = instr.branchType;
    if(branchType == Instruction_t::Unconditional || branchType == Instruction_t::Conditional)
    {
        duint start = mDisas->getBase();
        duint end = start + mDisas->getSize();
        duint addr = DbgGetBranchDestination(start + instr.rva);

        if(!addr)
            return false;

        return addr >= start && addr < end; //do not draw jumps that go out of the section
    }

    return false;
}

void CPUSideBar::paintEvent(QPaintEvent* event)
{
    Q_UNUSED(event);

    QPainter painter(this->viewport());

    // Paints background
    painter.fillRect(painter.viewport(), mBackgroundColor);

    // Don't draw anything if there aren't any instructions to draw
    if(mInstrBuffer->size() == 0)
        return;

    if(mCodeFoldingManager.isFolded(regDump.regcontext.cip))
    {
        mCodeFoldingManager.expandFoldSegment(regDump.regcontext.cip);
        mDisas->reloadData();
    }

    int jumpoffset = 0;

    duint last_va = mInstrBuffer->last().rva + mDisas->getBase();
    duint first_va = mInstrBuffer->first().rva + mDisas->getBase();

    QVector<std::pair<QString, duint>> regLabel;
    auto appendReg = [&regLabel, last_va, first_va](const QString & name, duint value)
    {
        if(value >= first_va && value <= last_va)
            regLabel.append(std::make_pair(name, value));
    };
#ifdef _WIN64
    appendReg("RIP", regDump.regcontext.cip);
    appendReg("RAX", regDump.regcontext.cax);
    appendReg("RCX", regDump.regcontext.ccx);
    appendReg("RDX", regDump.regcontext.cdx);
    appendReg("RBX", regDump.regcontext.cbx);
    appendReg("RSP", regDump.regcontext.csp);
    appendReg("RBP", regDump.regcontext.cbp);
    appendReg("RSI", regDump.regcontext.csi);
    appendReg("RDI", regDump.regcontext.cdi);
    appendReg("R8", regDump.regcontext.r8);
    appendReg("R9", regDump.regcontext.r9);
    appendReg("R10", regDump.regcontext.r10);
    appendReg("R11", regDump.regcontext.r11);
    appendReg("R12", regDump.regcontext.r12);
    appendReg("R13", regDump.regcontext.r13);
    appendReg("R14", regDump.regcontext.r14);
    appendReg("R15", regDump.regcontext.r15);
#else //x86
    appendReg("EIP", regDump.regcontext.cip);
    appendReg("EAX", regDump.regcontext.cax);
    appendReg("ECX", regDump.regcontext.ccx);
    appendReg("EDX", regDump.regcontext.cdx);
    appendReg("EBX", regDump.regcontext.cbx);
    appendReg("ESP", regDump.regcontext.csp);
    appendReg("EBP", regDump.regcontext.cbp);
    appendReg("ESI", regDump.regcontext.csi);
    appendReg("EDI", regDump.regcontext.cdi);
#endif //_WIN64
    if(ConfigBool("Gui", "SidebarWatchLabels"))
    {
        BridgeList<WATCHINFO> WatchList;
        DbgGetWatchList(&WatchList);
        for(int i = 0; i < WatchList.Count(); i++)
        {
            if(WatchList[i].varType == WATCHVARTYPE::TYPE_UINT || WatchList[i].varType == WATCHVARTYPE::TYPE_ASCII || WatchList[i].varType == WATCHVARTYPE::TYPE_UNICODE)
            {
                appendReg(QString(WatchList[i].WatchName), WatchList[i].value);
            }
        }
    }

    std::vector<JumpLine> jumpLines;
    std::vector<LabelArrow> labelArrows;

    for(int line = 0; line < viewableRows; line++)
    {
        if(line >= mInstrBuffer->size()) //at the end of the page it will crash otherwise
            break;

        const Instruction_t & instr = mInstrBuffer->at(line);
        duint instrVA = instr.rva + mDisas->getBase();
        duint instrVAEnd = instrVA + instr.length;

        // draw bullet
        drawBullets(&painter, line, DbgGetBpxTypeAt(instrVA) != bp_none, DbgIsBpDisabled(instrVA), DbgGetBookmarkAt(instrVA));

        if(isJump(line)) //handle jumps
        {
            duint baseAddr = mDisas->getBase();
            duint destVA = DbgGetBranchDestination(baseAddr + instr.rva);

            JumpLine jmp;
            jmp.isJumpGoingToExecute = DbgIsJumpGoingToExecute(instrVA);
            jmp.isSelected = (selectedVA == instrVA || selectedVA == destVA);
            jmp.isConditional = instr.branchType == Instruction_t::Conditional;
            jmp.line = line;

            /*
            if(mDisas->currentEIP() != mInstrBuffer->at(line).rva) //create a setting for this
                isJumpGoingToExecute=false;
            */

            jumpoffset++;

            // Do not draw jumps that leave the memory range
            if(destVA >= mDisas->getBase() + mDisas->getSize() || destVA < mDisas->getBase())
                continue;

            if(destVA <= last_va && destVA >= first_va)
            {
                int destLine = line;
                while(destLine > -1 && destLine < mInstrBuffer->size())
                {
                    duint va = mInstrBuffer->at(destLine).rva + mDisas->getBase();
                    if(destVA > instrVA) //jump goes down
                    {
                        duint vaEnd = va + mInstrBuffer->at(destLine).length - 1;
                        if(vaEnd >= destVA)
                            break;
                        destLine++;
                    }
                    else //jump goes up
                    {
                        if(va <= destVA)
                            break;
                        destLine--;
                    }
                }
                jmp.destLine = destLine;
            }
            else if(destVA > last_va)
                jmp.destLine = viewableRows + 6;
            else if(destVA < first_va)
                jmp.destLine = -6;
            jumpLines.emplace_back(jmp);
        }

        QString regLabelText;
        for(auto i = regLabel.cbegin(); i != regLabel.cend(); i++)
        {
            if(i->second >= instrVA && i->second < instrVAEnd)
                regLabelText += i->first + " ";
        }
        if(regLabelText.size())
        {
            regLabelText.chop(1);
            labelArrows.push_back(drawLabel(&painter, line, regLabelText));
        }

        if(isFoldingGraphicsPresent(line) == 1)
        {
            if(mCodeFoldingManager.isFoldStart(instrVA))
                drawFoldingCheckbox(&painter, line * fontHeight, mCodeFoldingManager.isFolded(instrVA));
            else
                drawFoldingCheckbox(&painter, line * fontHeight, false);
        }
        else if(mCodeFoldingManager.isFoldBody(instrVA))
        {
            painter.setPen(QColor(Qt::black));
            painter.drawLine(QPointF(viewport()->width() - fontHeight / 2 - mBulletXOffset - mBulletRadius, line * fontHeight), QPointF(viewport()->width() - fontHeight / 2 - mBulletXOffset - mBulletRadius, (line + 1) * fontHeight));
            if(mCodeFoldingManager.isFoldEnd(instrVA + instr.length - 1))
                painter.drawLine(QPointF(viewport()->width() - fontHeight / 2 - mBulletXOffset - mBulletRadius, (line + 1) * fontHeight), QPointF(viewport()->width(), (line + 1) * fontHeight));
        }
    }
    if(jumpLines.size())
    {
        AllocateJumpOffsets(jumpLines, labelArrows);
        for(auto i : jumpLines)
            drawJump(&painter, i.line, i.destLine, i.jumpOffset, i.isConditional, i.isJumpGoingToExecute, i.isSelected);
    }
    else
    {
        for(auto i = labelArrows.begin(); i != labelArrows.end(); i++)
            i->endX = viewport()->width() - 1 - 11 - (isFoldingGraphicsPresent(i->line) != 0 ? mBulletRadius + fontHeight : 0);
    }
    drawLabelArrows(&painter, labelArrows);
}

void CPUSideBar::mouseReleaseEvent(QMouseEvent* e)
{
    // Don't care if we are not debugging
    if(!DbgIsDebugging())
        return;

    // get clicked line
    const int x = e->pos().x();
    const int y = e->pos().y();
    const int line = y / fontHeight;
    if(line >= mInstrBuffer->size())
        return;
    const int width = viewport()->width();

    const int bulletRadius = fontHeight / 2 + 1; //14/2=7
    const int bulletX = width - mBulletXOffset;
    //const int bulletY = line * fontHeight + mBulletYOffset;

    const bool CheckBoxPresent = isFoldingGraphicsPresent(line);

    if(x > bulletX + bulletRadius)
        return;
    // calculate virtual address of clicked line
    duint wVA = mInstrBuffer->at(line).rva + mDisas->getBase();
    if(CheckBoxPresent)
    {
        if(x > width - fontHeight - mBulletXOffset - mBulletRadius && x < width - mBulletXOffset - mBulletRadius)
        {
            if(e->button() == Qt::LeftButton)
            {
                duint start, end;
                const Instruction_t* instr = &mInstrBuffer->at(line);
                const dsint SelectionStart = mDisas->getSelectionStart();
                const dsint SelectionEnd = mDisas->getSelectionEnd();
                if(mCodeFoldingManager.isFoldStart(wVA))
                {
                    mCodeFoldingManager.setFolded(wVA, !mCodeFoldingManager.isFolded(wVA));
                }
                else if(instr->rva == SelectionStart && (SelectionEnd - SelectionStart + 1) != instr->length)
                {
                    bool success = mCodeFoldingManager.addFoldSegment(wVA, mDisas->getSelectionEnd() - mDisas->getSelectionStart());
                    if(!success)
                        GuiAddLogMessage(tr("Cannot fold selection.\n").toUtf8().constData());
                }
                else if((DbgArgumentGet(wVA, &start, &end) || DbgFunctionGet(wVA, &start, &end)) && wVA == start)
                {
                    bool success = mCodeFoldingManager.addFoldSegment(wVA, end - start);
                    if(!success)
                        GuiAddLogMessage(tr("Cannot fold selection.\n").toUtf8().constData());
                }
                else
                    GuiAddLogMessage(tr("Cannot fold selection.\n").toUtf8().constData());
            }
            else if(e->button() == Qt::RightButton)
            {
                mCodeFoldingManager.delFoldSegment(wVA);
            }
            mDisas->reloadData();
            viewport()->update();
        }
    }
    if(x < bulletX - mBulletRadius)
        return;
    //if(y < bulletY - mBulletRadius || y > bulletY + bulletRadius)
    //    return;

    if(e->button() == Qt::LeftButton)
    {
        QString wCmd;
        // create --> disable --> delete --> create --> ...
        switch(Breakpoints::BPState(bp_normal, wVA))
        {
        case bp_enabled:
            // breakpoint exists and is enabled --> disable breakpoint
            wCmd = "bd ";
            break;
        case bp_disabled:
            // is disabled --> delete or enable
            if(Breakpoints::BPTrival(bp_normal, wVA))
                wCmd = "bc ";
            else
                wCmd = "be ";
            break;
        case bp_non_existent:
            // no breakpoint was found --> create breakpoint
            wCmd = "bp ";
            break;
        }
        wCmd += ToPtrString(wVA);
        DbgCmdExec(wCmd);
    }
}

void CPUSideBar::mouseDoubleClickEvent(QMouseEvent* event)
{
    const int line = event->y() / fontHeight;
    if(line >= mInstrBuffer->size())
        return;
    const bool CheckBoxPresent = isFoldingGraphicsPresent(line);

    if(CheckBoxPresent)
    {
        if(event->x() > width() - fontHeight - mBulletXOffset - mBulletRadius && event->x() < width() - mBulletXOffset - mBulletRadius)
        {
            if(event->button() == Qt::LeftButton)
            {
                duint wVA = mInstrBuffer->at(line).rva + mDisas->getBase();
                duint start = mCodeFoldingManager.getFoldBegin(wVA);
                duint end = mCodeFoldingManager.getFoldEnd(wVA);
                if(mCodeFoldingManager.isFolded(wVA) || (start <= regDump.regcontext.cip && end >= regDump.regcontext.cip))
                {
                    mDisas->setSingleSelection(start - mDisas->getBase());
                    mDisas->expandSelectionUpTo(end - mDisas->getBase());
                    mDisas->setFocus();
                }
            }
        }
    }
}

void CPUSideBar::mouseMoveEvent(QMouseEvent* event)
{
    if(!DbgIsDebugging() || !mInstrBuffer->size())
    {
        QAbstractScrollArea::mouseMoveEvent(event);
        QToolTip::hideText();
        setCursor(QCursor(Qt::ArrowCursor));
        return;
    }

    const QPoint & mousePos = event->pos();
    const QPoint & globalMousePos = event->globalPos();
    const int width = viewport()->width();

    const int mLine = mousePos.y() / fontHeight;

    const bool lineInBounds = mLine > 0 && mLine < mInstrBuffer->size();

    const int mBulletX = width - mBulletXOffset;
    const int mBulletY = mLine * fontHeight + mBulletYOffset;

    const int mouseBulletXOffset = abs(mBulletX - mousePos.x());
    const int mouseBulletYOffset = abs(mBulletY - mousePos.y());

    // calculate virtual address of clicked line
    duint wVA = 0;
    if(lineInBounds)
        wVA = mInstrBuffer->at(mLine).rva + mDisas->getBase();

    // check if mouse is on a code folding box
    if(mousePos.x() > width - fontHeight - mBulletXOffset - mBulletRadius && mousePos.x() < width - mBulletXOffset - mBulletRadius)
    {
        if(isFoldingGraphicsPresent(mLine))
        {
            if(mCodeFoldingManager.isFolded(wVA))
            {
                QToolTip::showText(globalMousePos, tr("Click to unfold, right click to delete."));
            }
            else
            {
                if(mCodeFoldingManager.isFoldStart(wVA))
                    QToolTip::showText(globalMousePos, tr("Click to fold, right click to delete."));
                else
                    QToolTip::showText(globalMousePos, tr("Click to fold."));
            }
        }
    }
    // if (mouseCursor not on a bullet) or (mLine not in bounds)
    else if((mouseBulletXOffset > mBulletRadius ||  mouseBulletYOffset > mBulletRadius) || !lineInBounds)
    {
        QToolTip::hideText();
        setCursor(QCursor(Qt::ArrowCursor));
    }
    else
    {
        switch(Breakpoints::BPState(bp_normal, wVA))
        {
        case bp_enabled:
            QToolTip::showText(globalMousePos, tr("Breakpoint Enabled"));
            break;
        case bp_disabled:
            QToolTip::showText(globalMousePos, tr("Breakpoint Disabled"));
            break;
        case bp_non_existent:
            QToolTip::showText(globalMousePos, tr("Breakpoint Not Set"));
            break;
        }
        setCursor(QCursor(Qt::PointingHandCursor));
    }
}

void CPUSideBar::drawJump(QPainter* painter, int startLine, int endLine, int jumpoffset, bool conditional, bool isexecute, bool isactive)
{
    painter->save();

    // Pixel adjustment to make drawing lines even
    int pixel_y_offs = 0;

    int y_start =  fontHeight * (1 + startLine) - 0.5 * fontHeight - pixel_y_offs;
    int y_end =  fontHeight * (1 + endLine) - 0.5 * fontHeight;
    int y_diff = y_end >= y_start ? 1 : -1;

    if(conditional)
    {
        if(y_diff > 0)
            painter->setPen(mConditionalPen);
        else
            painter->setPen(mConditionalBackwardsPen);
    }
    else //JMP
    {
        if(y_diff > 0)
            painter->setPen(mUnconditionalPen);
        else
            painter->setPen(mUnconditionalBackwardsPen);
    }

    if(isactive) //selected
    {
        QPen activePen = painter->pen();

        // Active/selected jumps use a bold line (2px wide)
        activePen.setWidth(2);

        // Adjust for 2px line
        pixel_y_offs = 0;

        // Use a different color to highlight jumps that will execute
        if(isexecute)
        {
            if(conditional)
            {
                if(y_diff > 0)
                    activePen.setColor(mConditionalJumpLineTrueColor);
                else
                    activePen.setColor(mConditionalJumpLineTrueBackwardsColor);
            }
            else
            {
                if(y_diff > 0)
                    activePen.setColor(mUnconditionalJumpLineTrueColor);
                else
                    activePen.setColor(mUnconditionalJumpLineTrueBackwardsColor);
            }
        }

        // Update the painter itself with the new pen style
        painter->setPen(activePen);
    }

    // Cache variables
    const int viewportWidth = viewport()->width();
    const int viewportHeight = viewport()->height();

    const int JumpPadding = 11;
    int x = viewportWidth - jumpoffset * JumpPadding - 15 - fontHeight;
    int x_right = viewportWidth - 12;

    // special handling of self-jumping
    if(startLine == endLine)
    {
        y_start -= fontHeight / 4;
        y_end += fontHeight / 4;
    }

    // Horizontal (<----)
    if(!isFoldingGraphicsPresent(startLine) != 0)
        painter->drawLine(x_right, y_start, x, y_start);
    else
        painter->drawLine(x_right - mBulletRadius - fontHeight, y_start, x, y_start);

    // Vertical
    painter->drawLine(x, y_start, x, y_end);

    // Specialized pen for solid lines only, keeping other styles
    QPen solidLine = painter->pen();
    solidLine.setStyle(Qt::SolidLine);

    // Draw the arrow
    if(!isactive) //selected
    {
        // Horizontal (---->)
        if(isFoldingGraphicsPresent(endLine) != 0)
            painter->drawLine(x, y_end, x_right - mBulletRadius - fontHeight, y_end);
        else
            painter->drawLine(x, y_end, x_right, y_end);

        if(endLine == viewableRows + 6)
        {
            int y = viewportHeight - 1;
            if(y > y_start)
            {
                painter->setPen(solidLine);
                QPoint wPoints[] =
                {
                    QPoint(x - 3, y - 3),
                    QPoint(x, y),
                    QPoint(x + 3, y - 3),
                };
                painter->drawPolyline(wPoints, 3);
            }
        }
        else if(endLine == -6)
        {
            int y = 0;
            painter->setPen(solidLine);
            QPoint wPoints[] =
            {
                QPoint(x - 3, y + 3),
                QPoint(x, y),
                QPoint(x + 3, y + 3),
            };
            painter->drawPolyline(wPoints, 3);
        }
        else
        {
            if(isFoldingGraphicsPresent(endLine) != 0)
                x_right -= mBulletRadius + fontHeight;
            painter->setPen(solidLine);
            QPoint wPoints[] =
            {
                QPoint(x_right - 3, y_end - 3),
                QPoint(x_right, y_end),
                QPoint(x_right - 3, y_end + 3),
            };
            painter->drawPolyline(wPoints, 3);
        }
    }
    else
    {
        if(endLine == viewableRows + 6)
        {
            int y = viewportHeight - 1;
            x--;
            painter->setPen(solidLine);
            QPoint wPoints[] =
            {
                QPoint(x - 3, y - 3),
                QPoint(x, y),
                QPoint(x + 3, y - 3),
            };
            painter->drawPolyline(wPoints, 3);
        }
        else if(endLine == -6)
        {
            int y = 0;
            painter->setPen(solidLine);
            QPoint wPoints[] =
            {
                QPoint(x - 3, y + 3),
                QPoint(x, y),
                QPoint(x + 3, y + 3),
            };
            painter->drawPolyline(wPoints, 3);
        }
        else
        {
            if(isFoldingGraphicsPresent(endLine) != 0)
                drawStraightArrow(painter, x, y_end, x_right - mBulletRadius - fontHeight, y_end);
            else
                drawStraightArrow(painter, x, y_end, x_right, y_end);
        }
    }
    painter->restore();
}

void CPUSideBar::drawBullets(QPainter* painter, int line, bool isbp, bool isbpdisabled, bool isbookmark)
{
    painter->save();

    if(isbp)
        painter->setBrush(QBrush(mBulletBreakpointColor));
    else if(isbookmark)
        painter->setBrush(QBrush(mBulletBookmarkColor));
    else
        painter->setBrush(QBrush(mBulletColor));

    painter->setPen(mBackgroundColor);

    const int x = viewport()->width() - mBulletXOffset; //initial x
    const int y = line * fontHeight; //initial y

    painter->setRenderHint(QPainter::Antialiasing, true);
    if(isbpdisabled) //disabled breakpoint
        painter->setBrush(QBrush(mBulletDisabledBreakpointColor));

    painter->drawEllipse(x, y + mBulletYOffset, mBulletRadius, mBulletRadius);

    painter->restore();
}

CPUSideBar::LabelArrow CPUSideBar::drawLabel(QPainter* painter, int Line, const QString & Text)
{
    painter->save();
    const int LineCoordinate = fontHeight * (1 + Line);

    const QColor & IPLabel = mCipLabelColor;
    const QColor & IPLabelBG = mCipLabelBackgroundColor;

    int width = mFontMetrics->width(Text);
    int x = 1;
    int y = LineCoordinate - fontHeight;

    QRect rect(x, y, width, fontHeight - 1);

    // Draw rectangle
    painter->setBrush(IPLabelBG);
    painter->setPen(IPLabelBG);
    painter->drawRect(rect);

    // Draw text inside the rectangle
    painter->setPen(IPLabel);
    painter->drawText(rect, Qt::AlignHCenter | Qt::AlignVCenter, Text);

    // Draw arrow
    /*y = fontHeight * (1 + Line) - 0.5 * fontHeight;

    painter->setPen(QPen(IPLabelBG, 2.0));
    painter->setBrush(QBrush(IPLabelBG));
    drawStraightArrow(painter, rect.right() + 2, y, this->viewport()->width() - x - 11 - (isFoldingGraphicsPresent(Line) != 0 ? mBulletRadius + fontHeight : 0), y);*/

    LabelArrow labelArrow;
    labelArrow.line = Line;
    labelArrow.startX = rect.right() + 2;
    labelArrow.endX = 0;

    painter->restore();

    return labelArrow;
}

void CPUSideBar::drawLabelArrows(QPainter* painter, const std::vector<LabelArrow> & labelArrows)
{
    if(!labelArrows.empty())
    {
        painter->save();
        painter->setPen(QPen(mCipLabelBackgroundColor, 2.0));
        for(auto i : labelArrows)
        {
            if(i.startX < i.endX)
            {
                int y = fontHeight * (1 + i.line) - 0.5 * fontHeight;
                drawStraightArrow(painter, i.startX, y, i.endX, y);
            }
        }
        painter->restore();
    }
}

void CPUSideBar::drawFoldingCheckbox(QPainter* painter, int y, bool state)
{
    int x = viewport()->width() - fontHeight - mBulletXOffset - mBulletRadius;
    painter->save();
    painter->setBrush(QBrush(mChkBoxBackColor));
    painter->setPen(QColor(mChkBoxForeColor));
    QRect rect(x, y, fontHeight, fontHeight);
    painter->drawRect(rect);

    painter->drawLine(QPointF(x + fontHeight / 4, y + fontHeight / 2), QPointF(x + 3 * fontHeight / 4, y + fontHeight / 2));
    if(state) // "+"
        painter->drawLine(QPointF(x + fontHeight / 2, y + fontHeight / 4), QPointF(x + fontHeight / 2, y + 3 * fontHeight / 4));
    painter->restore();
}

void CPUSideBar::drawStraightArrow(QPainter* painter, int x1, int y1, int x2, int y2)
{
    painter->drawLine(x1, y1, x2, y2);

    const int ArrowSizeX = 4;// Width  of arrow tip in pixels
    const int ArrowSizeY = 4;// Height of arrow tip in pixels

    // X and Y values adjusted so that the arrow itself is symmetrical on 2px
    painter->drawLine(x2 - 1, y2 - 1, x2 - ArrowSizeX, y2 - ArrowSizeY);// Arrow top
    painter->drawLine(x2 - 1, y2, x2 - ArrowSizeX, y2 + ArrowSizeY - 1);// Arrow bottom
}

void CPUSideBar::AllocateJumpOffsets(std::vector<JumpLine> & jumpLines, std::vector<LabelArrow> & labelArrows)
{
    unsigned int* numLines = new unsigned int[viewableRows * 2]; // Low:jump offsets of the vertical jumping line, High:jump offsets of the horizontal jumping line.
    memset(numLines, 0, sizeof(unsigned int) * viewableRows * 2);
    // preprocessing
    for(size_t i = 0; i < jumpLines.size(); i++)
    {
        JumpLine & jmp = jumpLines.at(i);
        jmp.jumpOffset = abs(jmp.destLine - jmp.line);
    }
    // Sort jumpLines so that longer jumps are put at the back.
    std::sort(jumpLines.begin(), jumpLines.end(), [](const JumpLine & op1, const JumpLine & op2)
    {
        return op2.jumpOffset > op1.jumpOffset;
    });
    // Allocate jump offsets
    for(size_t i = 0; i < jumpLines.size(); i++)
    {
        JumpLine & jmp = jumpLines.at(i);
        unsigned int maxJmpOffset = 0;
        if(jmp.line < jmp.destLine)
        {
            for(int j = jmp.line; j <= jmp.destLine && j < viewableRows; j++)
            {
                if(numLines[j] > maxJmpOffset)
                    maxJmpOffset = numLines[j];
            }
        }
        else
        {
            for(int j = jmp.line; j >= jmp.destLine && j >= 0; j--)
            {
                if(numLines[j] > maxJmpOffset)
                    maxJmpOffset = numLines[j];
            }
        }
        jmp.jumpOffset = maxJmpOffset + 1;
        if(jmp.line < jmp.destLine)
        {
            for(int j = jmp.line; j <= jmp.destLine && j < viewableRows; j++)
                numLines[j] = jmp.jumpOffset;
        }
        else
        {
            for(int j = jmp.line; j >= jmp.destLine && j >= 0; j--)
                numLines[j] = jmp.jumpOffset;
        }
        if(jmp.line >= 0 && jmp.line < viewableRows)
            numLines[jmp.line + viewableRows] = jmp.jumpOffset;
        if(jmp.destLine >= 0 && jmp.destLine < viewableRows)
            numLines[jmp.destLine + viewableRows] = jmp.jumpOffset;
    }
    // set label arrows according to jump offsets
    auto viewportWidth = viewport()->width();
    const int JumpPadding = 11;
    for(auto i = labelArrows.begin(); i != labelArrows.end(); i++)
    {
        if(numLines[i->line + viewableRows] != 0)
            i->endX = viewportWidth - numLines[i->line + viewableRows] * JumpPadding - 15 - fontHeight; // This expression should be consistent with drawJump
        else
            i->endX = viewportWidth - 1 - 11 - (isFoldingGraphicsPresent(i->line) != 0 ? mBulletRadius + fontHeight : 0);
    }
    delete[] numLines;
}

int CPUSideBar::isFoldingGraphicsPresent(int line)
{
    if(line < 0 || line >= mInstrBuffer->size()) //There couldn't be a folding box out of viewport
        return 0;
    const Instruction_t* instr = &mInstrBuffer->at(line);
    if(instr == nullptr) //Selection out of a memory page
        return 0;
    auto wVA = instr->rva + mDisas->getBase();
    if(mCodeFoldingManager.isFoldStart(wVA)) //Code is already folded
        return 1;
    const dsint SelectionStart = mDisas->getSelectionStart();
    const dsint SelectionEnd = mDisas->getSelectionEnd();
    duint start, end;
    if((DbgArgumentGet(wVA, &start, &end) || DbgFunctionGet(wVA, &start, &end)) && wVA == start)
    {
        return end - start + 1 != instr->length ? 1 : 0;
    }
    else if(instr->rva >= duint(SelectionStart) && instr->rva < duint(SelectionEnd))
    {
        if(instr->rva == SelectionStart)
            return (SelectionEnd - SelectionStart + 1) != instr->length ? 1 : 0;
    }
    return 0;
}

CodeFoldingHelper* CPUSideBar::getCodeFoldingManager()
{
    return &mCodeFoldingManager;
}

void CPUSideBar::foldDisassembly(duint startAddress, duint length)
{
    mCodeFoldingManager.addFoldSegment(startAddress, length);
}

void* CPUSideBar::operator new(size_t size)
{
    return _aligned_malloc(size, 16);
}

void CPUSideBar::operator delete(void* p)
{
    _aligned_free(p);
}
