/*
 * Copyright 2010  Jonas Emanuel Müller <zanoi@zanoi.net>
 *           2004, 2005  Frank Osterfeld <osterfeld@kde.org>
 *           2004  Stanislav Karchebny <Stanislav.Karchebny@kdemail.net>
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.

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

#include "mainwindowkde.h"

#include "addchanneldialog.h"
#include "channelmodel.h"
#include "episodemodel.h"
#include "episodeproxymodel.h"
#include "channel.h"
#include "episodeview.h"

#include "configure.h"

#include <KLineEdit>
#include <KUrl>
#include <KMessageBox>
#include <KAction>
#include <KLocale>
#include <KActionCollection>
#include <KStandardAction>
#include <KIO/CopyJob>
#include <KIO/DeleteJob>
#include <KIO/SimpleJob>
#include <KIO/NetAccess>
#include <KStandardGuiItem>
#include <KGuiItem>
#include <KConfigDialog>
#include <KStandardDirs>
#include <KConfig>
#include <KGlobal>
#include <KComponentData>
#include <KMenu>
#include <KFileDialog>
#include <KStandardShortcut>
#include <KRun>
#include <KMimeTypeTrader>
#include <KMimeType>
#include <KActionMenu>
#include <KShell>

#if KDE_IS_VERSION(4,4,0)
#include <KStatusNotifierItem>
#endif

#include <QMap>
#include <QSqlRelationalDelegate>
#include <QSqlError>
#include <QHeaderView>
#include <QModelIndex>
#include <QDockWidget>
#include <QProcess>
#include <QWidget>
#include <QTableView>
#include <QLabel>
#include <QRegExp>
#include <QCloseEvent>
#include <QClipboard>
#include <QDomDocument>
#include <QFile>
#include <QDir>
#include <QTimer>
#include <QScrollBar>
#include <QPointer>

namespace Videocatcher {

struct MainWindow::MainWindowPrivate
{
    MainWindowPrivate()
    {
    }
    
    ~MainWindowPrivate()
    {
    }

    ChannelModel *m_channelModel;
    EpisodeModel *m_originalEpisodeModel;
    EpisodeProxyModel *m_episodeModel;
    QTableView *m_channelView;
    EpisodeView *m_episodeView;
    EpisodeInformationWidget *m_episodeInformationWidget;
    QMap<KJob *, QString> *m_jobs;
    QTimer *m_autoRefreshChannelsTimer;
    int m_currentAutoRefreshLine;
    Ui::ConfigureWidget m_configureWidget;
    KUrl m_oldVideoFolder;
#if KDE_IS_VERSION(4,4,0)
    KStatusNotifierItem *m_statusNotifier;
#endif
    QMap<KJob *, unsigned long> *m_downloadSpeeds;
    // The search bar
    KLineEdit *m_searchText;
    // This stores the play menu for easy access
    KActionMenu *m_playMenu;
    // This is a list of the current actions in 
    // the play menu. The list is needed in order
    // to be able to remove the actions from the
    // menu.
    QStringList *m_playMenuActions;
};

MainWindow::MainWindow(QWidget *parent) : KXmlGuiWindow(parent), d(new MainWindowPrivate)
{
    d->m_jobs = new QMap<KJob *, QString>;
    d->m_downloadSpeeds = new QMap<KJob *, unsigned long>;

    createChannelPanel();
    createEpisodePanel();
    createInformationPanel();

    setCentralWidget(d->m_episodeView);
    QDockWidget *channelDockWidget = new QDockWidget(i18n("Channel List"), this);
    channelDockWidget->setObjectName("channelDockWidget");
    channelDockWidget->setWidget(d->m_channelView);
    addDockWidget(Qt::LeftDockWidgetArea, channelDockWidget);
    
    // Set up information panel
    QDockWidget* informationDockWidget = new QDockWidget(i18n("Episode Information"), this);
    informationDockWidget->setObjectName("informationDockWidget");
    informationDockWidget->setWidget(d->m_episodeInformationWidget);
    addDockWidget(Qt::RightDockWidgetArea, informationDockWidget);
    
    // Initialise search
    d->m_searchText = new KLineEdit( this );
    d->m_searchText->setClearButtonShown( true );
    d->m_searchText->setClickMessage( i18nc( "Search episodes", "Search" ) );
    d->m_searchText->setCompletionMode( KGlobalSettings::CompletionPopup );
    
    setupActions();
    connect(d->m_episodeView, SIGNAL(doubleClicked(const QModelIndex &)), this, SLOT(episodeDoubleClicked(const QModelIndex &)));
    
    connect(d->m_searchText, SIGNAL(textChanged(const QString&)), this, SLOT(searchChanged(const QString&)));
    
    QModelIndex index = d->m_channelModel->index(0, Episode_Status);
    d->m_channelView->setCurrentIndex(index);

    d->m_episodeView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(d->m_episodeView, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(showEpisodeViewContextMenu(const QPoint&)));

    d->m_channelView->setContextMenuPolicy(Qt::CustomContextMenu);
    connect(d->m_channelView, SIGNAL(customContextMenuRequested(const QPoint&)),
        this, SLOT(showChannelViewContextMenu(const QPoint&)));

#if KDE_IS_VERSION(4,4,0)
    qApp->setQuitOnLastWindowClosed(false);
    this->setAttribute(Qt::WA_DeleteOnClose, false);

    d->m_statusNotifier = new KStatusNotifierItem(this);
    d->m_statusNotifier->setIconByName("videocatcher");
    d->m_statusNotifier->setCategory(KStatusNotifierItem::ApplicationStatus);
    d->m_statusNotifier->setStatus(KStatusNotifierItem::Active);
    d->m_statusNotifier->setTitle(i18n("Videocatcher"));
    d->m_statusNotifier->setStandardActionsEnabled(false);

    KMenu * cm = d->m_statusNotifier->contextMenu();
    cm->addAction(actionCollection()->action("add"));
    cm->addAction(actionCollection()->action("refreshAll"));
    cm->addSeparator();
    cm->addAction(actionCollection()->action(KStandardAction::name(KStandardAction::Quit)));

    d->m_statusNotifier->setToolTip("videocatcher", i18n("Videocatcher"), i18n("Not downloading"));
#endif

    d->m_autoRefreshChannelsTimer = new QTimer(this);
    initAutoRefreshChannels();

    connect(Solid::Networking::notifier(), SIGNAL(statusChanged(Solid::Networking::Status)),
        SLOT(networkStatusChanged(Solid::Networking::Status)));

    //workaround for Qt SQLite bug
    while (d->m_originalEpisodeModel->canFetchMore()) {
        d->m_originalEpisodeModel->fetchMore();
    }
}

MainWindow::~MainWindow()
{
    delete d->m_jobs;
    delete d->m_downloadSpeeds;
    
    delete d;
}

void MainWindow::setupActions()
{
    //TODO: add status tips
#if KDE_IS_VERSION(4,4,0)
    KStandardAction::quit(this, SLOT(quit()), actionCollection());
#else
    KStandardAction::quit(this, SLOT(close()), actionCollection());
#endif
    KStandardAction::preferences(this, SLOT(configure()), actionCollection());
    
    KAction *importChannelsAction = new KAction(i18n("&Import from File"), this);
    importChannelsAction->setIcon(KIcon("document-import"));
    importChannelsAction->setStatusTip(i18n("Import new channels from file"));
    actionCollection()->addAction("importChannels", importChannelsAction);
    connect(importChannelsAction, SIGNAL(triggered()), this, SLOT(importChannels()));

    KAction *addChannelAction = new KAction(i18n("&Add Channel"), this);
    addChannelAction->setIcon(KIcon("feed-subscribe"));
    addChannelAction->setShortcuts(KShortcut("Insert"));
    addChannelAction->setStatusTip(i18n("Add a new channel"));
    actionCollection()->addAction("add", addChannelAction);
    connect(addChannelAction, SIGNAL(triggered()), this, SLOT(addChannel()));

    KAction *removeChannelAction = new KAction(i18n("Remo&ve Channel"), this);
    removeChannelAction->setIcon(KIcon("edit-delete"));
    removeChannelAction->setShortcuts(KShortcut("Alt+Delete"));
    removeChannelAction->setStatusTip(i18n("Remove channel"));
    actionCollection()->addAction("remove", removeChannelAction);
    connect(removeChannelAction, SIGNAL(triggered()), this, SLOT(removeChannel()));

//     KAction *editChannelAction = new KAction(i18n("&Edit Channel"), this);
//     editChannelAction->setIcon(KIcon("document-properties"));
//     editChannelAction->setShortcuts(KShortcut("F2"));
//     editChannelAction->setStatusTip(i18n("Edit Channel"));
//     actionCollection()->addAction("edit", editChannelAction);
//     connect(editChannelAction, SIGNAL(triggered()), this, SLOT(editChannel()));

    KAction *refreshChannelAction = new KAction(i18n("&Refresh Channel"), this);
    refreshChannelAction->setIcon(KIcon("view-refresh"));
    refreshChannelAction->setShortcuts(KStandardShortcut::shortcut(KStandardShortcut::Reload));
    refreshChannelAction->setStatusTip(i18n("Refresh selected channel"));
    //if not connected to any network
    if(Solid::Networking::status() != Solid::Networking::Connected && Solid::Networking::status() != Solid::Networking::Unknown) {
        refreshChannelAction->setEnabled(false);
    }
    actionCollection()->addAction("refresh", refreshChannelAction);
    connect(refreshChannelAction, SIGNAL(triggered()), this, SLOT(refreshChannel()));

    KAction *refreshAllChannelsAction = new KAction(i18n("Refresh &All Channels"), this);
    refreshAllChannelsAction->setShortcuts(KShortcut("Ctrl+L"));
    refreshAllChannelsAction->setIcon(KIcon("view-refresh")); //"go-bottom"
    refreshAllChannelsAction->setStatusTip(i18n("Refresh all channels"));
    //if not connected to any network
    if(Solid::Networking::status() != Solid::Networking::Connected && Solid::Networking::status() != Solid::Networking::Unknown) {
        refreshAllChannelsAction->setEnabled(false);
    }
    actionCollection()->addAction("refreshAll", refreshAllChannelsAction);
    connect(refreshAllChannelsAction, SIGNAL(triggered()), this, SLOT(refreshAllChannels()));

    KAction *downloadEpisodeAction = new KAction(i18n("&Download Episode"), this);
    //downloadEpisodeAction->setShortcut(i18n(""));
    downloadEpisodeAction->setIcon(KIcon("download"));
    downloadEpisodeAction->setStatusTip(i18n("Download episode"));
    downloadEpisodeAction->setEnabled(false);
    actionCollection()->addAction("download", downloadEpisodeAction);
    connect(downloadEpisodeAction, SIGNAL(triggered()), this, SLOT(downloadEpisode()));

    KAction *cancelEpisodeDownloadAction = new KAction(i18n("&Cancel Download"), this);
    //cancelEpisodeDownloadAction->setShortcut(i18n(""));
    cancelEpisodeDownloadAction->setIcon(KIcon("process-stop"));
    cancelEpisodeDownloadAction->setStatusTip(i18n("Cancel download"));
    cancelEpisodeDownloadAction->setEnabled(false);
    actionCollection()->addAction("cancel", cancelEpisodeDownloadAction);
    connect(cancelEpisodeDownloadAction, SIGNAL(triggered()), this,
        SLOT(cancelEpisodeDownload()));

    d->m_playMenu = new KActionMenu(i18n("&Play Episode"), this);
    d->m_playMenu->setShortcuts(KShortcut("Space"));
    d->m_playMenu->setIcon(KIcon("media-playback-start"));
    d->m_playMenu->setStatusTip(i18n("Play episode"));
    d->m_playMenu->setEnabled(false);
    d->m_playMenu->setDelayed(true);
    actionCollection()->addAction("play", d->m_playMenu);
    connect(d->m_playMenu, SIGNAL(triggered()), this, SLOT(playEpisode()));
    d->m_playMenuActions = new QStringList;

    KAction *streamEpisodeAction = new KAction(i18n("&Stream Episode"), this);
    //streamEpisodeAction->setShortcut(i18n(""));
    //streamEpisodeAction->setIcon(KIcon(""));
    streamEpisodeAction->setStatusTip(i18n("Stream episode"));
    streamEpisodeAction->setEnabled(false);
    actionCollection()->addAction("stream", streamEpisodeAction);
    connect(streamEpisodeAction, SIGNAL(triggered()), this, SLOT(streamEpisode()));

    KAction *deleteEpisodeAction = new KAction(i18n("&Delete Episode"), this);
    //deleteEpisodeAction->setShortcut(i18n(""));
    deleteEpisodeAction->setIcon(KIcon("user-trash"));
    deleteEpisodeAction->setStatusTip(i18n("Delete episode"));
    deleteEpisodeAction->setEnabled(false);
    actionCollection()->addAction("delete", deleteEpisodeAction);
    connect(deleteEpisodeAction, SIGNAL(triggered()), this, SLOT(deleteEpisode()));

    // Finally the search line-edit
    KAction *searchAction = new KAction( this );
    searchAction->setDefaultWidget(d->m_searchText);
    searchAction->setShortcut(KShortcut(QKeySequence(Qt::CTRL + Qt::Key_F)));
    connect( searchAction, SIGNAL(triggered(Qt::MouseButtons, Qt::KeyboardModifiers)),
         d->m_searchText, SLOT(setFocus()));
    actionCollection()->addAction( "searchText", searchAction );

    setupGUI();
}

void MainWindow::createChannelPanel()
{
    d->m_channelModel = new ChannelModel(this);
    
    d->m_channelView = new QTableView(this);
    d->m_channelView->setModel(d->m_channelModel);
    d->m_channelView->setItemDelegate(new QSqlRelationalDelegate(this));
    d->m_channelView->setSelectionMode(QAbstractItemView::SingleSelection);
    d->m_channelView->setSelectionBehavior(QAbstractItemView::SelectRows);
    d->m_channelView->setEditTriggers(QAbstractItemView::NoEditTriggers);
    d->m_channelView->setColumnHidden(Channel_Id, true);
    d->m_channelView->setColumnHidden(Channel_Url, true);
    d->m_channelView->setColumnHidden(Channel_Path, true);
    d->m_channelView->setColumnHidden(Channel_Author, true);
    d->m_channelView->setColumnHidden(Channel_Web_Link, true);
    d->m_channelView->resizeColumnsToContents();
    d->m_channelView->horizontalHeader()->setStretchLastSection(true);
    d->m_channelView->setShowGrid(false);
    d->m_channelView->verticalHeader()->hide();
    d->m_channelView->horizontalHeader()->hide();

    connect(d->m_channelView->selectionModel(), 
        SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), 
        this,
        SLOT(updateEpisodeView()));

    d->m_channelView->show();
}

void MainWindow::createEpisodePanel()
{
    d->m_originalEpisodeModel = new EpisodeModel(this);
    d->m_episodeModel = new EpisodeProxyModel(this);
    d->m_episodeModel->setSourceEpisodeModel(d->m_originalEpisodeModel);
    d->m_episodeModel->setTitleFilterEnabled(true);
    d->m_episodeModel->setChannelIdFilterEnabled(true);
    d->m_episodeModel->setStatusFilterEnabled(false);

    d->m_episodeView = new EpisodeView(this);
    d->m_episodeView->setEpisodeModel(d->m_episodeModel);
    d->m_episodeView->horizontalHeader()->setResizeMode(Episode_Title, QHeaderView::Stretch);

    connect(d->m_episodeView->selectionModel(), 
        SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), 
        this,
        SLOT(updateActions()));

    d->m_episodeView->show();
}

void MainWindow::createInformationPanel()
{
    d->m_episodeInformationWidget = new EpisodeInformationWidget(this);

    connect(d->m_episodeView->selectionModel(), 
        SIGNAL(currentRowChanged(const QModelIndex &, const QModelIndex &)), 
        this,
        SLOT(currentEpisodeChanged(const QModelIndex &, const QModelIndex &)));

    d->m_episodeInformationWidget->show();
}

void MainWindow::currentEpisodeChanged(const QModelIndex &current, const QModelIndex &previous)
{
    Q_UNUSED(previous);
    
    if(current.isValid()) {
        d->m_episodeInformationWidget->clear();
        d->m_episodeInformationWidget->setTitle(d->m_episodeModel->title(current));
        d->m_episodeInformationWidget->setAttribute("Description:", d->m_episodeModel->description(current));
        d->m_episodeInformationWidget->setAttribute("Size:", KIO::convertSize(d->m_episodeModel->videoSize(current)));
    }

    //update the play menu to show all registered applications
    //for the currently selected channels video mimetype
    QString videoPath = d->m_episodeModel->videoPath(current);

    if(!videoPath.isEmpty()) {
        KMimeType::Ptr mimeType;
        QString mimeTypeName;
        //note that the fourth parameter enables fast_mode
        //which means that no disk access is allowed
        //this is to prevent constant disk access, but might
        //be changed later.
        mimeType = KMimeType::findByUrl(videoPath, 0, true, true); //try finding mime type with file path
        mimeTypeName = mimeType.data()->name();
        kDebug() << "found mimetype to be " << mimeTypeName << " on first try";
        if(mimeTypeName.isEmpty() || mimeTypeName == "application/octet-stream") { //finding mime type failed
            QString videoUrl = d->m_episodeModel->videoUrl(current);
            mimeType = KMimeType::findByUrl(videoUrl, 0); //try finding mime type with url
            mimeTypeName = mimeType.data()->name();
            kDebug() << "found mimetype to be " << mimeTypeName << " on second try";
            if(mimeTypeName.isEmpty() || mimeTypeName == "application/octet-stream") { //finding mime type failed
                mimeTypeName = KIO::NetAccess::mimetype(videoUrl, this);
                kDebug() << "found mimetype to be " << mimeTypeName << " on third try";
            }
        }

        updatePlayMenu(mimeTypeName);
    }
}

void MainWindow::updateEpisodeView()
{
    QModelIndex index = d->m_channelView->currentIndex();
    if (index.isValid()) {
        int id = d->m_channelModel->id(index);
 
        d->m_episodeModel->setChannelIdFilter(id);
    } else {
        kDebug() << "index invalid";
        d->m_episodeModel->setChannelIdFilter(-1);
    }
}

void MainWindow::updateActions()
{
    int status;

    QModelIndex index = d->m_episodeView->currentIndex();
    if (index.isValid()) {
        status = d->m_episodeModel->status(index);

        //FIXME: what do to with Episode_Status_Paused? Need new action
        switch(status)
        {
        case Episode_Status_Not_Downloaded:
        case Episode_Status_New:
        case Episode_Status_Cancelled:
        case Episode_Status_Deleted:
            //if it is not downloaded
            action("download")->setEnabled(true);
            action("play")->setEnabled(false);
            action("delete")->setEnabled(false);   
            action("cancel")->setEnabled(false);
            action("stream")->setEnabled(true);
            break;
        case Episode_Status_Downloaded:
            action("download")->setEnabled(false);
            action("play")->setEnabled(true);
            action("delete")->setEnabled(true);
            action("cancel")->setEnabled(false);
            action("stream")->setEnabled(true);
            break;
        case Episode_Status_Unavailable:
            action("download")->setEnabled(false);
            action("play")->setEnabled(false);
            action("delete")->setEnabled(false);
            action("cancel")->setEnabled(false);
            action("stream")->setEnabled(false);
            break;
        default: //should be downloading, but in case there is an invalid state catch it anyway
            action("download")->setEnabled(false);
            action("play")->setEnabled(true);
            action("delete")->setEnabled(false);
            action("cancel")->setEnabled(true);
            action("stream")->setEnabled(true);
            break;
        }
    }
       
}

void MainWindow::addChannel()
{
    QString url("");
    QModelIndex index;
    QPointer<AddChannelDialog> dialog = new AddChannelDialog(this);

    if(dialog->exec()) {
        QString url = dialog->m_url->text();
        
        Loader* loader = Loader::create(url);
        connect(loader, 
            SIGNAL(loadingComplete(Channel *, ErrorCode, const QString)),
            this, SLOT(loadChannelResult(Channel *, ErrorCode, const QString)));
        loader->load();
    }
    delete dialog;
}

void MainWindow::loadChannelResult(Channel *channel, ErrorCode status, const QString url)
{
    QModelIndex index;
    int channelId = -1;
    QString enclosureUrl;
    double newestTime = 0;
    QString errorMessage;
    bool success;
    
    //find out whether channel is added or updated
    bool exists = d->m_channelModel->exists(url);
    //get channel id from url
    index = d->m_channelModel->findByUrl(url);

    if(status == Loader_Success) {
        if(!exists)
        {
            KUrl path = calculateChannelPath(channel->websiteUrl());

            //FIXME: add author
            success = d->m_channelModel->addChannel(channel->title(), channel->url(), "", channel->websiteUrl(), path.pathOrUrl());

            //return if new channel was not successfully added and submitted
            if(!(success && submitChannels()))  {
                return;
            }
            
            index = d->m_channelModel->findByUrl(channel->url());
            channelId = d->m_channelModel->id(index);
            kDebug() << "adding channel with channelId " << channelId;
            newestTime = 0;

        } else {
            channelId = d->m_channelModel->id(index);
            QModelIndex newest = d->m_originalEpisodeModel->findNewest(channelId);
            if(!newest.isValid()) {
                d->m_channelModel->setData(index, Last_Loading_Failed, LastLoadingRole);
                return;
            }
            //date of newest episode
            newestTime = d->m_originalEpisodeModel->datePublished(newest);

            //title of newest record
            QString newestTitle = d->m_originalEpisodeModel->title(newest);
            kDebug() << "currently newest episode: " << newestTitle << " (" << newestTime << ")";
        }

        QList<Episode> items = channel->episodes();
        foreach(const Episode &item, items) {
            if(newestTime < item.datePublished) {
                kDebug() << "found new episode: " << item.title << "( " << item.datePublished << ")";
                index = d->m_originalEpisodeModel->addEpisode(item.title, item.description, channelId, (double)item.datePublished, item.videoUrl, item.videoSize);
                if(index.isValid()) {
                    KUrl episodeVideoPath = calculateEpisodeVideoPath(index);
                    if(KIO::NetAccess::exists(episodeVideoPath, false, this)) {
                        d->m_originalEpisodeModel->setStatus(index,  Episode_Status_Downloaded);
                        d->m_originalEpisodeModel->setVideoPath(index, episodeVideoPath.pathOrUrl());
                    }
                } else {
                    qWarning() << "received invalid index when adding episode";
                }
            }
        }
        index = d->m_channelModel->findByUrl(url);
        d->m_channelModel->setData(index, Last_Loading_Success, LastLoadingRole);

        submitChannelsAndEpisodes();
        updateEpisodeView();

    } else { //parsing gave error
        switch(status) {
            case Loader_File_Not_Found:
                errorMessage = i18n("URL not found");
                break;
            case Loader_Connection_Problem:
                errorMessage = i18n("Please check your Internet connection");
                break;
            case Loader_Aborted:
                errorMessage = i18n("Loader aborted");
                break;
            case Loader_Parse_Error:
                errorMessage = i18n("Could not read channel. Did you give a correct URL?");
                break;
            case Loader_Other_Error:
            default:
                errorMessage =  i18n("Unknown error");
                break;
        }

        //if channel is newly created, use messagebox. If channel is updated change icon
        if(exists) {
            kDebug() << QString("Could not update channel %1: %2").arg(url).arg(errorMessage);
            d->m_channelModel->setData(index, Last_Loading_Failed, LastLoadingRole);
            submitChannels();
            return;
        } else {
            KMessageBox::error(this, i18n("Could not add channel \"%1\": %2", url, errorMessage), i18n("Error Adding Channel"));
            return;
        }
    }
}

void MainWindow::removeChannel()
{
    bool removeFiles;
    QModelIndex index;
    bool success = true;
    int channelId;
    QModelIndexList episodes;

    //get currently selected channel
    index = d->m_channelView->currentIndex();
    channelId = d->m_channelModel->id(index);

    episodes = d->m_originalEpisodeModel->findDownloadedEpisodes(channelId);    
    if(!episodes.isEmpty()) {
        int r = KMessageBox::warningYesNoCancel(this, i18n("Delete channel \"%1\" and all its downloaded episodes?", d->m_channelModel->title(index)), i18n("Delete Channel"), KStandardGuiItem::yes(), KGuiItem("Keep Episodes"));
        if( r == KMessageBox::Cancel) {
            return;
        } else if (r == KMessageBox::No) {
            removeFiles = false;
        } else {
            removeFiles = true;
        }

        if(removeFiles) {
            foreach(const QModelIndex &episode, episodes) {
                success = KIO::NetAccess::del(d->m_originalEpisodeModel->videoPath(episode), this);
                kDebug() << (success ? "successfully" : "unsuccussefully") << "tried removing file" << d->m_originalEpisodeModel->videoPath(episode);
            }
            if(!success) {
                KMessageBox::error(this, "The following error occurred while deleting episodes: " + KIO::NetAccess::lastErrorString(), i18n("Error Deleting Channel"));
                return;
            }
        }
    }
    QModelIndexList channelEpisodes = d->m_originalEpisodeModel->findAllbyChannelId(channelId);
    d->m_originalEpisodeModel->removeEpisodes(channelEpisodes);

    d->m_channelModel->remove(index);
    submitChannelsAndEpisodes();
    initAutoRefreshChannels(); //re-initialise automatic refresh timer 
    QModelIndex channelViewIndex = d->m_channelModel->index(0, Channel_Id);
    QModelIndex episodeViewIndex = d->m_episodeModel->index(0, Episode_Id);
    d->m_channelView->setCurrentIndex(channelViewIndex);
    d->m_episodeView->setCurrentIndex(episodeViewIndex);
    //TODO: also delete channel folder?
    updateEpisodeView();
    d->m_channelView->setFocus();
}

// void MainWindow::editChannel()
// {
// }

void MainWindow::refreshChannel()
{
    QModelIndex index;
    index = d->m_channelView->currentIndex();
    if (!index.isValid()) {
        return;
    }
    refreshChannel(index);
}

/** This method sets up the timer for automatic channel refreshes. Channels are refreshed 
 * one by one rather than all at once. The timer calls autoRefreshChannels() at an interval 
 * that will make every channel refresh at it's desired interval.
 * In case the interval changes, such as when the refresh time setting is changed,
 * this method should be called again to re-initialize the timer with the new interval.
 * Please note that the d->m_autoRefreshChannelsTimer object should be created before this method 
 * is called.
 */
