// SPDX-License-Identifier: LGPL-2.1-or-later
//
// SPDX-FileCopyrightText: 2013 Mihail Ivchenko <ematirov@gmail>
// SPDX-FileCopyrightText: 2014 Sanjiban Bairagya <sanjiban22393@gmail.com>
//

#include "TourWidget.h"
#include "FlyToEditWidget.h"
#include "SoundCueEditWidget.h"
#include "TourControlEditWidget.h"
#include "TourItemDelegate.h"
#include "WaitEditWidget.h"

#include "EditPlacemarkDialog.h"
#include "GeoDataAnimatedUpdate.h"
#include "GeoDataCamera.h"
#include "GeoDataChange.h"
#include "GeoDataCreate.h"
#include "GeoDataDelete.h"
#include "GeoDataDocument.h"
#include "GeoDataDocumentWriter.h"
#include "GeoDataFlyTo.h"
#include "GeoDataIconStyle.h"
#include "GeoDataLookAt.h"
#include "GeoDataPlacemark.h"
#include "GeoDataPlaylist.h"
#include "GeoDataSoundCue.h"
#include "GeoDataStyle.h"
#include "GeoDataTour.h"
#include "GeoDataTourControl.h"
#include "GeoDataTreeModel.h"
#include "GeoDataUpdate.h"
#include "GeoDataWait.h"
#include "KmlElementDictionary.h"
#include "MarbleDebug.h"
#include "MarbleDirs.h"
#include "MarbleModel.h"
#include "MarblePlacemarkModel.h"
#include "MarbleWidget.h"
#include "MovieCapture.h"
#include "ParsingRunnerManager.h"
#include "PlaybackFlyToItem.h"
#include "TourCaptureDialog.h"
#include "TourPlayback.h"
#include "ui_TourWidget.h"

#include <QCloseEvent>
#include <QDir>
#include <QFileDialog>
#include <QKeyEvent>
#include <QMenu>
#include <QMessageBox>
#include <QModelIndex>
#include <QPainter>
#include <QPointer>
#include <QToolButton>
#include <QUrl>

