#include "SongGroupEditor.hpp"
#include "MainWindow.hpp"
#include "amuse/SongConverter.hpp"

class PageDataChangeUndoCommand : public EditorUndoCommand
{
    bool m_drum;
    uint8_t m_prog;
    int m_column;
    int m_undoVal, m_redoVal;
    bool m_undid = false;
public:
    explicit PageDataChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
                                       bool drum, uint8_t prog, int column, int redoVal)
    : EditorUndoCommand(node, text), m_drum(drum), m_prog(prog), m_column(column), m_redoVal(redoVal) {}
    void undo()
    {
        m_undid = true;
        amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
        auto& map = m_drum ? index.m_drumPages : index.m_normPages;
        amuse::SongGroupIndex::PageEntry& entry = map[m_prog];

        switch (m_column)
        {
        case 0:
        {
            auto nh = map.extract(m_prog);
            nh.key() = m_undoVal;
            m_prog = m_undoVal;
            map.insert(std::move(nh));
            break;
        }
        case 1:
            entry.objId.id = m_undoVal;
            break;
        case 2:
            entry.priority = m_undoVal;
            break;
        case 3:
            entry.maxVoices = m_undoVal;
            break;
        default:
            break;
        }

        EditorUndoCommand::undo();
    }
    void redo()
    {
        amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
        auto& map = m_drum ? index.m_drumPages : index.m_normPages;
        amuse::SongGroupIndex::PageEntry& entry = map[m_prog];

        switch (m_column)
        {
        case 0:
        {
            auto nh = map.extract(m_prog);
            m_undoVal = m_prog;
            nh.key() = m_redoVal;
            m_prog = m_redoVal;
            map.insert(std::move(nh));
            break;
        }
        case 1:
            m_undoVal = entry.objId.id;
            entry.objId.id = m_redoVal;
            break;
        case 2:
            m_undoVal = entry.priority;
            entry.priority = m_redoVal;
            break;
        case 3:
            m_undoVal = entry.maxVoices;
            entry.maxVoices = m_redoVal;
            break;
        default:
            break;
        }

        if (m_undid)
            EditorUndoCommand::redo();
    }
};

class SetupDataChangeUndoCommand : public EditorUndoCommand
{
    amuse::SongId m_song;
    int m_row, m_column;
    int m_undoVal, m_redoVal;
    bool m_undid = false;
public:
    explicit SetupDataChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
                                        amuse::SongId song, int row, int column, int redoVal)
    : EditorUndoCommand(node, text), m_song(song), m_row(row), m_column(column), m_redoVal(redoVal) {}
    void undo()
    {
        m_undid = true;
        amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
        auto& map = index.m_midiSetups;
        std::array<amuse::SongGroupIndex::MIDISetup, 16>& entry = map[m_song];

        switch (m_column)
        {
        case 0:
            entry[m_row].programNo = m_undoVal;
            break;
        case 2:
            entry[m_row].volume = m_undoVal;
            break;
        case 3:
            entry[m_row].panning = m_undoVal;
            break;
        case 4:
            entry[m_row].reverb = m_undoVal;
            break;
        case 5:
            entry[m_row].chorus = m_undoVal;
            break;
        default:
            break;
        }

        EditorUndoCommand::undo();
    }
    void redo()
    {
        amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
        auto& map = index.m_midiSetups;
        std::array<amuse::SongGroupIndex::MIDISetup, 16>& entry = map[m_song];

        switch (m_column)
        {
        case 0:
            m_undoVal = entry[m_row].programNo;
            entry[m_row].programNo = m_redoVal;
            break;
        case 2:
            m_undoVal = entry[m_row].volume;
            entry[m_row].volume = m_redoVal;
            break;
        case 3:
            m_undoVal = entry[m_row].panning;
            entry[m_row].panning = m_redoVal;
            break;
        case 4:
            m_undoVal = entry[m_row].reverb;
            entry[m_row].reverb = m_redoVal;
            break;
        case 5:
            m_undoVal = entry[m_row].chorus;
            entry[m_row].chorus = m_redoVal;
            break;
        default:
            break;
        }

        if (m_undid)
            EditorUndoCommand::redo();
    }
};

class SongNameChangeUndoCommand : public EditorUndoCommand
{
    amuse::SongId m_song;
    std::string m_undoVal, m_redoVal;
    bool m_undid = false;
public:
    explicit SongNameChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
                                        amuse::SongId song, std::string_view redoVal)
    : EditorUndoCommand(node, text), m_song(song), m_redoVal(redoVal) {}
    void undo()
    {
        m_undid = true;
        g_MainWindow->projectModel()->setIdDatabases(m_node.get());
        amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
        auto& map = index.m_midiSetups;

        amuse::SongId newId = g_MainWindow->projectModel()->exchangeSongId(m_song, m_undoVal);

        auto nh = map.extract(m_song);
        nh.key() = newId;
        m_song = newId;
        map.insert(std::move(nh));

        EditorUndoCommand::undo();
    }
    void redo()
    {
        g_MainWindow->projectModel()->setIdDatabases(m_node.get());
        amuse::SongGroupIndex& index = *static_cast<ProjectModel::SongGroupNode*>(m_node.get())->m_index;
        auto& map = index.m_midiSetups;

        m_undoVal = amuse::SongId::CurNameDB->resolveNameFromId(m_song);
        amuse::SongId newId = g_MainWindow->projectModel()->exchangeSongId(m_song, m_redoVal);

        auto nh = map.extract(m_song);
        nh.key() = newId;
        m_song = newId;
        map.insert(std::move(nh));

        if (m_undid)
            EditorUndoCommand::redo();
    }
};

class SongMIDIPathChangeUndoCommand : public EditorUndoCommand
{
    amuse::SongId m_song;
    QString m_undoVal, m_redoVal;
    bool m_undid = false;
public:
    explicit SongMIDIPathChangeUndoCommand(ProjectModel::SongGroupNode* node, const QString& text,
                                           amuse::SongId song, QString redoVal)
    : EditorUndoCommand(node, text), m_song(song), m_redoVal(redoVal) {}
    void undo()
    {
        m_undid = true;
        g_MainWindow->projectModel()->setMIDIPathOfSong(m_song, m_undoVal);
        EditorUndoCommand::undo();
    }
    void redo()
    {
        m_undoVal = g_MainWindow->projectModel()->getMIDIPathOfSong(m_song);
        g_MainWindow->projectModel()->setMIDIPathOfSong(m_song, m_redoVal);
        if (m_undid)
            EditorUndoCommand::redo();
    }
};

