/*
Copyright (C) 2010 Srivats P.

This file is part of "Ostinato"

This is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>
*/

#include "dumpview.h"

#include <QScrollBar>
#include <QStylePainter>

//! \todo Enable Scrollbars

DumpView::DumpView(QWidget *parent)
    : QAbstractItemView(parent)
{
    int w, h;

    // NOTE: Monospaced fonts only !!!!!!!!!!!
    setFont(QFont("Courier"));
    w = fontMetrics().width('X');
    h = fontMetrics().height();

    mLineHeight = h;
    mCharWidth = w;

    mSelectedRow = mSelectedCol = -1;

    // calculate width for offset column and the whitespace that follows it
    // 0010   00 00 00 00 00 00 00 00 00   00 00 00 00 00 00 00 00   ........ ........
    mOffsetPaneTopRect = QRect(0, 0, w*4, h);
    mDumpPaneTopRect = QRect(mOffsetPaneTopRect.right()+w*3, 0, 
        w*((8*3-1)+2+(8*3-1)), h);
    mAsciiPaneTopRect = QRect(mDumpPaneTopRect.right()+w*3, 0, 
        w*(8+1+8), h);
    qDebug("DumpView::DumpView");
}

QModelIndex DumpView::indexAt(const QPoint &/*point*/) const
{
#if 0
    int x = point.x();
    int row, col;

    if (x > mAsciiPaneTopRect.left())
    {
        col = (x - mAsciiPaneTopRect.left()) / mCharWidth;
        if (col == 8) // don't select whitespace 
            goto _exit;
        else if (col > 8) // adjust for whitespace
            col--;
    } 
    else if (x > mDumpPaneTopRect.left())
    {
        col = (x - mDumpPaneTopRect.left()) / (mCharWidth*3);
    }
    row = point.y()/mLineHeight;

    if ((col < 16) && (row < ((data.size()+16)/16)))
    {
        selrow = row;
        selcol = col;
    }
    else
        goto _exit;

    // last row check col
    if ((row == (((data.size()+16)/16) - 1)) && (col >= (data.size() % 16)))
        goto _exit;

    qDebug("dumpview::selection(%d, %d)", selrow, selcol);

    offset = selrow * 16 + selcol;
#if 0
    for(int i = 0; i < model()->rowCount(parent); i++)
    {
        QModelIndex index = model()->index(i, 0, parent);

        if (model()->hasChildren(index))
            indexAtOffset(offset, index);    // Non Leaf
        else
            if (
            dump.append(model()->data(index, Qt::UserRole).toByteArray()); // Leaf
        // FIXME: Use RawValueRole instead of UserRole
    }
#endif
}

_exit:
    // Clear existing selection
    selrow = -1;

#endif
    return QModelIndex();
}

void DumpView::scrollTo(const QModelIndex &/*index*/, ScrollHint /*hint*/)
{
    // FIXME: implement scrolling
}

QRect DumpView::visualRect(const QModelIndex &/*index*/) const
{
    // FIXME: calculate actual rect
    return rect();
}

//protected:
int DumpView::horizontalOffset() const
{
    return horizontalScrollBar()->value();
}

bool DumpView::isIndexHidden(const QModelIndex &/*index*/) const
{
    return false;
}

QModelIndex DumpView::moveCursor(CursorAction /*cursorAction*/, 
        Qt::KeyboardModifiers /*modifiers*/)
{
    // FIXME(MED): need to implement movement using cursor
    return currentIndex();
}

void DumpView::setSelection(const QRect &/*rect*/,
        QItemSelectionModel::SelectionFlags flags)
{
    // FIXME(HI): calculate indexes using rect
    selectionModel()->select(QModelIndex(), flags);
}

int DumpView::verticalOffset() const
{
    return verticalScrollBar()->value();
}

QRegion DumpView::visualRegionForSelection(
        const QItemSelection &/*selection*/) const
{
    // FIXME(HI)
    return QRegion(rect());
}