namespace Marble
{

class TourWidgetPrivate
{
public:
    explicit TourWidgetPrivate(TourWidget *parent);
    ~TourWidgetPrivate();
    GeoDataFeature *getPlaylistFeature() const;
    void updateRootIndex();

public:
    void openFile();
    bool openFile(const QString &filename);
    void createTour();
    void saveTour();
    void saveTourAs();
    void mapCenterOn(const QModelIndex &index);
    void addFlyTo();
    void addWait();
    void addSoundCue();
    void addPlacemark();
    void addRemovePlacemark();
    void addChangePlacemark();
    void addTourPrimitive(GeoDataTourPrimitive *primitive);
    void deleteSelected();
    void updateButtonsStates();
    void moveUp();
    void moveDown();
    void captureTour();
    void handlePlaybackProgress(const double position);
    void handlePlaybackFinish();
    GeoDataObject *rootIndexObject() const;

private:
    GeoDataTour *findTour(GeoDataFeature *feature) const;
    bool openDocument(GeoDataDocument *document);
    bool saveTourAs(const QString &filename);
    bool overrideModifications();

public:
    TourWidget *q;
    MarbleWidget *m_widget;
    Ui::TourWidget m_tourUi;
    TourCaptureDialog *m_tourCaptureDialog;
    TourPlayback m_playback;
    TourItemDelegate *m_delegate;
    bool m_isChanged;
    bool m_playState;
    bool m_isLoopingStopped;
    GeoDataDocument *m_document;
    QAction *m_actionToggleLoopPlay;
    QToolButton *m_addPrimitiveButton;
    QAction *m_actionAddFlyTo;
    QAction *m_actionAddWait;
    QAction *m_actionAddSoundCue;
    QAction *m_actionAddPlacemark;
    QAction *m_actionAddRemovePlacemark;
    QAction *m_actionAddChangePlacemark;
};

TourWidgetPrivate::TourWidgetPrivate(TourWidget *parent)
    : q(parent)
    , m_widget(nullptr)
    , m_playback(nullptr)
    , m_delegate(nullptr)
    , m_isChanged(false)
    , m_playState(false)
    , m_document(nullptr)
    , m_addPrimitiveButton(new QToolButton)
{
    m_tourUi.setupUi(parent);
    m_tourUi.m_actionRecord->setEnabled(false);

    QAction *separator = m_tourUi.m_toolBarControl->insertSeparator(m_tourUi.m_actionMoveUp);

    m_addPrimitiveButton->setIcon(QIcon(QStringLiteral(":/marble/flag.png")));
    m_addPrimitiveButton->setToolTip(QObject::tr("Add FlyTo"));
    m_addPrimitiveButton->setPopupMode(QToolButton::MenuButtonPopup);

    auto addPrimitiveMenu = new QMenu(q);

    m_actionAddFlyTo = new QAction(QIcon(QStringLiteral(":/marble/flag.png")), QObject::tr("Add FlyTo"), addPrimitiveMenu);
    addPrimitiveMenu->addAction(m_actionAddFlyTo);
    m_actionAddWait = new QAction(QIcon(QStringLiteral(":/marble/player-time.png")), QObject::tr("Add Wait"), addPrimitiveMenu);
    addPrimitiveMenu->addAction(m_actionAddWait);
    m_actionAddSoundCue = new QAction(QIcon(QStringLiteral(":/marble/audio-x-generic.png")), QObject::tr("Add SoundCue"), addPrimitiveMenu);
    addPrimitiveMenu->addAction(m_actionAddSoundCue);
    addPrimitiveMenu->addSeparator();
    m_actionAddPlacemark = new QAction(QIcon(QStringLiteral(":/icons/add-placemark.png")), QObject::tr("Add Placemark"), addPrimitiveMenu);
    addPrimitiveMenu->addAction(m_actionAddPlacemark);
    m_actionAddRemovePlacemark = new QAction(QIcon(QStringLiteral(":/icons/remove.png")), QObject::tr("Remove placemark"), addPrimitiveMenu);
    addPrimitiveMenu->addAction(m_actionAddRemovePlacemark);
    m_actionAddChangePlacemark = new QAction(QIcon(QStringLiteral(":/marble/document-edit.png")), QObject::tr("Change placemark"), addPrimitiveMenu);
    addPrimitiveMenu->addAction(m_actionAddChangePlacemark);
    m_actionToggleLoopPlay = new QAction(QObject::tr("Loop"), m_tourUi.m_slider);
    m_actionToggleLoopPlay->setCheckable(true);
    m_actionToggleLoopPlay->setChecked(false);
    m_tourUi.m_slider->setContextMenuPolicy(Qt::ActionsContextMenu);
    m_tourUi.m_slider->addAction(m_actionToggleLoopPlay);

    m_addPrimitiveButton->setMenu(addPrimitiveMenu);
    m_addPrimitiveButton->setEnabled(false);

    m_tourUi.m_toolBarControl->insertWidget(separator, m_addPrimitiveButton);

    QObject::connect(m_tourUi.m_listView, SIGNAL(activated(QModelIndex)), q, SLOT(mapCenterOn(QModelIndex)));
    QObject::connect(m_addPrimitiveButton, SIGNAL(clicked()), q, SLOT(addFlyTo()));
    QObject::connect(m_actionAddFlyTo, SIGNAL(triggered()), q, SLOT(addFlyTo()));
    QObject::connect(m_actionAddWait, SIGNAL(triggered()), q, SLOT(addWait()));
    QObject::connect(m_actionAddSoundCue, SIGNAL(triggered()), q, SLOT(addSoundCue()));
    QObject::connect(m_actionAddPlacemark, SIGNAL(triggered()), q, SLOT(addPlacemark()));
    QObject::connect(m_actionAddRemovePlacemark, SIGNAL(triggered()), q, SLOT(addRemovePlacemark()));
    QObject::connect(m_actionAddChangePlacemark, SIGNAL(triggered()), q, SLOT(addChangePlacemark()));
    QObject::connect(m_tourUi.m_actionDelete, SIGNAL(triggered()), q, SLOT(deleteSelected()));
    QObject::connect(m_tourUi.m_actionMoveUp, SIGNAL(triggered()), q, SLOT(moveUp()));
    QObject::connect(m_tourUi.m_actionMoveDown, SIGNAL(triggered()), q, SLOT(moveDown()));
    QObject::connect(m_tourUi.m_actionNewTour, SIGNAL(triggered()), q, SLOT(createTour()));
    QObject::connect(m_tourUi.m_actionOpenTour, SIGNAL(triggered()), q, SLOT(openFile()));
    QObject::connect(m_tourUi.m_actionSaveTour, SIGNAL(triggered()), q, SLOT(saveTour()));
    QObject::connect(m_tourUi.m_actionSaveTourAs, SIGNAL(triggered()), q, SLOT(saveTourAs()));
    QObject::connect(m_tourUi.m_actionRecord, SIGNAL(triggered()), q, SLOT(captureTour()));
    QObject::connect(&m_playback, SIGNAL(finished()), q, SLOT(stopPlaying()));
    QObject::connect(&m_playback, SIGNAL(itemFinished(int)), q, SLOT(setHighlightedItemIndex(int)));
}

TourWidgetPrivate::~TourWidgetPrivate()
{
    delete m_delegate;
}

TourWidget::TourWidget(QWidget *parent, Qt::WindowFlags flags)
    : QWidget(parent, flags)
    , d(new TourWidgetPrivate(this))
{
    layout()->setContentsMargins({});

    connect(d->m_tourUi.actionPlay, SIGNAL(triggered()), this, SLOT(togglePlaying()));
    connect(d->m_tourUi.actionStop, SIGNAL(triggered()), this, SLOT(stopLooping()));
    connect(d->m_tourUi.actionStop, SIGNAL(triggered()), this, SLOT(stopPlaying()));
    connect(d->m_tourUi.m_slider, SIGNAL(sliderMoved(int)), this, SLOT(handleSliderMove(int)));

    d->m_tourUi.m_toolBarPlayback->setDisabled(true);
    d->m_tourUi.m_slider->setDisabled(true);
    d->m_tourUi.m_listView->installEventFilter(this);
}

TourWidget::~TourWidget()
{
    delete d;
}

bool TourWidget::eventFilter(QObject *watched, QEvent *event)
{
    Q_UNUSED(watched);

    Q_ASSERT(watched == d->m_tourUi.m_listView);
    GeoDataObject *rootObject = d->rootIndexObject();

    if (!rootObject) {
        return false;
    }

    if (event->type() == QEvent::KeyPress) {
        auto key = static_cast<QKeyEvent *>(event);
        QModelIndexList selectedIndexes = d->m_tourUi.m_listView->selectionModel()->selectedIndexes();

        if (key->key() == Qt::Key_Delete) {
            if (!selectedIndexes.isEmpty()) {
                deleteSelected();
            }
            return true;
        }

        if (key->key() == Qt::Key_PageDown && key->modifiers().testFlag(Qt::ControlModifier) && !selectedIndexes.isEmpty()) {
            QModelIndexList::iterator end = selectedIndexes.end() - 1;
            if (const GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
                if (end->row() != playlist->size() - 1) {
                    moveDown();
                }
            }
            return true;
        }

        if (key->key() == Qt::Key_PageUp && key->modifiers().testFlag(Qt::ControlModifier) && !selectedIndexes.isEmpty()) {
            QModelIndexList::iterator start = selectedIndexes.begin();
            if (start->row() != 0) {
                moveUp();
            }
            return true;
        }
    }

    return false;
}

void TourWidget::setMarbleWidget(MarbleWidget *widget)
{
    d->m_widget = widget;
    delete d->m_delegate;
    d->m_delegate = new TourItemDelegate(d->m_tourUi.m_listView, d->m_widget, this);
    connect(d->m_delegate, SIGNAL(edited(QModelIndex)), this, SLOT(updateDuration()));
    connect(d->m_delegate, SIGNAL(edited(QModelIndex)), &d->m_playback, SLOT(updateTracks()));
    d->m_tourUi.m_listView->setItemDelegate(d->m_delegate);
}

void TourWidget::togglePlaying()
{
    if (!d->m_playState) {
        d->m_playState = true;
        startPlaying();
    } else {
        d->m_playState = false;
        pausePlaying();
    }
}

void TourWidget::startPlaying()
{
    setHighlightedItemIndex(0);
    d->m_isLoopingStopped = false;
    d->m_playback.play();
    d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-pause.png")));
    d->m_tourUi.actionPlay->setEnabled(true);
    d->m_tourUi.actionStop->setEnabled(true);
    d->m_tourUi.m_actionRecord->setEnabled(false);
    d->m_delegate->setEditable(false);
    d->m_addPrimitiveButton->setEnabled(false);
    d->m_playState = true;
}

void TourWidget::pausePlaying()
{
    d->m_playback.pause();
    d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
    d->m_tourUi.actionPlay->setEnabled(true);
    d->m_tourUi.actionStop->setEnabled(true);
}

void TourWidget::stopPlaying()
{
    removeHighlight();
    d->m_playback.stop();
    d->m_tourUi.actionPlay->setIcon(QIcon(QStringLiteral(":/marble/playback-play.png")));
    d->m_tourUi.actionPlay->setEnabled(true);
    d->m_tourUi.m_actionRecord->setEnabled(true);
    d->m_tourUi.actionStop->setEnabled(false);
    d->m_playState = false;
    d->m_delegate->setEditable(true);
    d->m_addPrimitiveButton->setEnabled(true);

    // Loop if the option ( m_actionLoopPlay ) is checked
    if (d->m_actionToggleLoopPlay->isChecked() && !d->m_isLoopingStopped) {
        startPlaying();
    }
}

void TourWidget::stopLooping()
{
    d->m_isLoopingStopped = true;
}

void TourWidget::closeEvent(QCloseEvent *event)
{
    if (!d->m_document || !d->m_isChanged) {
        event->accept();
        return;
    }

    const int result = QMessageBox::question(d->m_widget,
                                             QObject::tr("Save tour"),
                                             QObject::tr("There are unsaved Tours. Do you want to save your changes?"),
                                             QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);

    switch (result) {
    case QMessageBox::Save:
        d->saveTour();
        event->accept();
        break;
    case QMessageBox::Discard:
        event->accept();
        break;
    case QMessageBox::Cancel:
        event->ignore();
    }
}

void TourWidget::handleSliderMove(int value)
{
    removeHighlight();
    d->m_playback.seek(value / 100.0);
    QTime nullTime(0, 0, 0);
    QTime time = nullTime.addSecs(value / 100.0);
    d->m_tourUi.m_elapsedTime->setText(QStringLiteral("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
}

void TourWidgetPrivate::openFile()
{
    if (overrideModifications()) {
        QString const filename = QFileDialog::getOpenFileName(q, QObject::tr("Open Tour"), QDir::homePath(), QObject::tr("KML Tours (*.kml)"));
        if (!filename.isEmpty()) {
            ParsingRunnerManager manager(m_widget->model()->pluginManager());
            GeoDataDocument *document = manager.openFile(filename);
            m_playback.setBaseUrl(QUrl::fromLocalFile(filename));
            openDocument(document);
        }
    }
}

bool TourWidgetPrivate::openFile(const QString &filename)
{
    if (overrideModifications()) {
        if (!filename.isEmpty()) {
            ParsingRunnerManager manager(m_widget->model()->pluginManager());
            GeoDataDocument *document = manager.openFile(filename);
            m_playback.setBaseUrl(QUrl::fromLocalFile(filename));
            return openDocument(document);
        }
    }

    return false;
}

GeoDataTour *TourWidgetPrivate::findTour(GeoDataFeature *feature) const
{
    if (GeoDataTour *tour = (feature ? geodata_cast<GeoDataTour>(feature) : nullptr)) {
        return tour;
    }

    auto container = dynamic_cast<GeoDataContainer *>(feature);
    if (container) {
        QList<GeoDataFeature *>::Iterator end = container->end();
        QList<GeoDataFeature *>::Iterator iter = container->begin();
        for (; iter != end; ++iter) {
            GeoDataTour *tour = findTour(*iter);
            if (tour) {
                return tour;
            }
        }
    }
    return nullptr;
}

void TourWidgetPrivate::mapCenterOn(const QModelIndex &index)
{
    QVariant coordinatesVariant = m_widget->model()->treeModel()->data(index, MarblePlacemarkModel::CoordinateRole);
    if (!coordinatesVariant.isNull()) {
        auto const coordinates = coordinatesVariant.value<GeoDataCoordinates>();
        GeoDataLookAt lookat;
        lookat.setCoordinates(coordinates);
        lookat.setRange(coordinates.altitude());
        m_widget->flyTo(lookat, Instant);
    }
}

void TourWidgetPrivate::addFlyTo()
{
    auto flyTo = new GeoDataFlyTo();
    auto lookat = new GeoDataLookAt(m_widget->lookAt());
    lookat->setAltitude(lookat->range());
    flyTo->setView(lookat);
    bool isMainTrackEmpty = m_playback.mainTrackSize() == 0;
    flyTo->setDuration(isMainTrackEmpty ? 0.0 : 1.0);
    addTourPrimitive(flyTo);
}

void TourWidgetPrivate::addWait()
{
    auto wait = new GeoDataWait();
    wait->setDuration(1.0);
    addTourPrimitive(wait);
}

void TourWidgetPrivate::addSoundCue()
{
    auto soundCue = new GeoDataSoundCue();
    addTourPrimitive(soundCue);
}

void TourWidgetPrivate::addPlacemark()
{
    // Get the normalized coordinates of the focus point. There will be automatically added a new
    // placemark.
    qreal lat = m_widget->focusPoint().latitude();
    qreal lon = m_widget->focusPoint().longitude();
    GeoDataCoordinates::normalizeLonLat(lon, lat);

    auto document = new GeoDataDocument;
    if (m_document->id().isEmpty()) {
        if (m_document->name().isEmpty()) {
            m_document->setId(QStringLiteral("untitled_tour"));
        } else {
            m_document->setId(m_document->name().trimmed().replace(QLatin1Char(' '), QLatin1Char('_')).toLower());
        }
    }
    document->setTargetId(m_document->id());

    auto placemark = new GeoDataPlacemark;
    placemark->setCoordinate(lon, lat);
    placemark->setVisible(true);
    placemark->setBalloonVisible(true);
    auto newStyle = new GeoDataStyle(*placemark->style());
    newStyle->iconStyle().setIconPath(MarbleDirs::path(QStringLiteral("bitmaps/redflag_22.png")));
    placemark->setStyle(GeoDataStyle::Ptr(newStyle));

    document->append(placemark);

    auto create = new GeoDataCreate;
    create->append(document);
    auto update = new GeoDataUpdate;
    update->setCreate(create);
    auto animatedUpdate = new GeoDataAnimatedUpdate;
    animatedUpdate->setUpdate(update);

    if (m_delegate->editAnimatedUpdate(animatedUpdate)) {
        addTourPrimitive(animatedUpdate);
        m_delegate->setDefaultFeatureId(placemark->id());
    } else {
        delete animatedUpdate;
    }
}

void TourWidgetPrivate::addRemovePlacemark()
{
    auto deleteItem = new GeoDataDelete;
    auto placemark = new GeoDataPlacemark;
    placemark->setTargetId(m_delegate->defaultFeatureId());
    deleteItem->append(placemark);
    auto update = new GeoDataUpdate;
    update->setDelete(deleteItem);
    auto animatedUpdate = new GeoDataAnimatedUpdate;
    animatedUpdate->setUpdate(update);
    addTourPrimitive(animatedUpdate);
}

void TourWidgetPrivate::addChangePlacemark()
{
    auto change = new GeoDataChange;
    GeoDataPlacemark *placemark = nullptr;
    GeoDataFeature *lastFeature = m_delegate->findFeature(m_delegate->defaultFeatureId());
    if (GeoDataPlacemark *target = (lastFeature != nullptr ? geodata_cast<GeoDataPlacemark>(lastFeature) : nullptr)) {
        placemark = new GeoDataPlacemark(*target);
        placemark->setTargetId(m_delegate->defaultFeatureId());
        placemark->setId(QString());
    } else {
        placemark = new GeoDataPlacemark;
    }
    change->append(placemark);
    auto update = new GeoDataUpdate;
    update->setChange(change);
    auto animatedUpdate = new GeoDataAnimatedUpdate;
    animatedUpdate->setUpdate(update);
    addTourPrimitive(animatedUpdate);
}

void TourWidgetPrivate::addTourPrimitive(GeoDataTourPrimitive *primitive)
{
    GeoDataObject *rootObject = rootIndexObject();
    if (auto playlist = geodata_cast<GeoDataPlaylist>(rootObject)) {
        QModelIndex currentIndex = m_tourUi.m_listView->currentIndex();
        QModelIndex playlistIndex = m_widget->model()->treeModel()->index(playlist);
        int row = currentIndex.isValid() ? currentIndex.row() + 1 : playlist->size();
        m_widget->model()->treeModel()->addTourPrimitive(playlistIndex, primitive, row);
        m_isChanged = true;
        m_tourUi.m_actionSaveTour->setEnabled(true);

        // Scrolling to the inserted item.
        if (currentIndex.isValid()) {
            m_tourUi.m_listView->scrollTo(currentIndex);
        } else {
            m_tourUi.m_listView->scrollToBottom();
        }
    }
}

void TourWidgetPrivate::deleteSelected()
{
    QString title = QObject::tr("Remove Selected Items");
    QString text = QObject::tr("Are you sure want to remove selected items?");
    QPointer<QMessageBox> dialog = new QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::No, q);
    dialog->setDefaultButton(QMessageBox::No);
    if (dialog->exec() == QMessageBox::Yes) {
        GeoDataObject *rootObject = rootIndexObject();
        if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
            QModelIndex playlistIndex = m_widget->model()->treeModel()->index(playlist);
            QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
            std::sort(selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) {
                return b < a;
            });
            QModelIndexList::iterator end = selected.end();
            QModelIndexList::iterator iter = selected.begin();
            for (; iter != end; ++iter) {
                m_widget->model()->treeModel()->removeTourPrimitive(playlistIndex, iter->row());
            }
            m_isChanged = true;
            m_tourUi.m_actionSaveTour->setEnabled(true);
        }
    }
    delete dialog;
}