void MainWindow::initAutoRefreshChannels()
{
    d->m_autoRefreshChannelsTimer->stop(); //if timer is already running, stop it
    //only set timer if connected to a network
    if(Solid::Networking::status() == Solid::Networking::Connected || Solid::Networking::status() == Solid::Networking::Unknown) { 
        if(d->m_channelModel->rowCount()) { //only set timer if there are any channels
            //get the refresh single channel refresh time from the settings and convert it to milliseconds
            int singleChannelAutoRefreshInterval = Configure::channelRefreshInterval().toInt() * 60000;
            //do nothing if refresh interval is set to 0
            if(singleChannelAutoRefreshInterval == 0) {
                return;
            }
            //divide the calculated refresh time through the number of channels to get the desired interval between all automatic refreshes
            int autoRefreshInterval = singleChannelAutoRefreshInterval / (d->m_channelModel->rowCount()-1.0);
            d->m_currentAutoRefreshLine = 0;
            connect(d->m_autoRefreshChannelsTimer, SIGNAL(timeout()), this, SLOT(autoRefreshChannels()));
            d->m_autoRefreshChannelsTimer->start(autoRefreshInterval); //start counter with the calculated refresh time and calling autoRefreshChannels
        }
    }
}

/** This is the slot that is called  by the timer to automatically refresh channels. 
 * It works by refreshing one channel at each call, and cycling through all available channels.
 * Thus, to refresh all channels it would have to be called as many times as there are channels.
 */