PageObjectDelegate::PageObjectDelegate(QObject* parent)
: BaseObjectDelegate(parent) {}

ProjectModel::INode* PageObjectDelegate::getNode(const QAbstractItemModel* __model, const QModelIndex& index) const
{
    const PageModel* model = static_cast<const PageModel*>(__model);
    auto entry = model->m_sorted[index.row()];
    if (entry->second.objId.id.id == 0xffff)
        return nullptr;
    ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
    return group->pageObjectNodeOfId(entry->second.objId.id);
}

QWidget* PageObjectDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    const PageModel* model = static_cast<const PageModel*>(index.model());
    ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(model->m_node.get());
    EditorFieldPageObjectNode* cb = new EditorFieldPageObjectNode(group, parent);
    connect(cb, SIGNAL(currentIndexChanged(int)), this, SLOT(objIndexChanged()));
    return cb;
}

void PageObjectDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
    int idx = 0;
    if (ProjectModel::BasePoolObjectNode* node = static_cast<ProjectModel::BasePoolObjectNode*>(getNode(index.model(), index)))
        idx = g_MainWindow->projectModel()->getPageObjectProxy()->mapFromSource(g_MainWindow->projectModel()->index(node)).row();
    static_cast<EditorFieldPageObjectNode*>(editor)->setCurrentIndex(idx);
    if (static_cast<EditorFieldPageObjectNode*>(editor)->shouldPopupOpen())
        QApplication::postEvent(editor, new QEvent(QEvent::User));
}

void PageObjectDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
{
    const PageModel* model = static_cast<const PageModel*>(m);
    auto entry = model->m_sorted[index.row()];
    ProjectModel::BasePoolObjectNode* node = static_cast<EditorFieldPageObjectNode*>(editor)->currentNode();
    amuse::ObjectId id;
    if (node)
        id = node->id();
    if (id == entry->second.objId.id)
    {
        emit m->dataChanged(index, index);
        return;
    }
    g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(model->m_node.get(),
        tr("Change %1").arg(m->headerData(1, Qt::Horizontal).toString()), model->m_drum, entry->first, 1, id.id));
    emit m->dataChanged(index, index);
}

void PageObjectDelegate::objIndexChanged()
{
    emit commitData(static_cast<QWidget*>(sender()));
}

void MIDIFileFieldWidget::buttonPressed()
{
    if (m_le.text().isEmpty())
        m_dialog.setDirectory(g_MainWindow->projectModel()->dir());
    else
        m_dialog.setDirectory(QFileInfo(g_MainWindow->projectModel()->dir().absoluteFilePath(m_le.text())).path());
    m_dialog.open(this, SLOT(fileDialogOpened(const QString&)));
}

void MIDIFileFieldWidget::fileDialogOpened(const QString& path)
{
    m_le.setText(g_MainWindow->projectModel()->dir().relativeFilePath(path));
    emit pathChanged();
}

MIDIFileFieldWidget::MIDIFileFieldWidget(QWidget* parent)
: QWidget(parent), m_button(tr("Browse")),
  m_dialog(this, tr("Open Song File"), {}, QStringLiteral("Songs(*.mid *.son *.sng)"))
{
    QHBoxLayout* layout = new QHBoxLayout;
    layout->addWidget(&m_le);
    layout->addWidget(&m_button);
    layout->setContentsMargins(QMargins());
    layout->setSpacing(0);
    setLayout(layout);

    connect(&m_le, SIGNAL(returnPressed()), this, SIGNAL(pathChanged()));
    connect(&m_button, SIGNAL(clicked(bool)), this, SLOT(buttonPressed()));

    m_dialog.setFileMode(QFileDialog::ExistingFile);
}

QWidget* MIDIFileDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
    MIDIFileFieldWidget* field = new MIDIFileFieldWidget(parent);
    connect(field, SIGNAL(pathChanged()), this, SLOT(pathChanged()));
    return field;
}

void MIDIFileDelegate::destroyEditor(QWidget *editor, const QModelIndex &index) const
{
    QTableView* table = static_cast<QTableView*>(editor->parentWidget()->parentWidget());
    editor->deleteLater();
    emit const_cast<QAbstractItemModel*>(index.model())->dataChanged(index, index);
    table->setCurrentIndex(index);
}

void MIDIFileDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
{
    MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor);
    const SetupListModel* model = static_cast<const SetupListModel*>(index.model());
    auto entry = model->m_sorted[index.row()];
    widget->setPath(g_MainWindow->projectModel()->getMIDIPathOfSong(entry->first));
}

void MIDIFileDelegate::setModelData(QWidget* editor, QAbstractItemModel* m, const QModelIndex& index) const
{
    MIDIFileFieldWidget* widget = static_cast<MIDIFileFieldWidget*>(editor);
    const SetupListModel* model = static_cast<const SetupListModel*>(index.model());
    auto entry = model->m_sorted[index.row()];
    if (g_MainWindow->projectModel()->getMIDIPathOfSong(entry->first) == widget->path())
    {
        emit m->dataChanged(index, index);
        return;
    }
    g_MainWindow->pushUndoCommand(new SongMIDIPathChangeUndoCommand(model->m_node.get(),
        tr("Change MIDI Path"), entry->first, widget->path()));
    emit m->dataChanged(index, index);
}

bool MIDIFileDelegate::editorEvent(QEvent* event, QAbstractItemModel* model,
                                   const QStyleOptionViewItem& option, const QModelIndex& index)
{
    if (event->type() == QEvent::MouseButtonPress)
    {
        QMouseEvent* ev = static_cast<QMouseEvent*>(event);
        if (ev->button() == Qt::RightButton)
        {
            QString path = index.data().toString();
            if (path.isEmpty())
                return false;

            ContextMenu* menu = new ContextMenu;

            QAction* midiAction = new QAction(tr("Save As MIDI"), menu);
            midiAction->setData(path);
            midiAction->setIcon(QIcon::fromTheme(QStringLiteral("file-save")));
            connect(midiAction, SIGNAL(triggered()), this, SLOT(doExportMIDI()));
            menu->addAction(midiAction);

            QAction* sngAction = new QAction(tr("Save As SNG"), menu);
            sngAction->setData(path);
            sngAction->setIcon(QIcon::fromTheme(QStringLiteral("file-save")));
            connect(sngAction, SIGNAL(triggered()), this, SLOT(doExportSNG()));
            menu->addAction(sngAction);

            menu->popup(ev->globalPos());
        }
    }
    return false;
}