//protected slots:
void DumpView::dataChanged(const QModelIndex &/*topLeft*/, 
        const QModelIndex &/*bottomRight*/,
        const QVector<int> &/*roles*/)
{
    // FIXME(HI)
    update();
}

void DumpView::selectionChanged(const QItemSelection &/*selected*/, 
        const QItemSelection &/*deselected*/)
{
    // FIXME(HI)
    update();
}

void DumpView::populateDump(QByteArray &dump, int &selOfs, int &selSize, 
        QModelIndex parent)
{
    // FIXME: Use new enum instead of Qt::UserRole
    //! \todo (low): generalize this for any model not just our pkt model

    Q_ASSERT(!parent.isValid());

    qDebug("!!!! %d $$$$", dump.size());

    for(int i = 0; i < model()->rowCount(parent); i++)
    {
        QModelIndex index = model()->index(i, 0, parent);

        Q_ASSERT(index.isValid());

        // Assumption: protocol data is in bytes (not bits)
        qDebug("%d: %d bytes", i, model()->data(index, Qt::UserRole).toByteArray().size());
        dump.append(model()->data(index, Qt::UserRole).toByteArray());

    }

    if (selectionModel()->selectedIndexes().size())
    {
        int j, bits;
        QModelIndex    index;

        Q_ASSERT(selectionModel()->selectedIndexes().size() == 1);
        index = selectionModel()->selectedIndexes().at(0);

        if (index.parent().isValid())
        {
            // Field

            // SelOfs = SUM(protocol sizes before selected field's protocol) +
            // SUM(field sizes before selected field)

            selOfs = 0;
            j = index.parent().row() - 1;
            while (j >= 0)
            {
                selOfs += model()->data(index.parent().sibling(j,0), 
                        Qt::UserRole).toByteArray().size();
                j--;
            }

            bits = 0;
            j = index.row() - 1;
            while (j >= 0)
            {
                bits += model()->data(index.sibling(j,0), Qt::UserRole+1).
                    toInt();
                j--;
            }
            selOfs += bits/8;
            selSize = model()->data(index, Qt::UserRole).toByteArray().size();
        }
        else
        {
            // Protocol
            selOfs = 0;
            j = index.row() - 1;
            while (j >= 0)
            {
                selOfs += model()->data(index.sibling(j,0), Qt::UserRole).
                    toByteArray().size();
                j--;
            }
            selSize = model()->data(index, Qt::UserRole).toByteArray().size();
        }
    }
}