void MainWindow::autoRefreshChannels()
{
    //get index for line saved in line counter
    QModelIndex channelToBeRefreshed = d->m_channelModel->index(d->m_currentAutoRefreshLine, Channel_Url);
    //refresh channel on line given by d->m_currentAutoRefreshLine
    refreshChannel(channelToBeRefreshed);
    
    if(d->m_currentAutoRefreshLine == (d->m_channelModel->rowCount()-1)) { //if at last line
        d->m_currentAutoRefreshLine = 0; //set line counter to first line
    } else {
        d->m_currentAutoRefreshLine++; //set line counter to the next line
    }
}

void MainWindow::refreshChannel(QModelIndex index)
{
    QString url;
    Loader* loader;
    url = d->m_channelModel->url(index);
    loader = Loader::create(url);
    connect(loader, 
        SIGNAL(loadingComplete(Channel *, ErrorCode, const QString)),
        this, SLOT(loadChannelResult(Channel*, ErrorCode, const QString)));
    d->m_channelModel->setData(index, Last_Loading_Started, LastLoadingRole);
    kDebug() << "refreshing channel with url " << KUrl(url).prettyUrl();
    loader->load();
}

void MainWindow::refreshAllChannels()
{
    QModelIndex index;
    QString url;

    for(int i=0; i<d->m_channelModel->rowCount(); ++i) {
        index = d->m_channelModel->index(i, Channel_Url);
        refreshChannel(index);
    }
}