void TourWidgetPrivate::updateButtonsStates()
{
    QModelIndexList selectedIndexes = m_tourUi.m_listView->selectionModel()->selectedIndexes();
    if (selectedIndexes.isEmpty()) {
        m_tourUi.m_actionDelete->setEnabled(false);
        m_tourUi.m_actionMoveDown->setEnabled(false);
        m_tourUi.m_actionMoveUp->setEnabled(false);
    } else {
        m_tourUi.m_actionDelete->setEnabled(true);
        std::sort(selectedIndexes.begin(), selectedIndexes.end(), std::less<QModelIndex>());
        QModelIndexList::iterator end = selectedIndexes.end() - 1;
        QModelIndexList::iterator start = selectedIndexes.begin();
        m_tourUi.m_actionMoveUp->setEnabled((start->row() != 0)); // if we can move up enable action else disable.
        GeoDataObject *rootObject = rootIndexObject();
        if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
            m_tourUi.m_actionMoveDown->setEnabled((end->row() != playlist->size() - 1)); // if we can move down enable action else disable.
        }
    }
}

void TourWidgetPrivate::moveUp()
{
    GeoDataObject *rootObject = rootIndexObject();
    if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
        QModelIndex playlistIndex = m_widget->model()->treeModel()->index(playlist);
        QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
        std::sort(selected.begin(), selected.end(), std::less<QModelIndex>());
        QModelIndexList::iterator end = selected.end();
        QModelIndexList::iterator iter = selected.begin();
        for (; iter != end; ++iter) {
            int const index = iter->row();
            Q_ASSERT(index > 0);
            m_widget->model()->treeModel()->swapTourPrimitives(playlistIndex, index - 1, index);
        }
        m_isChanged = true;
        m_tourUi.m_actionSaveTour->setEnabled(true);
        updateButtonsStates();
    }
}