void MIDIFileDelegate::doExportMIDI()
{
    QAction* act = static_cast<QAction*>(sender());
    QString path = act->data().toString();
    if (path.isEmpty())
        return;

    QString inPath = g_MainWindow->projectModel()->dir().absoluteFilePath(path);
    std::vector<uint8_t> data;
    {
        QFile fi(inPath);
        if (!fi.open(QFile::ReadOnly))
        {
            g_MainWindow->uiMessenger().critical(tr("File Error"),
                tr("Unable to open %1 for reading: %1").arg(inPath).arg(fi.errorString()));
            return;
        }
        auto d = fi.readAll();
        data.resize(d.size());
        memcpy(&data[0], d.data(), d.size());
    }

    if (!memcmp(data.data(), "MThd", 4))
    {
        //data = amuse::SongConverter::MIDIToSong(data, 1, true);
    }
    else
    {
        bool isBig;
        if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
        {
            g_MainWindow->uiMessenger().critical(tr("File Error"),
                tr("Invalid song data at %1").arg(inPath));
            return;
        }
        int version;
        data = amuse::SongConverter::SongToMIDI(data.data(), version, isBig);
    }

    QFileInfo inInfo(inPath);
    QString outPath =
        QFileDialog::getSaveFileName(g_MainWindow, tr("Export MIDI"),
        QFileInfo(inInfo.path(), inInfo.completeBaseName() + QStringLiteral(".mid")).filePath(), tr("MIDI(*.mid)"));
    if (outPath.isEmpty())
        return;
    QFile fo(outPath);
    if (!fo.open(QFile::WriteOnly))
    {
        g_MainWindow->uiMessenger().critical(tr("File Error"),
            tr("Unable to open %1 for writing: %1").arg(outPath).arg(fo.errorString()));
        return;
    }
    fo.write((char*)data.data(), data.size());
}

void MIDIFileDelegate::doExportSNG()
{
    QAction* act = static_cast<QAction*>(sender());
    QString path = act->data().toString();
    if (path.isEmpty())
        return;

    QString inPath = g_MainWindow->projectModel()->dir().absoluteFilePath(path);
    std::vector<uint8_t> data;
    {
        QFile fi(inPath);
        if (!fi.open(QFile::ReadOnly))
        {
            g_MainWindow->uiMessenger().critical(tr("File Error"),
                tr("Unable to open %1 for reading: %1").arg(inPath).arg(fi.errorString()));
            return;
        }
        auto d = fi.readAll();
        data.resize(d.size());
        memcpy(&data[0], d.data(), d.size());
    }

    if (!memcmp(data.data(), "MThd", 4))
    {
        data = amuse::SongConverter::MIDIToSong(data, 1, true);
        if (data.empty())
        {
            g_MainWindow->uiMessenger().critical(tr("File Error"),
                tr("Invalid MIDI data at %1").arg(inPath));
            return;
        }
    }
    else
    {
        bool isBig;
        if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
        {
            g_MainWindow->uiMessenger().critical(tr("File Error"),
                tr("Invalid song data at %1").arg(inPath));
            return;
        }
    }

    QFileInfo inInfo(inPath);
    QString outPath =
        QFileDialog::getSaveFileName(g_MainWindow, tr("Export SNG"),
        QFileInfo(inInfo.path(), inInfo.completeBaseName() + QStringLiteral(".sng")).filePath(), tr("Song(*.sng)"));
    if (outPath.isEmpty())
        return;
    QFile fo(outPath);
    if (!fo.open(QFile::WriteOnly))
    {
        g_MainWindow->uiMessenger().critical(tr("File Error"),
            tr("Unable to open %1 for writing: %1").arg(outPath).arg(fo.errorString()));
        return;
    }
    fo.write((char*)data.data(), data.size());
}

void MIDIFileDelegate::pathChanged()
{
    emit commitData(static_cast<MIDIFileFieldWidget*>(sender()));
}

MIDIFileDelegate::MIDIFileDelegate(QObject* parent)
: QStyledItemDelegate(parent) {}

std::unordered_map<uint8_t, amuse::SongGroupIndex::PageEntry>& PageModel::_getMap() const
{
    return m_drum ? m_node->m_index->m_drumPages : m_node->m_index->m_normPages;
}

void PageModel::_buildSortedList()
{
    m_sorted.clear();
    if (!m_node)
        return;
    auto& map = _getMap();
    m_sorted.reserve(map.size());
    for (auto it = map.begin() ; it != map.end() ; ++it)
        m_sorted.emplace_back(it);
    std::sort(m_sorted.begin(), m_sorted.end());
}

QModelIndex PageModel::_indexOfProgram(uint8_t prog) const
{
    auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), prog);
    if (search == m_sorted.cend() || search->m_it->first != prog)
        return QModelIndex();
    else
        return createIndex(search - m_sorted.begin(), 0);
}

int PageModel::_hypotheticalIndexOfProgram(uint8_t prog) const
{
    auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), prog);
    return search - m_sorted.begin();
}

void PageModel::loadData(ProjectModel::SongGroupNode* node)
{
    beginResetModel();
    m_node = node;
    _buildSortedList();
    endResetModel();
}

void PageModel::unloadData()
{
    beginResetModel();
    m_node.reset();
    m_sorted.clear();
    endResetModel();
}

int PageModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    if (!m_node)
        return 0;
    return int(m_sorted.size()) + 1;
}

int PageModel::columnCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    return 4;
}

QVariant PageModel::data(const QModelIndex& index, int role) const
{
    if (!m_node)
        return QVariant();
    if (index.row() == m_sorted.size())
        return QVariant();

    auto entry = m_sorted[index.row()];

    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch (index.column())
        {
        case 0:
            return entry->first + 1;
        case 1:
        {
            ProjectModel::GroupNode* group = g_MainWindow->projectModel()->getGroupNode(m_node.get());
            if (ProjectModel::BasePoolObjectNode* node = group->pageObjectNodeOfId(entry->second.objId.id))
                return node->text();
            return QVariant();
        }
        case 2:
            return entry->second.priority;
        case 3:
            return entry->second.maxVoices;
        default:
            break;
        }
    }

    return QVariant();
}