void MainWindow::downloadEpisode()
{
    KUrl directory;
    QModelIndex index = d->m_episodeView->currentIndex();
    if (!index.isValid()) {
        kDebug() << "index invalid";
        return;
    }

    QString videoUrl = d->m_episodeModel->videoUrl(index);
    KUrl videoPath = calculateEpisodeVideoPath(d->m_episodeModel->mapToSource(index));

    //update and return if file exists already
    if(KIO::NetAccess::exists(videoPath, false, this)) {
        d->m_originalEpisodeModel->setStatus(d->m_episodeModel->mapToSource(index),  Episode_Status_Downloaded);
        d->m_originalEpisodeModel->setVideoPath(d->m_episodeModel->mapToSource(index), videoPath.pathOrUrl());
        submitEpisodes();
        return;
    }

    directory = videoPath.directory();
    if(!KIO::NetAccess::exists(directory, true, this)) {
        if(!KIO::NetAccess::exists(directory.upUrl(), true, this)) {
            if(!KIO::NetAccess::exists(directory.upUrl().upUrl(), true, this)) {
                KIO::NetAccess::mkdir(directory.upUrl().upUrl(), this);
            }
            KIO::NetAccess::mkdir(directory.upUrl(), this);
        }
        KIO::NetAccess::mkdir(directory, this);
    }
   
    kDebug() << "copying " << KUrl(videoUrl).prettyUrl() << " to " << videoPath.prettyUrl();

    KIO::Job* job = KIO::copy(KUrl(videoUrl), videoPath);
    connect(job, SIGNAL(result(KJob *)), this, SLOT(downloadEpisodeResult(KJob *)));
    connect(job, SIGNAL(percent(KJob *, unsigned long)), this, SLOT(updateDownloadStatus(KJob *, unsigned long)));
    connect(job, SIGNAL(speed(KJob *, unsigned long)), this, SLOT(updateDownloadSpeed(KJob *, unsigned long)));
    d->m_jobs->insert(job, videoUrl);

    d->m_episodeModel->setStatus(index, 0); //set download status to 0% downloadeed
    d->m_episodeModel->setVideoPath(index, videoPath.pathOrUrl() + ".part");

    submitEpisodes();
    updateActions();
}