void TourWidgetPrivate::moveDown()
{
    GeoDataObject *rootObject = rootIndexObject();
    if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
        QModelIndex playlistIndex = m_widget->model()->treeModel()->index(playlist);
        QModelIndexList selected = m_tourUi.m_listView->selectionModel()->selectedIndexes();
        std::sort(selected.begin(), selected.end(), [](const QModelIndex &a, const QModelIndex &b) {
            return b < a;
        });
        QModelIndexList::iterator end = selected.end();
        QModelIndexList::iterator iter = selected.begin();
        for (; iter != end; ++iter) {
            int const index = iter->row();
            Q_ASSERT(index < playlist->size() - 1);
            m_widget->model()->treeModel()->swapTourPrimitives(playlistIndex, index, index + 1);
        }
        m_isChanged = true;
        m_tourUi.m_actionSaveTour->setEnabled(true);
        updateButtonsStates();
    }
}

GeoDataFeature *TourWidgetPrivate::getPlaylistFeature() const
{
    GeoDataObject *rootObject = rootIndexObject();
    if (GeoDataPlaylist *playlist = (rootObject ? geodata_cast<GeoDataPlaylist>(rootObject) : nullptr)) {
        GeoDataObject *object = playlist->parent();
        if (GeoDataTour *tour = (object ? geodata_cast<GeoDataTour>(object) : nullptr)) {
            return tour;
        }
    }
    return nullptr;
}