bool PageModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!m_node || role != Qt::EditRole)
        return false;

    auto& map = _getMap();
    auto entry = m_sorted[index.row()];

    switch (index.column())
    {
    case 0:
    {
        int prog = value.toInt() - 1;
        if (prog == entry->first)
            return false;
        if (map.find(prog) != map.cend())
        {
            QMessageBox::critical(g_MainWindow, tr("Program Conflict"),
                tr("Program %1 is already defined in table").arg(value.toInt()));
            return false;
        }
        emit layoutAboutToBeChanged();

        g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(m_node.get(),
            tr("Change %1").arg(headerData(0, Qt::Horizontal).toString()), m_drum, entry->first, 0, value.toInt()));

        _buildSortedList();
        QModelIndex newIndex = _indexOfProgram(value.toInt());
        changePersistentIndex(index, newIndex);
        emit layoutChanged();
        emit dataChanged(newIndex, newIndex);
        return true;
    }
    case 1:
        if (entry->second.objId.id == value.toInt())
            return false;
        break;
    case 2:
        if (entry->second.priority == value.toInt())
            return false;
        break;
    case 3:
        if (entry->second.maxVoices == value.toInt())
            return false;
        break;
    default:
        return false;
    }

    g_MainWindow->pushUndoCommand(new PageDataChangeUndoCommand(m_node.get(),
        tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), m_drum,
        entry->first, index.column(), value.toInt()));

    emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});
    return true;
}

QVariant PageModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
    {
        switch (section)
        {
        case 0:
            return tr("Program");
        case 1:
            return tr("Object");
        case 2:
            return tr("Priority");
        case 3:
            return tr("Max Voices");
        default:
            break;
        }
    }
    return QVariant();
}

Qt::ItemFlags PageModel::flags(const QModelIndex& index) const
{
    if (!index.isValid())
        return Qt::NoItemFlags;
    if (index.row() == m_sorted.size())
        return Qt::NoItemFlags;
    return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}

class PageRowUndoCommand : public EditorUndoCommand
{
protected:
    PageTableView* m_view;
    std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>> m_data;
    bool m_undid = false;
    void add()
    {
        m_view->selectionModel()->clearSelection();
        for (const auto& p : m_data)
        {
            int row = static_cast<PageModel*>(m_view->model())->_insertRow(p);
            m_view->selectionModel()->select(QItemSelection(
                m_view->model()->index(row, 0), m_view->model()->index(row, 3)),
                                       QItemSelectionModel::SelectCurrent);
        }
    }
    void del()
    {
        for (auto it = m_data.rbegin(); it != m_data.rend(); ++it)
        {
            *it = static_cast<PageModel*>(m_view->model())->_removeRow(it->first);
        }
    }
    void undo()
    {
        m_undid = true;
        EditorUndoCommand::undo();
    }
    void redo()
    {
        if (m_undid)
            EditorUndoCommand::redo();
    }
public:
    explicit PageRowUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, PageTableView* view,
                                std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>>&& data)
    : EditorUndoCommand(node, text), m_view(view), m_data(std::move(data)) {}
};

class PageRowAddUndoCommand : public PageRowUndoCommand
{
    using base = PageRowUndoCommand;
public:
    explicit PageRowAddUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, PageTableView* view,
                                   std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>>&& data)
    : PageRowUndoCommand(node, text, view, std::move(data)) {}
    void undo() { base::undo(); base::del(); }
    void redo() { base::redo(); base::add(); }
};

class PageRowDelUndoCommand : public PageRowUndoCommand
{
    using base = PageRowUndoCommand;
public:
    explicit PageRowDelUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, PageTableView* view,
                                   std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>>&& data)
    : PageRowUndoCommand(node, text, view, std::move(data)) {}
    void undo() { base::undo(); base::add(); }
    void redo() { base::redo(); base::del(); }
};

int PageModel::_insertRow(const std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>& data)
{
    if (!m_node)
        return 0;
    auto& map = _getMap();
    int idx = _hypotheticalIndexOfProgram(data.first);
    beginInsertRows(QModelIndex(), idx, idx);
    map.emplace(data);
    _buildSortedList();
    endInsertRows();
    return idx;
}

std::pair<uint8_t, amuse::SongGroupIndex::PageEntry> PageModel::_removeRow(uint8_t prog)
{
    std::pair<uint8_t, amuse::SongGroupIndex::PageEntry> ret;
    if (!m_node)
        return ret;
    auto& map = _getMap();
    int idx = _hypotheticalIndexOfProgram(prog);
    beginRemoveRows(QModelIndex(), idx, idx);
    ret.first = prog;
    auto search = map.find(prog);
    if (search != map.cend())
    {
        ret.second = search->second;
        map.erase(search);
    }
    _buildSortedList();
    endRemoveRows();
    return ret;
}

PageModel::PageModel(bool drum, QObject* parent)
: QAbstractTableModel(parent), m_drum(drum)
{}

std::unordered_map<amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>& SetupListModel::_getMap() const
{
    return m_node->m_index->m_midiSetups;
}

void SetupListModel::_buildSortedList()
{
    m_sorted.clear();
    if (!m_node)
        return;
    auto& map = _getMap();
    m_sorted.reserve(map.size());
    for (auto it = map.begin() ; it != map.end() ; ++it)
        m_sorted.emplace_back(it);
    std::sort(m_sorted.begin(), m_sorted.end());
}

QModelIndex SetupListModel::_indexOfSong(amuse::SongId id) const
{
    auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), id);
    if (search == m_sorted.cend() || search->m_it->first != id)
        return QModelIndex();
    else
        return createIndex(search - m_sorted.begin(), 0);
}

int SetupListModel::_hypotheticalIndexOfSong(const std::string& songName) const
{
    auto search = std::lower_bound(m_sorted.cbegin(), m_sorted.cend(), songName);
    return search - m_sorted.begin();
}

void SetupListModel::loadData(ProjectModel::SongGroupNode* node)
{
    beginResetModel();
    m_node = node;
    g_MainWindow->projectModel()->setIdDatabases(m_node.get());
    _buildSortedList();
    endResetModel();
}

void SetupListModel::unloadData()
{
    beginResetModel();
    m_node = nullptr;
    m_sorted.clear();
    endResetModel();
}

int SetupListModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    if (!m_node)
        return 0;
    return int(m_sorted.size()) + 1;
}

int SetupListModel::columnCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    return 2;
}

QVariant SetupListModel::data(const QModelIndex& index, int role) const
{
    if (!m_node)
        return QVariant();
    if (index.row() == m_sorted.size())
        return QVariant();

    auto entry = m_sorted[index.row()];

    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        if (index.column() == 0)
        {
            g_MainWindow->projectModel()->setIdDatabases(m_node.get());
            return amuse::SongId::CurNameDB->resolveNameFromId(entry->first.id).data();
        }
        else if (index.column() == 1)
        {
            return g_MainWindow->projectModel()->getMIDIPathOfSong(entry.m_it->first);
        }
    }

    return QVariant();
}