void MainWindow::updateDownloadStatus(KJob* job, unsigned long percent)
{
    QModelIndex index;
    QString url = d->m_jobs->value(job);
    index = d->m_originalEpisodeModel->findByUrl(url);
    d->m_originalEpisodeModel->setStatus(index, (int)percent);
}

void MainWindow::updateDownloadSpeed(KJob* job, unsigned long speed)
{
    d->m_downloadSpeeds->remove(job);
    d->m_downloadSpeeds->insert(job, speed);

    QMap<KJob *, unsigned long>::const_iterator i = d->m_downloadSpeeds->constBegin();
    int sum = 0;
    while(i != d->m_downloadSpeeds->constEnd()) {
        sum += i.value();
        ++i;
    }
    int downloadSpeed = (sum/1024.0)+0.5; //change to kilobytes and round it
#if KDE_IS_VERSION(4,4,0)
    d->m_statusNotifier->setToolTip("videocatcher", "Videocatcher", QString("Download speed %1 KiB/s").arg(downloadSpeed));
#endif
}

void MainWindow::downloadEpisodeResult(KJob *job) 
{
    QString url = d->m_jobs->value(job);
    QModelIndex index = d->m_originalEpisodeModel->findByUrl(url);
    QString videopath = d->m_originalEpisodeModel->videoPath(index);
    if (job->error()) {
        d->m_originalEpisodeModel->setStatus(index, Episode_Status_Not_Downloaded);
        d->m_originalEpisodeModel->setVideoPath(index, "");

        if(KIO::NetAccess::exists(videopath, true, this)) {
            bool success = KIO::NetAccess::del(videopath,  this);
            if(!success) {
                //dont handle this error?
                qWarning() << "couldn't delete partially downloaded file";
            }
        }
    } else {
        d->m_originalEpisodeModel->setStatus(index, Episode_Status_Downloaded);
        QString newPath(videopath);
        newPath.chop(5); //remove the ".part" fomr the video path
        d->m_originalEpisodeModel->setVideoPath(index, newPath);
    }

#if KDE_IS_VERSION(4,4,0)
    //remove from download speed statistic
    d->m_downloadSpeeds->remove(job);
    if(d->m_downloadSpeeds->isEmpty()) {
        kDebug() << "d->m_downloadSpeeds is empty";
        d->m_statusNotifier->setToolTip("videocatcher", "Videocatcher", "Not downloading");
    }
#endif

    d->m_jobs->remove(job);

    submitEpisodes();
    updateEpisodeView();
    updateActions();
}

void MainWindow::cancelEpisodeDownload()
{
    QModelIndex index;
    index = d->m_episodeView->currentIndex();
    cancelEpisodeDownload(index);
}

void MainWindow::cancelEpisodeDownload(QModelIndex episode)
{
    QString url;
    QModelIndex index = d->m_episodeModel->mapToSource(episode);
 
    if (index.isValid()) {
        url = d->m_originalEpisodeModel->videoUrl(index);
        KJob* job;
        job = d->m_jobs->key(url);
        if(job) { //does download exist?
#if KDE_IS_VERSION(4,4,0)
            //remove from download speed statistic
            d->m_downloadSpeeds->remove(job);
            if(d->m_downloadSpeeds->isEmpty()) {
                d->m_statusNotifier->setToolTip("videocatcher", "Videocatcher", "Not downloading");
            }
#endif

            job->kill();
            d->m_jobs->remove(job);
        }

        d->m_originalEpisodeModel->setStatus(index, Episode_Status_Not_Downloaded);
        QString videopath = d->m_originalEpisodeModel->videoPath(index);
        d->m_originalEpisodeModel->setVideoPath(index, "");
 
        if(KIO::NetAccess::exists(videopath, true, this)) {
            bool success = KIO::NetAccess::del(videopath,  this);
            if(!success) {
                //dont handle this error?
                qWarning() << "couldn't delete partially downloaded file";
            }
        }
 
        d->m_episodeView->setCurrentIndex(d->m_episodeModel->mapFromSource(index));
        submitEpisodes();
        updateActions();
    }
}

void MainWindow::cancelAllEpisodeDownloads()
{
    QModelIndexList downloads;
    
    downloads = d->m_episodeModel->findDownloadingEpisodes();
    foreach(const QModelIndex &download, downloads)
    {
        cancelEpisodeDownload(download);
    }
}

/**
 * This method will play the currently selected episode. If the action which
 * called this slot has data set (with setData()) it  will be interpreted as 
 * a QString name of the preferred service to play the episode with. Otherwise 
 * the default service for the episodes mime type will be used.
 */
