#include "StringsWidget.h"
#include "ui_StringsWidget.h"
#include "core/MainWindow.h"
#include "common/Helpers.h"
#include "WidgetShortcuts.h"

#include <QClipboard>
#include <QMenu>
#include <QModelIndex>
#include <QShortcut>

StringsModel::StringsModel(QList<StringDescription> *strings, QObject *parent)
    : AddressableItemModel<QAbstractListModel>(parent),
      strings(strings)
{
}

int StringsModel::rowCount(const QModelIndex &) const
{
    return strings->count();
}

int StringsModel::columnCount(const QModelIndex &) const
{
    return StringsModel::ColumnCount;
}

QVariant StringsModel::data(const QModelIndex &index, int role) const
{
    if (index.row() >= strings->count())
        return QVariant();

    const StringDescription &str = strings->at(index.row());

    switch (role) {
    case Qt::DisplayRole:
        switch (index.column()) {
        case StringsModel::OffsetColumn:
            return RAddressString(str.vaddr);
        case StringsModel::StringColumn:
            return str.string;
        case StringsModel::TypeColumn:
            return str.type.toUpper();
        case StringsModel::LengthColumn:
            return QString::number(str.length);
        case StringsModel::SizeColumn:
            return QString::number(str.size);
        case StringsModel::SectionColumn:
            return str.section;
        default:
            return QVariant();
        }
    case StringDescriptionRole:
        return QVariant::fromValue(str);
    default:
        return QVariant();
    }
}

QVariant StringsModel::headerData(int section, Qt::Orientation, int role) const
{
    switch (role) {
    case Qt::DisplayRole:
        switch (section) {
        case StringsModel::OffsetColumn:
            return tr("Address");
        case StringsModel::StringColumn:
            return tr("String");
        case StringsModel::TypeColumn:
            return tr("Type");
        case StringsModel::LengthColumn:
            return tr("Length");
        case StringsModel::SizeColumn:
            return tr("Size");
        case StringsModel::SectionColumn:
            return tr("Section");
        default:
            return QVariant();
        }
    default:
        return QVariant();
    }
}

RVA StringsModel::address(const QModelIndex &index) const
{
    const StringDescription &str = strings->at(index.row());
    return str.vaddr;
}

const StringDescription *StringsModel::description(const QModelIndex &index) const
{
    return &strings->at(index.row());
}

StringsProxyModel::StringsProxyModel(StringsModel *sourceModel, QObject *parent)
    : AddressableFilterProxyModel(sourceModel, parent)
{
    setFilterCaseSensitivity(Qt::CaseInsensitive);
    setSortCaseSensitivity(Qt::CaseInsensitive);
}

bool StringsProxyModel::filterAcceptsRow(int row, const QModelIndex &parent) const
{
    QModelIndex index = sourceModel()->index(row, 0, parent);
    StringDescription str = index.data(StringsModel::StringDescriptionRole).value<StringDescription>();
    if (selectedSection.isEmpty())
        return str.string.contains(filterRegExp());
    else
        return selectedSection == str.section && str.string.contains(filterRegExp());
}

bool StringsProxyModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
    auto model = static_cast<StringsModel *>(sourceModel());
    auto leftStr = model->description(left);
    auto rightStr = model->description(right);

    switch (left.column()) {
    case StringsModel::OffsetColumn:
        return leftStr->vaddr < rightStr->vaddr;
    case StringsModel::StringColumn: // sort by string
        return leftStr->string < rightStr->string;
    case StringsModel::TypeColumn: // sort by type
        return leftStr->type < rightStr->type;
    case StringsModel::SizeColumn: // sort by size
        return leftStr->size < rightStr->size;
    case StringsModel::LengthColumn: // sort by length
        return leftStr->length < rightStr->length;
    case StringsModel::SectionColumn:
        return leftStr->section < rightStr->section;
    default:
        break;
    }

    // fallback
    return leftStr->vaddr < rightStr->vaddr;
}