bool SetupListModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!m_node || role != Qt::EditRole || index.column() != 0)
        return false;

    auto entry = m_sorted[index.row()];

    g_MainWindow->projectModel()->setIdDatabases(m_node.get());
    auto utf8key = value.toString().toUtf8();
    std::unordered_map<std::string, amuse::ObjectId>::iterator idIt;
    if ((idIt = amuse::SongId::CurNameDB->m_stringToId.find(utf8key.data())) != amuse::SongId::CurNameDB->m_stringToId.cend())
    {
        if (idIt->second == entry->first)
            return false;
        QMessageBox::critical(g_MainWindow, tr("Song Conflict"),
                              tr("Song %1 is already defined in project").arg(value.toString()));
        return false;
    }
    emit layoutAboutToBeChanged();
    g_MainWindow->pushUndoCommand(new SongNameChangeUndoCommand(m_node.get(),
        tr("Change Song Name"), entry.m_it->first, utf8key.data()));
    _buildSortedList();
    QModelIndex newIndex = _indexOfSong(entry.m_it->first);
    changePersistentIndex(index, newIndex);
    emit layoutChanged();
    emit dataChanged(newIndex, newIndex, {Qt::DisplayRole, Qt::EditRole});

    return true;
}

QVariant SetupListModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
    {
        switch (section)
        {
        case 0:
            return tr("Song");
        case 1:
            return tr("MIDI File");
        default:
            break;
        }
    }
    return QVariant();
}

Qt::ItemFlags SetupListModel::flags(const QModelIndex& index) const
{
    if (index.row() == m_sorted.size())
        return Qt::NoItemFlags;
    return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable;
}

class SetupRowUndoCommand : public EditorUndoCommand
{
protected:
    SetupTableView* m_view;
    std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> m_data;
    bool m_undid = false;
    void add()
    {
        QTableView* listView = m_view->m_listView;
        listView->selectionModel()->clearSelection();
        for (auto& p : m_data)
        {
            int row = static_cast<SetupListModel*>(m_view->m_listView->model())->_insertRow(p);
            listView->selectionModel()->select(QItemSelection(
                listView->model()->index(row, 0), listView->model()->index(row, 1)),
                                             QItemSelectionModel::SelectCurrent);
        }
    }
    void del()
    {
        QTableView* listView = m_view->m_listView;
        for (auto it = m_data.rbegin(); it != m_data.rend(); ++it)
        {
            *it = static_cast<SetupListModel*>(listView->model())->_removeRow(std::get<0>(*it));
        }
    }
    void undo()
    {
        m_undid = true;
        EditorUndoCommand::undo();
    }
    void redo()
    {
        if (m_undid)
            EditorUndoCommand::redo();
    }
public:
    explicit SetupRowUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, SetupTableView* view,
                                 std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>>&& data)
    : EditorUndoCommand(node, text), m_view(view), m_data(std::move(data)) {}
};

class SetupRowAddUndoCommand : public SetupRowUndoCommand
{
    using base = SetupRowUndoCommand;
public:
    explicit SetupRowAddUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, SetupTableView* view,
                                    std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>>&& data)
    : SetupRowUndoCommand(node, text, view, std::move(data)) {}
    void undo() { base::undo(); base::del(); }
    void redo() { base::redo(); base::add(); }
};

class SetupRowDelUndoCommand : public SetupRowUndoCommand
{
    using base = SetupRowUndoCommand;
public:
    explicit SetupRowDelUndoCommand(ProjectModel::SongGroupNode* node, const QString& text, SetupTableView* view,
                                    std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>>&& data)
    : SetupRowUndoCommand(node, text, view, std::move(data)) {}
    void undo() { base::undo(); base::add(); }
    void redo() { base::redo(); base::del(); }
};

int SetupListModel::_insertRow(
    std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>& data)
{
    if (!m_node)
        return 0;
    auto& map = _getMap();
    g_MainWindow->projectModel()->setIdDatabases(m_node.get());
    if (std::get<0>(data).id == 0xffff)
        std::get<0>(data) = amuse::SongId::CurNameDB->generateId(amuse::NameDB::Type::Song);

    g_MainWindow->projectModel()->_allocateSongId(std::get<0>(data), std::get<1>(data));
    int idx = _hypotheticalIndexOfSong(std::get<1>(data));
    beginInsertRows(QModelIndex(), idx, idx);
    map[std::get<0>(data)] = std::get<2>(data);
    _buildSortedList();
    endInsertRows();
    return idx;
}

std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>
    SetupListModel::_removeRow(amuse::SongId id)
{
    std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>> ret = {};
    if (!m_node)
        return ret;

    auto& map = _getMap();
    g_MainWindow->projectModel()->setIdDatabases(m_node.get());
    std::get<0>(ret) = id;
    std::get<1>(ret) = amuse::SongId::CurNameDB->resolveNameFromId(id);
    int idx = _indexOfSong(id).row();
    beginRemoveRows(QModelIndex(), idx, idx);
    auto search = map.find(id);
    if (search != map.end())
    {
        std::get<2>(ret) = search->second;
        g_MainWindow->projectModel()->deallocateSongId(id);
        map.erase(search);
    }
    _buildSortedList();
    endRemoveRows();
    return ret;
}

SetupListModel::SetupListModel(QObject* parent)
: QAbstractTableModel(parent)
{}

void SetupModel::loadData(std::pair<const amuse::SongId, std::array<amuse::SongGroupIndex::MIDISetup, 16>>* data)
{
    beginResetModel();
    m_data = data;
    endResetModel();
}

void SetupModel::unloadData()
{
    beginResetModel();
    m_data = nullptr;
    endResetModel();
}

int SetupModel::rowCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    if (!m_data)
        return 0;
    return 16;
}

int SetupModel::columnCount(const QModelIndex& parent) const
{
    if (parent.isValid())
        return 0;
    return 5;
}

QVariant SetupModel::data(const QModelIndex& index, int role) const
{
    if (!m_data)
        return QVariant();

    auto& entry = m_data->second[index.row()];

    if (role == Qt::DisplayRole || role == Qt::EditRole)
    {
        switch (index.column())
        {
        case 0:
            return entry.programNo + 1;
        case 1:
            return entry.volume;
        case 2:
            return entry.panning;
        case 3:
            return entry.reverb;
        case 4:
            return entry.chorus;
        default:
            break;
        }
    }

    return QVariant();
}