void MainWindow::playEpisode()
{
    QString preferredService;
    QAction *action = qobject_cast<QAction*>(sender());
    if(action) {
        preferredService = action->data().toString();
    }

    QModelIndex index = d->m_episodeView->currentIndex();
    if (!index.isValid()) {
        KMessageBox::information(this, i18n("No episode selected. Please select an episode."), i18n("Play Episode"));
        return;
    }

    if(preferredService.isEmpty()) {
        playEpisode(index);
    } else {
        playEpisode(index, preferredService);
    }
}

void MainWindow::streamEpisode()
{
    QModelIndex index = d->m_episodeView->currentIndex();
    if (!index.isValid()) {
        KMessageBox::information(this, i18n("No episode selected. Please select an episode."), i18n("Play Episode"));
        return;
    }

    QString path(d->m_episodeModel->videoUrl(index));
    QString videoStreamCommand = Configure::videoPlaybackCommand();
    kDebug() << "videoStreamCommand: " << videoStreamCommand << " " << QStringList(path);
    QProcess::startDetached(videoStreamCommand, QStringList(path));    
}

void MainWindow::deleteEpisode()
{
    QModelIndex index;

    index = d->m_episodeView->currentIndex();
    if (!index.isValid()) {
        return;
    }

    QString path(d->m_episodeModel->videoPath(index));
    QString url(d->m_episodeModel->videoUrl(index));

    //update and return if file does not exist
    if(!KIO::NetAccess::exists(path, false, this)) {
        d->m_originalEpisodeModel->setStatus(d->m_episodeModel->mapToSource(index),  Episode_Status_Not_Downloaded);
        d->m_originalEpisodeModel->setVideoPath(d->m_episodeModel->mapToSource(index), "");
        submitEpisodes();
        return;
    }

    KIO::Job* job = KIO::del(path);
    connect(job, SIGNAL(result(KJob *)), this, SLOT(deleteEpisodeResult(KJob *)));
    d->m_jobs->insert(job, url);

    submitEpisodes();
    updateActions();
    updateEpisodeView();
}

void MainWindow::deleteEpisodeResult(KJob *job)
{
    QString url = d->m_jobs->value(job);
    if (job->error()) {
        KMessageBox::error(0, job->errorString(), i18n("Error Deleting Episode"));
        //TODO: what to do with status and path?
        //return;
    }

    QModelIndex index = d->m_originalEpisodeModel->findByUrl(url);

    if(!index.isValid())
    {
        return;
    }

    d->m_originalEpisodeModel->setVideoPath(index, "");
    d->m_originalEpisodeModel->setStatus(index, Episode_Status_Not_Downloaded);

    submitEpisodes();
    updateActions();
    updateEpisodeView();
    d->m_jobs->remove(job);
}

void MainWindow::configure()
{  
    if(KConfigDialog::showDialog("configure")) {
      return;
    }
    d->m_oldVideoFolder = Configure::videoPath();
    KConfigDialog* dialog = new KConfigDialog(this, "configure", Configure::self());
    QWidget* generalSettingsDialog = new QWidget;
    d->m_configureWidget.setupUi(generalSettingsDialog);
    dialog->addPage(generalSettingsDialog, i18nc("Noun, referring to all options not belonging to a specific category", "General"), "configure");
    dialog->setAttribute(Qt::WA_DeleteOnClose);
    connect(dialog, SIGNAL(settingsChanged(const QString&)), this, SLOT(settingsChanged(const QString&)));
    dialog->show();
}

void MainWindow::settingsChanged(const QString& dialogName)
{
    QModelIndex index;
    
    if(dialogName == "configure") {
        initAutoRefreshChannels();
        KUrl newVideoFolder = Configure::videoPath();
        if(!newVideoFolder.equals(d->m_oldVideoFolder)) {
            //create destination folder
            if(!KIO::NetAccess::exists(newVideoFolder, true, this)) {
                KIO::NetAccess::mkdir(newVideoFolder, this);
            }
            
            //since episode path depends on channel path, channel path has to be changed first
            for(int i=0; i<d->m_channelModel->rowCount(); ++i) {
                index = d->m_channelModel->index(i, Channel_Path);
                KUrl channelPath(d->m_channelModel->path(index));
                
                if(KUrl(channelPath.directory()) != newVideoFolder && !channelPath.isEmpty()) {
                    KUrl newChannelPath = calculateChannelPath(index);
                    kDebug() << "copying " << channelPath.prettyUrl() << "to " << newChannelPath.prettyUrl();
                    kDebug() << "checking whether " << channelPath.prettyUrl() << "exists";
                    if(KIO::NetAccess::exists(channelPath, true, this)) {
                        KIO::Job* job = KIO::move(channelPath, newChannelPath);
                        job->exec();
                        if(job->error()) {
                            KMessageBox::error(this, i18n("Could not copy videos to new folder: %1", job->errorString()), i18n("Error"));
                        } else {
                            kDebug() << "moving succeeded, setting path to: " << newChannelPath.prettyUrl();
                            QString newChannelPathString = newChannelPath.pathOrUrl();
                            d->m_channelModel->setPath(index, newChannelPathString);
                        }
                    }
                }
            }
            
            for(int i=0; i<d->m_originalEpisodeModel->rowCount(); ++i) {
                QModelIndex originalIndex = d->m_originalEpisodeModel->index(i, Episode_Video_Path);
                if(!d->m_originalEpisodeModel->videoPath(originalIndex).isEmpty()) {
                    d->m_originalEpisodeModel->setVideoPath(originalIndex, calculateEpisodeVideoPath(originalIndex).pathOrUrl());
                }
            }
        }
    }

    submitChannelsAndEpisodes();    
}

/**
 * This method updates the play menu to display all registered applications for the given
 * mime type. If a custom command is specified in the applications configuration dialog
 * it will be appended to the menu.
 *
 * @param mimeType the mime type for which the registered applications should be shown.
 */
void MainWindow::updatePlayMenu(const QString &mimeType)
{
    for(int i=0; i < d->m_playMenuActions->size(); ++i) {
        kDebug() << "removing actions: " << d->m_playMenuActions->at(i);
            d->m_playMenu->removeAction(actionCollection()->action(d->m_playMenuActions->at(i)));
        }
        d->m_playMenuActions->clear();

        KService::List services = KMimeTypeTrader::self()->query(mimeType, "Application");

        KService::List::ConstIterator it = services.constBegin();
        const KService::List::ConstIterator end = services.constEnd();
        for(; it != end; ++it) {
            KAction *action;
            const KService::Ptr service = *it;
            kDebug() << "service: " << service->desktopEntryName();
            action = new KAction((*it)->name(), this);
            action->setIcon(KIcon((*it)->icon()));
            action->setData((*it)->desktopEntryName());
            connect(action, SIGNAL(triggered()), this, SLOT(playEpisode()));
            actionCollection()->addAction((*it)->desktopEntryName(), action);
            d->m_playMenu->addAction(action);
            *d->m_playMenuActions << (*it)->desktopEntryName();
        }

        // append a custom command playback option, if a command is configured in the settings
        QString videoPlaybackCommand = Configure::videoPlaybackCommand();
        if(!videoPlaybackCommand.isEmpty()) {
            KAction *action;
            kDebug() << "appending custom command: " << videoPlaybackCommand;
            action = new KAction(videoPlaybackCommand, this);
            //action->setIcon(KIcon((*it)->icon()));
            action->setData("customCommand");
            connect(action, SIGNAL(triggered()), this, SLOT(playEpisode()));
            actionCollection()->addAction("customCommand", action);
            d->m_playMenu->addAction(action);
            *d->m_playMenuActions << "customCommand";

        }

}