void TourWidgetPrivate::updateRootIndex()
{
    GeoDataTour *tour = findTour(m_document);
    if (tour) {
        GeoDataPlaylist *playlist = tour->playlist();
        if (playlist) {
            m_tourUi.m_listView->setModel(m_widget->model()->treeModel());
            m_tourUi.m_listView->setRootIndex(m_widget->model()->treeModel()->index(playlist));
            QObject::connect(m_tourUi.m_listView->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), q, SLOT(updateButtonsStates()));
        }
        m_playback.setMarbleWidget(m_widget);
        m_playback.setTour(tour);
        m_tourUi.m_slider->setMaximum(m_playback.duration() * 100);
        QTime nullTime(0, 0, 0);
        QTime time = nullTime.addSecs(m_playback.duration());
        m_tourUi.m_totalTime->setText(QStringLiteral("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
        QObject::connect(&m_playback, SIGNAL(progressChanged(double)), q, SLOT(handlePlaybackProgress(double)));
        q->stopPlaying();
        m_tourUi.m_toolBarPlayback->setEnabled(true);
        bool isPlaybackEmpty = m_playback.mainTrackSize() != 0;
        m_tourUi.actionPlay->setEnabled(isPlaybackEmpty);
        m_tourUi.m_slider->setEnabled(isPlaybackEmpty);
        m_tourUi.m_actionRecord->setEnabled(isPlaybackEmpty);
        m_tourUi.actionStop->setEnabled(false);
        if (m_playback.mainTrackSize() > 0) {
            if (dynamic_cast<PlaybackFlyToItem *>(m_playback.mainTrackItemAt(0))) {
                QModelIndex playlistIndex = m_widget->model()->treeModel()->index(playlist);
                for (int i = 0; playlist && i < playlist->size(); ++i) {
                    if (geodata_cast<GeoDataFlyTo>(playlist->primitive(i))) {
                        m_delegate->setFirstFlyTo(m_widget->model()->treeModel()->index(i, 0, playlistIndex));
                        break;
                    }
                }
            } else {
                m_delegate->setFirstFlyTo(QPersistentModelIndex());
            }
        }
    }
}

void TourWidget::addFlyTo()
{
    d->addFlyTo();
    finishAddingItem();
}

void TourWidget::addWait()
{
    d->addWait();
    finishAddingItem();
}

void TourWidget::addSoundCue()
{
    d->addSoundCue();
    finishAddingItem();
}

void TourWidget::addPlacemark()
{
    d->addPlacemark();
    finishAddingItem();
}

void TourWidget::addRemovePlacemark()
{
    d->addRemovePlacemark();
    finishAddingItem();
}

void TourWidget::addChangePlacemark()
{
    d->addChangePlacemark();
    finishAddingItem();
}

void TourWidget::deleteSelected()
{
    d->deleteSelected();
    GeoDataFeature *feature = d->getPlaylistFeature();
    if (feature) {
        Q_EMIT featureUpdated(feature);
        d->updateRootIndex();
    }
}

void TourWidget::updateDuration()
{
    d->m_tourUi.m_slider->setMaximum(d->m_playback.duration() * 100);
    QTime nullTime(0, 0, 0);
    QTime totalTime = nullTime.addSecs(d->m_playback.duration());
    d->m_tourUi.m_totalTime->setText(
        QStringLiteral("%L1:%L2").arg(totalTime.minute(), 2, 10, QLatin1Char('0')).arg(totalTime.second(), 2, 10, QLatin1Char('0')));
    d->m_tourUi.m_slider->setValue(0);
    d->m_tourUi.m_elapsedTime->setText(QStringLiteral("%L1:%L2").arg(0, 2, 10, QLatin1Char('0')).arg(0, 2, 10, QLatin1Char('0')));
}

void TourWidget::finishAddingItem()
{
    GeoDataFeature *feature = d->getPlaylistFeature();
    if (feature) {
        Q_EMIT featureUpdated(feature);
        d->updateRootIndex();
    }
}

void TourWidget::moveDown()
{
    d->moveDown();
    GeoDataFeature *feature = d->getPlaylistFeature();
    if (feature) {
        Q_EMIT featureUpdated(feature);
        d->updateRootIndex();
    }
}

void TourWidget::moveUp()
{
    d->moveUp();
    GeoDataFeature *feature = d->getPlaylistFeature();
    if (feature) {
        Q_EMIT featureUpdated(feature);
        d->updateRootIndex();
    }
}

GeoDataObject *TourWidgetPrivate::rootIndexObject() const
{
    QModelIndex const rootIndex = m_tourUi.m_listView->rootIndex();
    return rootIndex.isValid() ? static_cast<GeoDataObject *>(rootIndex.internalPointer()) : nullptr;
}

void TourWidgetPrivate::createTour()
{
    if (overrideModifications()) {
        auto document = new GeoDataDocument();
        document->setDocumentRole(UserDocument);
        document->setName(QStringLiteral("New Tour"));
        document->setId(QStringLiteral("new_tour"));
        auto tour = new GeoDataTour();
        tour->setName(QStringLiteral("New Tour"));
        auto playlist = new GeoDataPlaylist;
        tour->setPlaylist(playlist);
        document->append(static_cast<GeoDataFeature *>(tour));
        m_playback.setBaseUrl(QUrl::fromLocalFile(MarbleDirs::marbleDataPath()));
        openDocument(document);
        m_isChanged = true;
        m_tourUi.m_actionSaveTour->setEnabled(true);
        m_tourUi.m_slider->setEnabled(true);
    }
}

bool TourWidgetPrivate::openDocument(GeoDataDocument *document)
{
    if (document) {
        if (m_document) {
            m_widget->model()->treeModel()->removeDocument(m_document);
            delete m_document;
        }
        m_document = document;
        m_widget->model()->treeModel()->addDocument(m_document);
        m_isChanged = false;
        updateRootIndex();
        m_addPrimitiveButton->setEnabled(true);
        m_tourUi.m_actionSaveTourAs->setEnabled(true);
        m_tourUi.m_actionSaveTour->setEnabled(false);
        m_isChanged = false;
        return true;
    }
    return false;
}

void TourWidgetPrivate::saveTour()
{
    if (m_document) {
        if (!m_document->fileName().isEmpty()) {
            saveTourAs(m_document->fileName());
        } else {
            saveTourAs();
        }
    }
}

void TourWidgetPrivate::saveTourAs()
{
    if (m_document) {
        QString const filename = QFileDialog::getSaveFileName(q, QObject::tr("Save Tour as"), QDir::homePath(), QObject::tr("KML Tours (*.kml)"));
        if (!filename.isEmpty()) {
            saveTourAs(filename);
        }
    }
}

bool TourWidgetPrivate::saveTourAs(const QString &filename)
{
    if (!filename.isEmpty()) {
        if (GeoDataDocumentWriter::write(filename, *m_document)) {
            m_tourUi.m_actionSaveTour->setEnabled(false);
            m_isChanged = false;
            GeoDataDocument *document = m_document;
            if (!document->fileName().isNull()) {
                m_widget->model()->removeGeoData(document->fileName());
            }
            m_widget->model()->addGeoDataFile(filename);
            m_document->setFileName(filename);
            return true;
        }
    }
    return false;
}

void TourWidgetPrivate::captureTour()
{
    auto widget = new MarbleWidget;
    widget->setMapThemeId(m_widget->mapThemeId());
    widget->resize(1280, 720);

    m_widget->model()->treeModel()->removeDocument(m_document);
    widget->model()->treeModel()->addDocument(m_document);

    GeoDataTour *tour = findTour(m_document);
    auto playback = new TourPlayback;
    playback->setMarbleWidget(widget);
    playback->setTour(tour);

    m_tourUi.m_listView->setModel(widget->model()->treeModel());
    if (tour) {
        m_tourUi.m_listView->setRootIndex(widget->model()->treeModel()->index(tour->playlist()));
        m_tourUi.m_listView->repaint();

        QPointer<TourCaptureDialog> tourCaptureDialog = new TourCaptureDialog(widget, m_widget);
        tourCaptureDialog->setDefaultFilename(tour->name());
        tourCaptureDialog->setTourPlayback(playback);
        tourCaptureDialog->exec();
    }

    delete playback;
    widget->model()->treeModel()->removeDocument(m_document);
    m_widget->model()->treeModel()->addDocument(m_document);
    updateRootIndex();
    delete widget;
}

bool TourWidgetPrivate::overrideModifications()
{
    if (m_document && m_isChanged) {
        QString title = QObject::tr("Discard Changes");
        QString text = QObject::tr("Are you sure want to discard all unsaved changes and close current document?");
        QPointer<QMessageBox> dialog = new QMessageBox(QMessageBox::Question, title, text, QMessageBox::Yes | QMessageBox::No, q);
        dialog->setDefaultButton(QMessageBox::No);
        if (dialog->exec() != QMessageBox::Yes) {
            delete dialog;
            return false;
        }
        delete dialog;
    }
    return true;
}

bool TourWidget::openTour(const QString &filename)
{
    return d->openFile(filename);
}

void TourWidgetPrivate::handlePlaybackProgress(const double position)
{
    if (!m_tourUi.m_slider->isSliderDown()) {
        m_tourUi.m_slider->setValue(position * 100);
        QTime nullTime(0, 0, 0);
        QTime time = nullTime.addSecs(position);
        m_tourUi.m_elapsedTime->setText(QStringLiteral("%L1:%L2").arg(time.minute(), 2, 10, QLatin1Char('0')).arg(time.second(), 2, 10, QLatin1Char('0')));
    }
}

void TourWidget::setHighlightedItemIndex(int index)
{
    GeoDataObject *rootObject = d->rootIndexObject();
    auto playlist = static_cast<GeoDataPlaylist *>(rootObject);
    QModelIndex playlistIndex = d->m_widget->model()->treeModel()->index(playlist);

    // Only flyTo and wait items have duration, so the other types have to be skipped.
    int searchedIndex = 0;
    for (int i = 0; i < playlist->size(); i++) {
        QModelIndex currentIndex = d->m_widget->model()->treeModel()->index(i, 0, playlistIndex);
        auto object = qvariant_cast<GeoDataObject *>(currentIndex.data(MarblePlacemarkModel::ObjectPointerRole));

        if (geodata_cast<GeoDataFlyTo>(object) || geodata_cast<GeoDataWait>(object))
            ++searchedIndex;

        if (index == searchedIndex) {
            d->m_tourUi.m_listView->selectionModel()->setCurrentIndex(currentIndex, QItemSelectionModel::NoUpdate);
            d->m_tourUi.m_listView->scrollTo(currentIndex);
            break;
        }
    }
    d->m_tourUi.m_listView->viewport()->update();
}

void TourWidget::removeHighlight()
{
    QModelIndex index;

    // Restoring the CurrentIndex to the previously selected item
    // or clearing it if there was no selected item.
    if (d->m_tourUi.m_listView->selectionModel()->hasSelection()) {
        index = d->m_tourUi.m_listView->selectionModel()->selectedIndexes().last();
    } else {
        index = QModelIndex();
    }

    d->m_tourUi.m_listView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::NoUpdate);
    d->m_tourUi.m_listView->viewport()->update();
}

bool TourWidget::isPlaying() const
{
    return d->m_playState;
}

}

#include "moc_TourWidget.cpp"