bool SetupModel::setData(const QModelIndex& index, const QVariant& value, int role)
{
    if (!m_data || role != Qt::EditRole)
        return false;

    auto& entry = m_data->second[index.row()];

    int val = value.toInt();
    switch (index.column())
    {
    case 0:
        val -= 1;
        if (entry.programNo == val)
            return false;
        break;
    case 1:
        if (entry.volume == val)
            return false;
        break;
    case 2:
        if (entry.panning == val)
            return false;
        break;
    case 3:
        if (entry.reverb == val)
            return false;
        break;
    case 4:
        if (entry.chorus == val)
            return false;
        break;
    default:
        return false;
    }

    g_MainWindow->pushUndoCommand(new SetupDataChangeUndoCommand(
        static_cast<SongGroupEditor*>(parent())->m_setupList.m_node.get(),
        tr("Change %1").arg(headerData(index.column(), Qt::Horizontal).toString()), m_data->first,
        index.row(), index.column(), val));

    emit dataChanged(index, index, {Qt::DisplayRole, Qt::EditRole});

    return true;
}

QVariant SetupModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    if (role == Qt::DisplayRole)
    {
        if (orientation == Qt::Horizontal)
        {
            switch (section)
            {
            case 0:
                return tr("Program");
            case 1:
                return tr("Volume");
            case 2:
                return tr("Panning");
            case 3:
                return tr("Reverb");
            case 4:
                return tr("Chorus");
            default:
                break;
            }
        }
        else
        {
            return section + 1;
        }
    }
    else if (role == Qt::BackgroundColorRole && orientation == Qt::Vertical)
    {
        if (section == 9)
            return QColor(64, 0, 0);
        return QColor(0, 64, 0);
    }
    return QVariant();
}

Qt::ItemFlags SetupModel::flags(const QModelIndex& index) const
{
    return Qt::ItemIsEnabled | Qt::ItemIsEditable;
}

SetupModel::SetupModel(QObject* parent)
: QAbstractTableModel(parent)
{}

void PageTableView::deleteSelection()
{
    QModelIndexList list = selectionModel()->selectedRows();
    if (list.isEmpty())
        return;
    std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>> data;
    data.reserve(list.size());
    for (QModelIndex idx : list)
        data.push_back(std::make_pair(model()->data(idx).toInt(), amuse::SongGroupIndex::PageEntry{}));
    std::sort(data.begin(), data.end(), [](const auto& a, const auto& b) { return a.first < b.first; });
    g_MainWindow->pushUndoCommand(
        new PageRowDelUndoCommand(static_cast<PageModel*>(model())->m_node.get(),
            data.size() > 1 ? tr("Delete Page Entries") : tr("Delete Page Entry"), this, std::move(data)));
}

void PageTableView::setModel(QAbstractItemModel* model)
{
    QTableView::setModel(model);
    horizontalHeader()->setMinimumSectionSize(75);
    horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed);
    horizontalHeader()->resizeSection(0, 75);
    horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
    horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed);
    horizontalHeader()->resizeSection(2, 75);
    horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed);
    horizontalHeader()->resizeSection(3, 100);
}

PageTableView::PageTableView(QWidget* parent)
: QTableView(parent)
{
    setSelectionBehavior(QAbstractItemView::SelectRows);
    setSelectionMode(QAbstractItemView::ExtendedSelection);
    setGridStyle(Qt::NoPen);

    m_127Delegate.setItemEditorFactory(&m_127Factory);
    m_128Delegate.setItemEditorFactory(&m_128Factory);
    m_255Delegate.setItemEditorFactory(&m_255Factory);

    setItemDelegateForColumn(0, &m_128Delegate);
    setItemDelegateForColumn(1, &m_poDelegate);
    setItemDelegateForColumn(2, &m_255Delegate);
    setItemDelegateForColumn(3, &m_255Delegate);
}

void SetupTableView::setModel(QAbstractItemModel* list, QAbstractItemModel* table)
{
    {
        m_listView->setModel(list);
        auto hheader = m_listView->horizontalHeader();
        hheader->setMinimumSectionSize(200);
        hheader->resizeSection(0, 200);
        hheader->setSectionResizeMode(1, QHeaderView::Stretch);
    }
    {
        m_tableView->setModel(table);
        m_tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
        m_tableView->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed);
    }
}

void SetupTableView::deleteSelection()
{
    QModelIndexList list = m_listView->selectionModel()->selectedRows();
    if (list.isEmpty())
        return;
    std::sort(list.begin(), list.end(), [](const auto& a, const auto& b) { return a.row() < b.row(); });
    std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> data;
    data.reserve(list.size());
    for (QModelIndex idx : list)
    {
        auto& entry = *static_cast<SetupListModel*>(m_listView->model())->m_sorted[idx.row()].m_it;
        data.push_back({entry.first, {}, {}});
    }
    g_MainWindow->pushUndoCommand(
        new SetupRowDelUndoCommand(static_cast<SetupListModel*>(m_listView->model())->m_node.get(),
        data.size() > 1 ? tr("Delete Setup Entries") : tr("Delete Setup Entry"), this, std::move(data)));
}

void SetupTableView::showEvent(QShowEvent* event)
{
    setSizes({width() - 375, 375});
}

SetupTableView::SetupTableView(QWidget* parent)
: QSplitter(parent), m_listView(new QTableView), m_tableView(new QTableView)
{
    setChildrenCollapsible(false);
    setStretchFactor(0, 1);
    setStretchFactor(1, 0);

    addWidget(m_listView);
    addWidget(m_tableView);

    m_listView->setSelectionBehavior(QAbstractItemView::SelectRows);
    m_listView->setSelectionMode(QAbstractItemView::ExtendedSelection);
    m_listView->setGridStyle(Qt::NoPen);
    m_listView->setItemDelegateForColumn(1, &m_midiDelegate);

    m_tableView->setSelectionMode(QAbstractItemView::NoSelection);
    m_tableView->setGridStyle(Qt::NoPen);

    m_127Delegate.setItemEditorFactory(&m_127Factory);
    m_128Delegate.setItemEditorFactory(&m_128Factory);

    m_tableView->setItemDelegateForColumn(0, &m_128Delegate);
    m_tableView->setItemDelegateForColumn(1, &m_127Delegate);
    m_tableView->setItemDelegateForColumn(2, &m_127Delegate);
    m_tableView->setItemDelegateForColumn(3, &m_127Delegate);
    m_tableView->setItemDelegateForColumn(4, &m_127Delegate);
}