KUrl MainWindow::calculateEpisodeVideoPath(QModelIndex index)
{
    QString hostname;
    
    int channelId = d->m_originalEpisodeModel->channelId(index);
    QModelIndex channel = d->m_channelModel->findById(channelId);
    KUrl episodeVideoPath = d->m_channelModel->path(channel);
    //FIXME: if path doesn't exist fall back to calculateChannelPath();
    
    KUrl url = KUrl::fromPathOrUrl(d->m_originalEpisodeModel->videoUrl(index));
    episodeVideoPath.addPath(url.fileName());

    kDebug() << "returning episodeVideoPath: " << episodeVideoPath.pathOrUrl();
    return episodeVideoPath;
}

KUrl MainWindow::calculateChannelPath(QModelIndex channel)
{
    return calculateChannelPath(d->m_channelModel->webLink(channel));
}

KUrl MainWindow::calculateChannelPath(QString webLink)
{
    KUrl channelPath(Configure::videoPath());
    kDebug() << "channelPath" << channelPath.prettyUrl();
    
    //use host name only, remove www., ww1., ww2. etc
    KUrl websiteUrl(webLink);
    QString hostname = websiteUrl.host().remove(QRegExp("www*\\."));
    kDebug() << "hostname: " << hostname;
    channelPath.addPath(hostname);
    kDebug() << "returning channelPath: " << channelPath.prettyUrl();
    return channelPath;
}