StringsWidget::StringsWidget(MainWindow *main) :
    CutterDockWidget(main),
    ui(new Ui::StringsWidget),
    tree(new CutterTreeWidget(this))
{
    ui->setupUi(this);
    ui->quickFilterView->setLabelText(tr("Section:"));

    // Add Status Bar footer
    tree->addStatusBar(ui->verticalLayout);

    qhelpers::setVerticalScrollMode(ui->stringsTreeView);

    // Shift-F12 to toggle strings window
    QShortcut *toggle_shortcut = new QShortcut(widgetShortcuts["StringsWidget"], main);
    connect(toggle_shortcut, &QShortcut::activated, this, [ = ] () {
        toggleDockWidget(true);
    });

    connect(ui->actionCopy_String, SIGNAL(triggered()), this, SLOT(on_actionCopy()));

    ui->actionFilter->setShortcut(QKeySequence::Find);

    ui->stringsTreeView->setContextMenuPolicy(Qt::CustomContextMenu);

    model = new StringsModel(&strings, this);
    proxyModel = new StringsProxyModel(model, this);
    ui->stringsTreeView->setMainWindow(main);
    ui->stringsTreeView->setModel(proxyModel);
    ui->stringsTreeView->sortByColumn(-1, Qt::AscendingOrder);

    //
    auto menu = ui->stringsTreeView->getItemContextMenu();
    menu->addAction(ui->actionCopy_String);

    connect(ui->quickFilterView, SIGNAL(filterTextChanged(const QString &)), proxyModel,
            SLOT(setFilterWildcard(const QString &)));

    connect(ui->quickFilterView, &ComboQuickFilterView::filterTextChanged, this, [this] {
        tree->showItemsNumber(proxyModel->rowCount());
    });

    QShortcut *searchShortcut = new QShortcut(QKeySequence::Find, this);
    connect(searchShortcut, &QShortcut::activated, ui->quickFilterView,
            &ComboQuickFilterView::showFilter);
    searchShortcut->setContext(Qt::WidgetWithChildrenShortcut);

    QShortcut *clearShortcut = new QShortcut(QKeySequence(Qt::Key_Escape), this);
    connect(clearShortcut, &QShortcut::activated, this, [this]() {
        ui->quickFilterView->clearFilter();
        ui->stringsTreeView->setFocus();
    });
    clearShortcut->setContext(Qt::WidgetWithChildrenShortcut);

    connect(Core(), &CutterCore::refreshAll, this, &StringsWidget::refreshStrings);
    connect(Core(), &CutterCore::codeRebased, this, &StringsWidget::refreshStrings);

    connect(
        ui->quickFilterView->comboBox(), &QComboBox::currentTextChanged, this,
        [this]() {
            proxyModel->selectedSection = ui->quickFilterView->comboBox()->currentData().toString();
            proxyModel->setFilterRegExp(proxyModel->filterRegExp());
            tree->showItemsNumber(proxyModel->rowCount());
        }
    );

    auto header = ui->stringsTreeView->header();
    header->setSectionResizeMode(QHeaderView::ResizeMode::ResizeToContents);
    header->setSectionResizeMode(StringsModel::StringColumn, QHeaderView::ResizeMode::Stretch);
    header->setStretchLastSection(false);
    header->setResizeContentsPrecision(256);
}

StringsWidget::~StringsWidget() {}

void StringsWidget::refreshStrings()
{
    if (task) {
        task->wait();
    }

    task = QSharedPointer<StringsTask>(new StringsTask());
    connect(task.data(), &StringsTask::stringSearchFinished, this,
            &StringsWidget::stringSearchFinished);
    Core()->getAsyncTaskManager()->start(task);

    refreshSectionCombo();
}

void StringsWidget::refreshSectionCombo()
{
    QComboBox *combo = ui->quickFilterView->comboBox();

    combo->clear();
    combo->addItem(tr("(all)"));

    for (const QString &section : Core()->getSectionList()) {
        combo->addItem(section, section);
    }

    proxyModel->selectedSection.clear();
}

void StringsWidget::stringSearchFinished(const QList<StringDescription> &strings)
{
    model->beginResetModel();
    this->strings = strings;
    model->endResetModel();

    tree->showItemsNumber(proxyModel->rowCount());

    task.clear();
}

void StringsWidget::on_actionCopy()
{
    QModelIndex current_item = ui->stringsTreeView->currentIndex();
    int row = current_item.row();

    QModelIndex index;

    index = ui->stringsTreeView->model()->index(row, 1);

    QClipboard *clipboard = QApplication::clipboard();
    clipboard->setText(index.data().toString());
}