// TODO(LOW): rewrite this function - it's a mess!
void DumpView::paintEvent(QPaintEvent* /*event*/)
{
    QStylePainter    painter(viewport());
    QRect            offsetRect = mOffsetPaneTopRect;
    QRect            dumpRect = mDumpPaneTopRect;
    QRect            asciiRect = mAsciiPaneTopRect;
    QPalette        pal = palette();
    static QByteArray        data;
    //QByteArray        ba;
    int                selOfs = -1, selSize;
    int                curSelOfs, curSelSize;

    qDebug("dumpview::paintEvent");

    // FIXME(LOW): unable to set the self widget's font in constructor
    painter.setFont(QFont("Courier"));

    // Qt automatically clears the background before we are called
    // QWidget::paintEvent doc:
    //   When the paint event occurs, the update region has normally
    //   been erased, so you are painting on the widget's background.

    if (model())
    {
        data.clear();
        populateDump(data, selOfs, selSize);
    }

    // display the offset, dump and ascii panes 8 + 8 bytes on a line
    for (int i = 0; i < data.size(); i+=16)
    {
        QString    dumpStr, asciiStr;    

        //ba = data.mid(i, 16);

        // display offset
        painter.drawItemText(offsetRect, Qt::AlignLeft | Qt::AlignTop, pal,
            true, QString("%1").arg(i, 4, 16, QChar('0')), QPalette::WindowText);
        // construct the dumpStr and asciiStr
        for (int j = i; (j < (i+16)) && (j < data.size()); j++)
        {
            unsigned char c = data.at(j);

            // extra space after 8 bytes
            if (((j+8) % 16) == 0)
            {
                dumpStr.append(" ");
                asciiStr.append(" ");
            }

            dumpStr.append(QString("%1").arg((uint)c, 2, 16, QChar('0')).
                toUpper()).append(" ");

            if (isPrintable(c))
                asciiStr.append(QChar(c));
            else
                asciiStr.append(QChar('.'));
        }

        // display dump
        painter.drawItemText(dumpRect, Qt::AlignLeft | Qt::AlignTop, pal,
            true, dumpStr, QPalette::WindowText);

        // display ascii
        painter.drawItemText(asciiRect, Qt::AlignLeft | Qt::AlignTop, pal,
            true, asciiStr, QPalette::WindowText);

        // if no selection, skip selection painting
        if (selOfs < 0)
            goto _next;

        // Check overlap between current row and selection
        {
            QRect r1(i, 0, qMin(16, data.size()-i), 8);
            QRect s1(selOfs, 0, selSize, 8);
            if (r1.intersects(s1))
            {
                QRect t = r1.intersected(s1);

                curSelOfs = t.x();
                curSelSize = t.width();
            }
            else
                curSelSize = 0;

        }

        // overpaint selection on current row (if any)
        if (curSelSize > 0)
        {
            QRect r;
            QString selectedAsciiStr, selectedDumpStr;

            qDebug("dumpview::paintEvent - Highlighted (%d, %d)", 
                    curSelOfs, curSelSize);

            // construct the dumpStr and asciiStr
            for (int k = curSelOfs; (k < (curSelOfs + curSelSize)); k++)
            {
                unsigned char c = data.at(k);

                // extra space after 8 bytes
                if (((k+8) % 16) == 0)
                {
                    // Avoid adding space at the start for fields starting
                    // at second column 8 byte boundary
                    if (k!=curSelOfs)
                    {
                        selectedDumpStr.append(" ");
                        selectedAsciiStr.append(" ");
                    }
                }

                selectedDumpStr.append(QString("%1").arg((uint)c, 2, 16, 
                            QChar('0')).toUpper()).append(" ");

                if (isPrintable(c))
                    selectedAsciiStr.append(QChar(c));
                else
                    selectedAsciiStr.append(QChar('.'));
            }

            // display dump
            r = dumpRect;
            if ((curSelOfs - i) < 8)
                r.translate(mCharWidth*(curSelOfs-i)*3, 0);
            else
                r.translate(mCharWidth*((curSelOfs-i)*3+1), 0);

            // adjust width taking care of selection stretching between
            // the two 8byte columns
            if (( (curSelOfs-i) < 8 ) && ( (curSelOfs-i+curSelSize) > 8 ))
                r.setWidth((curSelSize * 3 + 1) * mCharWidth);
            else
                r.setWidth((curSelSize * 3) * mCharWidth);

            painter.fillRect(r, pal.highlight());
            painter.drawItemText(r, Qt::AlignLeft | Qt::AlignTop, pal,
                true, selectedDumpStr, QPalette::HighlightedText);

            // display ascii
            r = asciiRect;
            if ((curSelOfs - i) < 8)
                r.translate(mCharWidth*(curSelOfs-i)*1, 0);
            else
                r.translate(mCharWidth*((curSelOfs-i)*1+1), 0);

            // adjust width taking care of selection stretching between
            // the two 8byte columns
            if (( (curSelOfs-i) < 8 ) && ( (curSelOfs-i+curSelSize) > 8 ))
                r.setWidth((curSelSize * 1 + 1) * mCharWidth);
            else
                r.setWidth((curSelSize * 1) * mCharWidth);

            painter.fillRect(r, pal.highlight());
            painter.drawItemText(r, Qt::AlignLeft | Qt::AlignTop, pal,
                true, selectedAsciiStr, QPalette::HighlightedText);
        }

_next:    
        // move the rects down
        offsetRect.translate(0, mLineHeight);
        dumpRect.translate(0, mLineHeight);
        asciiRect.translate(0, mLineHeight);
    }    
}