void MainWindow::closeEvent(QCloseEvent *event) 
{
#if KDE_IS_VERSION(4,4,0)
    // if the event comes from out the application (close event) we hide the application window
    // if the event comes from the application (system shutdown) we say goodbye
    if(event->spontaneous()) {
        event->ignore();
        hide();
    }
#else
    QModelIndexList downloading = d->m_originalEpisodeModel->findDownloadingEpisodes();

    if(!downloading.empty()) {
        int r = KMessageBox::warningYesNo(this, i18n("All currently active downloads will be canceled. Are you sure you want to quit Videocatcher?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel());
        if( r == KMessageBox::Yes) {
            cancelAllEpisodeDownloads();
            event->accept();
        } else if (r == KMessageBox::No) {
            event->ignore();
        }
    }
#endif
}

// is only used if KDE version is 4.4 or greater (see definition of quit action)
void MainWindow::quit()
{
    QModelIndexList downloading = d->m_originalEpisodeModel->findDownloadingEpisodes();

    if(!downloading.empty()) {
        int r = KMessageBox::warningYesNo(this, i18n("All currently active downloads will be canceled. Are you sure you want to quit Videocatcher?"), i18n("Confirm Quit"), KStandardGuiItem::quit(), KStandardGuiItem::cancel());
        if( r == KMessageBox::Yes) {
            cancelAllEpisodeDownloads();
        } else if (r == KMessageBox::No) {
            return;
        }
    }

    qApp->quit();
}

void MainWindow::networkStatusChanged(Solid::Networking::Status status)
{
    kDebug() << "network status changed";
    //update automatic refresh, disables automatic refreshed if not connected to a network
    initAutoRefreshChannels();
    if(status == Solid::Networking::Connected || status == Solid::Networking::Unknown) {
        action("refresh")->setEnabled(true);
        action("refreshAll")->setEnabled(true);
        refreshAllChannels();
    } else {
        action("refresh")->setEnabled(false);
        action("refreshAll")->setEnabled(false);
    }
}

void MainWindow::episodeDoubleClicked(const QModelIndex &index) 
{
    int status;
    status = d->m_episodeModel->status(index);
    if(status == Episode_Status_Downloaded) {
        playEpisode(index);
    }
}

/**
 * This method plays a particular episode with an external application. If the external application
 * is not specified or cannot be found the systems default application for the files' mimetype will
 * be used.
 * 
 * @param row a QModelIndex representing the row of the episode that is to be played
 * @param preferredServiceName The name of the preferred service to play back the episode. 
 * In case the service is not found it will fall back to the default service. If "customCommand"
 * is passed, the episode will be played back using the shell command specified by the user
 * in the applications configuration dialog. This is in order to play back using applications which are not
 * registered as a service.
 */
void MainWindow::playEpisode(const QModelIndex row, const QString &preferredServiceName) {
    if(preferredServiceName == "customCommand") {
        QString videoPlaybackCommand = Configure::videoPlaybackCommand();
        kDebug() << "videoPlaybackCommand: " << videoPlaybackCommand << " " << QStringList(d->m_episodeModel->videoPath(row));
        QString command = videoPlaybackCommand + " " + KShell::quoteArg(d->m_episodeModel->videoPath(row));
        KRun::runCommand(command, this);
    } else {
        KRun *run = new KRun(d->m_episodeModel->videoPath(row), this);
        if (!preferredServiceName.isEmpty()) {
            run->setPreferredService(preferredServiceName);
        }
    }
}

void MainWindow::searchChanged(const QString & str)
{
    d->m_episodeModel->setTitleFilter(str);
}


void MainWindow::showEpisodeViewContextMenu(const QPoint& pos) {
    KMenu contextMenu;
    QAction* action;
    
    QPoint globalPos = d->m_episodeView->mapToGlobal(pos);

    action = actionCollection()->action("download");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    action = actionCollection()->action("cancel");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    action = actionCollection()->action("play");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    action = actionCollection()->action("delete");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    action = actionCollection()->action("stream");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    contextMenu.addSeparator();
    contextMenu.addAction("Copy URL To &Clipboard", this, SLOT(copyEpisodeURLToClipboard()));

    contextMenu.exec(globalPos);
}

void MainWindow::showChannelViewContextMenu(const QPoint& pos) {
    KMenu contextMenu;
    QAction* action;

    QPoint globalPos = d->m_channelView->mapToGlobal(pos);

    action = actionCollection()->action("refresh");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    action = actionCollection()->action("refreshAll");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    contextMenu.addSeparator();
    action = actionCollection()->action("add");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }
    action = actionCollection()->action("remove");
    if(action->isEnabled()) {
        contextMenu.addAction(action);
    }

    contextMenu.exec(globalPos);
}

void MainWindow::copyEpisodeURLToClipboard()
{
    QClipboard* clipboard = QApplication::clipboard();

    QModelIndex index = d->m_episodeView->currentIndex();
    if (!index.isValid()) {
        KMessageBox::information(this, i18n("No episode selected. Please select an episode."), i18n("Copy Episode URL to Clipboard"));
        return;
    }

    QString path(d->m_episodeModel->videoUrl(index));

    clipboard->setText(path);
}

//OPML import code taken from akregator
void MainWindow::importChannels()
{
    QString filename;
    KUrl url = KFileDialog::getOpenFileName(QDir::homePath(), i18n("*.opml"), this, "Import Channels");
    if(url.isEmpty()) {
        return;
    }

    bool isRemote = false;

    if (url.isLocalFile())
        filename = url.toLocalFile();
    else
    {
        isRemote = true;

        if (!KIO::NetAccess::download(url, filename, this) )
        {
            KMessageBox::error(this, KIO::NetAccess::lastErrorString() );
            return;
        }
    }

    QFile file(filename);
    if (file.open(QIODevice::ReadOnly))
    {
        // Read OPML feeds list and build QDom tree.
        QDomDocument doc;
        if (doc.setContent(file.readAll()))
            readFromOpml(doc);
        else
            KMessageBox::error(this, i18n("Could not import the file %1 (no valid OPML)", filename), i18n("OPML Parsing Error") );
    }
    else
        KMessageBox::error(this, i18n("The file %1 could not be read, check if it exists or if it is readable for the current user.", filename), i18n("Read Error"));

    if (isRemote)
        KIO::NetAccess::removeTempFile(filename);
}

bool MainWindow::readFromOpml(const QDomDocument& doc)
{
    QDomElement root = doc.documentElement();

    kDebug() <<"loading OPML feed" << root.tagName().toLower();

    if (root.tagName().toLower() != "opml")
    {
        return false;
    }
    QDomNode bodyNode = root.firstChild();

    while (!bodyNode.isNull() && bodyNode.toElement().tagName().toLower() != "body")
        bodyNode = bodyNode.nextSibling();


    if (bodyNode.isNull())
    {
        kDebug() << "Failed to acquire body node, markup broken?";
        return false;
    }

    QDomElement body = bodyNode.toElement();

    QDomNode i = body.firstChild();

    while( !i.isNull() )
    {
        parseChildNodes(i);
        i = i.nextSibling();
    }

    return true;
}

void MainWindow::parseChildNodes(QDomNode &node)
{
    QDomElement e = node.toElement(); // try to convert the node to an element.

    if( !e.isNull() )
    {

        if (e.hasAttribute("xmlUrl") || e.hasAttribute("xmlurl") || e.hasAttribute("xmlURL") )
        {
            QString xmlUrl = e.hasAttribute("xmlUrl") ? e.attribute("xmlUrl") : e.attribute("xmlurl");
            if (xmlUrl.isEmpty())
                xmlUrl = e.attribute("xmlURL");
    
            Loader* loader = Loader::create(xmlUrl);
            connect(loader, 
                SIGNAL(loadingComplete(Channel *, ErrorCode, const QString)),
                this, SLOT(loadChannelResult(Channel *, ErrorCode, const QString)));
            loader->load();
        }
        else
        {
            if (e.hasChildNodes())
            {
                QDomNode child = e.firstChild();
                while(!child.isNull())
                {
                    parseChildNodes(child);
                    child = child.nextSibling();
                }
            }
        }
    }
}

bool MainWindow::submitChannels()
{
    QModelIndex channelViewCurrentIndex;
    int episodeScrollBarPosition;
    QModelIndex episodeViewCurrentIndex;
    int channelScrollBarPosition;
    bool channelModelSubmit;

    channelViewCurrentIndex = d->m_channelView->currentIndex();
    channelScrollBarPosition = d->m_channelView->verticalScrollBar()->value();
    episodeViewCurrentIndex = d->m_episodeView->currentIndex();
    episodeScrollBarPosition = d->m_episodeView->verticalScrollBar()->value();
    d->m_channelModel->database().transaction();
    channelModelSubmit = d->m_channelModel->submitAll();
    if (channelModelSubmit) {
        d->m_channelModel->database().commit();
    } else {
        d->m_channelModel->database().rollback();
        kError() << QString("The database reported an error: %1").arg(d->m_channelModel->lastError().text());
    }
    //workaround for Qt SQLite bug
    while (d->m_originalEpisodeModel->canFetchMore()) {
        d->m_originalEpisodeModel->fetchMore();
    }
    d->m_channelView->setCurrentIndex(channelViewCurrentIndex);
    d->m_channelView->verticalScrollBar()->setValue(channelScrollBarPosition);
    d->m_episodeView->setCurrentIndex(episodeViewCurrentIndex);
    d->m_episodeView->verticalScrollBar()->setValue(episodeScrollBarPosition);
    return channelModelSubmit;
}

bool MainWindow::submitEpisodes()
{
    QModelIndex channelViewCurrentIndex;
    int episodeScrollBarPosition;
    QModelIndex episodeViewCurrentIndex;
    int channelScrollBarPosition;
    bool originalEpisodeModelSubmit;

    channelViewCurrentIndex = d->m_channelView->currentIndex();
    channelScrollBarPosition = d->m_channelView->verticalScrollBar()->value();
    episodeViewCurrentIndex = d->m_episodeView->currentIndex();
    episodeScrollBarPosition = d->m_episodeView->verticalScrollBar()->value();
    d->m_originalEpisodeModel->database().transaction();
    originalEpisodeModelSubmit = d->m_originalEpisodeModel->submitAll();
    if (originalEpisodeModelSubmit) {
        d->m_originalEpisodeModel->database().commit();
    } else {
        d->m_originalEpisodeModel->database().rollback();
        kError() << QString("The database reported an error: %1").arg(d->m_originalEpisodeModel->lastError().text());
    }
    //workaround for Qt SQLite bug
    while (d->m_originalEpisodeModel->canFetchMore()) {
        d->m_originalEpisodeModel->fetchMore();
    }
    d->m_channelView->setCurrentIndex(channelViewCurrentIndex);
    d->m_channelView->verticalScrollBar()->setValue(channelScrollBarPosition);
    d->m_episodeView->setCurrentIndex(episodeViewCurrentIndex);
    d->m_episodeView->verticalScrollBar()->setValue(episodeScrollBarPosition);
    return originalEpisodeModelSubmit;
}

bool MainWindow::submitChannelsAndEpisodes()
{
    QModelIndex episodeViewCurrentIndex;
    int episodeScrollBarPosition;
    QModelIndex channelViewCurrentIndex;
    int channelScrollBarPosition;
    bool channelModelSubmit;
    bool originalEpisodeModelSubmit;

    channelViewCurrentIndex = d->m_channelView->currentIndex();
    channelScrollBarPosition = d->m_channelView->verticalScrollBar()->value();
    episodeViewCurrentIndex = d->m_episodeView->currentIndex();
    episodeScrollBarPosition = d->m_episodeView->verticalScrollBar()->value();
    d->m_channelModel->database().transaction();
    d->m_originalEpisodeModel->database().transaction();
    channelModelSubmit = d->m_channelModel->submitAll();
    originalEpisodeModelSubmit = d->m_originalEpisodeModel->submitAll();
    if (channelModelSubmit && originalEpisodeModelSubmit) {
        d->m_channelModel->database().commit();
        d->m_originalEpisodeModel->database().commit();
    } else {
        d->m_channelModel->database().rollback();
        d->m_originalEpisodeModel->database().rollback();
        if(!channelModelSubmit) {
            kError() << QString("The database reported an error: %1").arg(d->m_channelModel->lastError().text());
        } else {
            kError() << QString("The database reported an error: %1").arg(d->m_originalEpisodeModel->lastError().text());
        }
    }
    //workaround for Qt SQLite bug
    while (d->m_originalEpisodeModel->canFetchMore()) {
        d->m_originalEpisodeModel->fetchMore();
    }
    d->m_channelView->setCurrentIndex(channelViewCurrentIndex);
    d->m_channelView->verticalScrollBar()->setValue(channelScrollBarPosition);
    d->m_episodeView->setCurrentIndex(episodeViewCurrentIndex);
    d->m_episodeView->verticalScrollBar()->setValue(episodeScrollBarPosition);
    return channelModelSubmit && originalEpisodeModelSubmit;
}

} //namespace Videocatcher