void ColoredTabBarStyle::drawControl(QStyle::ControlElement element, const QStyleOption *option,
                                     QPainter *painter, const QWidget *widget) const
{
    if (qobject_cast<const ColoredTabBar*>(widget) && element == QStyle::CE_TabBarTab)
    {
        QStyleOptionTab optionTab = *static_cast<const QStyleOptionTab*>(option);
        switch (optionTab.position)
        {
        case QStyleOptionTab::Beginning:
            optionTab.palette.setColor(QPalette::Button, QColor(0, 64, 0));
            break;
        case QStyleOptionTab::Middle:
            optionTab.palette.setColor(QPalette::Button, QColor(64, 0, 0));
            break;
        default:
            break;
        }
        QProxyStyle::drawControl(element, &optionTab, painter, widget);
    }
    else
        QProxyStyle::drawControl(element, option, painter, widget);
}

ColoredTabBar::ColoredTabBar(QWidget* parent)
: QTabBar(parent)
{
    setDrawBase(false);
}

ColoredTabWidget::ColoredTabWidget(QWidget* parent)
: QTabWidget(parent)
{
    setTabBar(&m_tabBar);
}

void MIDIPlayerWidget::clicked()
{
    if (!m_seq)
    {
        m_seq = g_MainWindow->startSong(m_groupId, m_songId, m_arrData.data());
        if (m_seq)
        {
            m_playAction.setText(tr("Stop"));
            m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconStop.svg")));
        }
    }
    else
    {
        stopped();
    }
}

void MIDIPlayerWidget::stopped()
{
    m_seq->stopSong();
    m_seq.reset();
    m_playAction.setText(tr("Play"));
    m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
}

void MIDIPlayerWidget::resizeEvent(QResizeEvent* event)
{
    m_button.setGeometry(event->size().width() - event->size().height(), 0,
                         event->size().height(), event->size().height());
}

void MIDIPlayerWidget::mouseDoubleClickEvent(QMouseEvent* event)
{
    qobject_cast<QTableView*>(parentWidget()->parentWidget())->setIndexWidget(m_index, nullptr);
    event->ignore();
}

void MIDIPlayerWidget::mousePressEvent(QMouseEvent* event)
{
    if (event->button() == Qt::RightButton)
    {
        QTableView* view = qobject_cast<QTableView*>(parentWidget()->parentWidget());
        QAbstractItemDelegate* delegate = view->itemDelegateForColumn(1);
        delegate->editorEvent(event, view->model(), {}, m_index);
    }
    event->ignore();
}

MIDIPlayerWidget::~MIDIPlayerWidget()
{
    if (m_seq)
        m_seq->stopSong();
}

MIDIPlayerWidget::MIDIPlayerWidget(QModelIndex index, amuse::GroupId gid, amuse::SongId id,
                                   std::vector<uint8_t>&& arrData, QWidget* parent)
: QWidget(parent), m_button(this), m_playAction(tr("Play")), m_index(index),
  m_groupId(gid), m_songId(id), m_arrData(std::move(arrData))
{
    m_playAction.setIcon(QIcon(QStringLiteral(":/icons/IconSoundMacro.svg")));
    m_button.setDefaultAction(&m_playAction);
    connect(&m_playAction, SIGNAL(triggered()), this, SLOT(clicked()));
}

bool SongGroupEditor::loadData(ProjectModel::SongGroupNode* node)
{
    m_normPages.loadData(node);
    m_drumPages.loadData(node);
    m_setupList.loadData(node);
    m_setup.unloadData();
    return true;
}

void SongGroupEditor::unloadData()
{
    m_normPages.unloadData();
    m_drumPages.unloadData();
    m_setupList.unloadData();
    m_setup.unloadData();
}

ProjectModel::INode* SongGroupEditor::currentNode() const
{
    return m_normPages.m_node.get();
}

void SongGroupEditor::resizeEvent(QResizeEvent* ev)
{
    m_tabs.setGeometry(QRect({}, ev->size()));
    m_addRemoveButtons.move(0, ev->size().height() - 32);
}

void SongGroupEditor::doAdd()
{
    if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
    {
        QModelIndex idx = table->selectionModel()->currentIndex();
        int row;
        if (!idx.isValid())
            row = table->model()->rowCount() - 1;
        else
            row = idx.row();

        PageModel* model = static_cast<PageModel*>(table->model());
        int prog = 0;
        if (!model->m_sorted.empty())
        {
            prog = -1;
            if (row < model->m_sorted.size())
            {
                prog = model->m_sorted[row].m_it->first;
                while (prog >= 0 && model->_indexOfProgram(prog).isValid())
                    --prog;
            }
            if (prog == -1)
            {
                prog = 0;
                while (prog < 128 && model->_indexOfProgram(prog).isValid())
                    ++prog;
            }
            if (prog == 128)
                return;
        }

        std::vector<std::pair<uint8_t, amuse::SongGroupIndex::PageEntry>> data;
        data.push_back(std::make_pair(prog, amuse::SongGroupIndex::PageEntry{}));
        g_MainWindow->pushUndoCommand(
            new PageRowAddUndoCommand(model->m_node.get(), tr("Add Page Entry"), table, std::move(data)));

        m_addRemoveButtons.addAction()->setDisabled(table->model()->rowCount() >= 128);
    }
    else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
    {
        SetupListModel* model = static_cast<SetupListModel*>(table->m_listView->model());
        g_MainWindow->projectModel()->setIdDatabases(model->m_node.get());
        std::vector<std::tuple<amuse::SongId, std::string, std::array<amuse::SongGroupIndex::MIDISetup, 16>>> data;
        auto songId = g_MainWindow->projectModel()->allocateSongId();
        data.push_back(std::make_tuple(songId.first, songId.second, std::array<amuse::SongGroupIndex::MIDISetup, 16>{}));
        g_MainWindow->pushUndoCommand(
            new SetupRowAddUndoCommand(model->m_node.get(), tr("Add Setup Entry"), table, std::move(data)));
    }
}

void SongGroupEditor::doSelectionChanged()
{
    if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
        m_addRemoveButtons.removeAction()->setDisabled(table->selectionModel()->selectedRows().isEmpty());
    else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
        m_addRemoveButtons.removeAction()->setDisabled(table->m_listView->selectionModel()->selectedRows().isEmpty());
    g_MainWindow->updateFocus();
}

void SongGroupEditor::doSetupSelectionChanged()
{
    doSelectionChanged();
    if (m_setupTable->m_listView->selectionModel()->selectedRows().isEmpty() || m_setupList.m_sorted.empty())
    {
        m_setup.unloadData();
    }
    else
    {
        auto entry = m_setupList.m_sorted[m_setupTable->m_listView->selectionModel()->selectedRows().last().row()];
        m_setup.loadData(&*entry.m_it);
    }
}

void SongGroupEditor::currentTabChanged(int idx)
{
    if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
    {
        m_addRemoveButtons.addAction()->setDisabled(table->model()->rowCount() >= 128);
        doSelectionChanged();
    }
    else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
    {
        m_addRemoveButtons.addAction()->setDisabled(false);
        doSelectionChanged();
    }
}

void SongGroupEditor::rowsAboutToBeRemoved(const QModelIndex &parent, int first, int last)
{
    for (int i = first; i <= last; ++i)
    {
        auto entry = m_setupList.m_sorted[i];
        if (&*entry.m_it == m_setup.m_data)
        {
            m_setup.unloadData();
            return;
        }
    }
}

void SongGroupEditor::modelAboutToBeReset()
{
    m_setup.unloadData();
}

static std::vector<uint8_t> LoadSongFile(QString path)
{
    QFileInfo fi(path);
    if (!fi.isFile())
        return {};

    std::vector<uint8_t> data;
    {
        QFile f(path);
        if (!f.open(QFile::ReadOnly))
            return {};
        auto d = f.readAll();
        data.resize(d.size());
        memcpy(&data[0], d.data(), d.size());
    }

    if (!memcmp(data.data(), "MThd", 4))
    {
        data = amuse::SongConverter::MIDIToSong(data, 1, true);
    }
    else
    {
        bool isBig;
        if (amuse::SongState::DetectVersion(data.data(), isBig) < 0)
            return {};
    }

    return data;
}

void SongGroupEditor::setupDataChanged()
{
    int idx = 0;
    for (const auto& p : m_setupList.m_sorted)
    {
        QString path = g_MainWindow->projectModel()->getMIDIPathOfSong(p.m_it->first);
        QModelIndex index = m_setupList.index(idx, 1);
        if (m_setupTable->m_listView->isPersistentEditorOpen(index))
        {
            ++idx;
            continue;
        }
        if (path.isEmpty())
        {
            m_setupTable->m_listView->setIndexWidget(index, nullptr);
        }
        else
        {
            MIDIPlayerWidget* w = qobject_cast<MIDIPlayerWidget*>(m_setupTable->m_listView->indexWidget(index));
            if (!w || w->songId() != p.m_it->first)
            {
                std::vector<uint8_t> arrData = LoadSongFile(g_MainWindow->projectModel()->dir().absoluteFilePath(path));
                if (!arrData.empty())
                {
                    MIDIPlayerWidget* newW = new MIDIPlayerWidget(index, m_setupList.m_node->m_id, p.m_it->first,
                        std::move(arrData), m_setupTable->m_listView->viewport());
                    m_setupTable->m_listView->setIndexWidget(index, newW);
                }
                else
                {
                    m_setupTable->m_listView->setIndexWidget(index, nullptr);
                }
            }
        }
        ++idx;
    }
}

AmuseItemEditFlags SongGroupEditor::itemEditFlags() const
{
    if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
        return (table->hasFocus() && !table->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
    else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
        return (table->m_listView->hasFocus() && !table->m_listView->selectionModel()->selectedRows().isEmpty()) ? AmuseItemDelete : AmuseItemNone;
    return AmuseItemNone;
}

void SongGroupEditor::itemDeleteAction()
{
    if (PageTableView* table = qobject_cast<PageTableView*>(m_tabs.currentWidget()))
        table->deleteSelection();
    else if (SetupTableView* table = qobject_cast<SetupTableView*>(m_tabs.currentWidget()))
        table->deleteSelection();
}

SongGroupEditor::SongGroupEditor(QWidget* parent)
: EditorWidget(parent), m_normPages(false, this), m_drumPages(true, this), m_setup(this),
  m_normTable(new PageTableView), m_drumTable(new PageTableView),
  m_setupTable(new SetupTableView), m_tabs(this), m_addRemoveButtons(this)
{
    m_tabs.addTab(m_normTable, tr("Normal Pages"));
    m_tabs.addTab(m_drumTable, tr("Drum Pages"));
    m_tabs.addTab(m_setupTable, tr("MIDI Setups"));

    connect(&m_tabs, SIGNAL(currentChanged(int)), this, SLOT(currentTabChanged(int)));

    m_normTable->setModel(&m_normPages);
    m_drumTable->setModel(&m_drumPages);
    m_setupTable->setModel(&m_setupList, &m_setup);
    connect(m_normTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
            this, SLOT(doSelectionChanged()));
    connect(m_drumTable->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
            this, SLOT(doSelectionChanged()));
    connect(m_setupTable->m_listView->selectionModel(), SIGNAL(selectionChanged(const QItemSelection&, const QItemSelection&)),
            this, SLOT(doSetupSelectionChanged()));

    connect(&m_setupList, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)),
            this, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int)));
    connect(&m_setupList, SIGNAL(modelAboutToBeReset()),
            this, SLOT(modelAboutToBeReset()));
    connect(&m_setupList, SIGNAL(rowsInserted(const QModelIndex&, int, int)),
            this, SLOT(setupDataChanged()));
    connect(&m_setupList, SIGNAL(rowsRemoved(const QModelIndex&, int, int)),
            this, SLOT(setupDataChanged()));
    connect(&m_setupList, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&, const QVector<int>&)),
            this, SLOT(setupDataChanged()));
    connect(&m_setupList, SIGNAL(layoutChanged(const QList<QPersistentModelIndex>&, QAbstractItemModel::LayoutChangeHint)),
            this, SLOT(setupDataChanged()));
    connect(&m_setupList, SIGNAL(modelReset()),
            this, SLOT(setupDataChanged()));

    m_addRemoveButtons.addAction()->setToolTip(tr("Add new page entry"));
    connect(m_addRemoveButtons.addAction(), SIGNAL(triggered(bool)), this, SLOT(doAdd()));
    m_addRemoveButtons.removeAction()->setToolTip(tr("Remove selected page entries"));
    connect(m_addRemoveButtons.removeAction(), SIGNAL(triggered(bool)), this, SLOT(itemDeleteAction()));

    m_tabs.setCurrentIndex(0);
}
