/*
  kjobmodel.cpp

  This file is part of GammaRay, the Qt application inspection and
  manipulation tool.

  Copyright (C) 2012-2014 Klarälvdalens Datakonsult AB, a KDAB Group company, info@kdab.com
  Author: Volker Krause <volker.krause@kdab.com>

  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 "kjobmodel.h"
#include <core/util.h>

#include <KJob>

#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
#include <QApplication>
#else
#include <QGuiApplication>
#endif
#include <QPalette>

using namespace GammaRay;

/*
 * TODO
 * - show job hierarchy
 * - show all job info messages
 * - show progress information
 * - allow to cancel/suspend if job supports that
 * - track runtime
 * - allow to clear the model
 * - tooltips with additional information (capabilities etc)
 */

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

QVariant KJobModel::data(const QModelIndex &index, int role) const
{
  if (!index.isValid()) {
    return QVariant();
  }

  const KJobInfo &job = m_data.at(index.row());
  if (role == Qt::DisplayRole) {
    switch (index.column()) {
    case 0:
      return job.name;
    case 1:
      return job.type;
    case 2:
      return job.statusText;
    }
  } else if (role == Qt::ForegroundRole) {
    switch (job.state) {
    case KJobInfo::Finished:
    case KJobInfo::Deleted:
      return qApp->palette().brush(QPalette::Disabled, QPalette::Foreground);
    case KJobInfo::Error:
      return QVariant::fromValue<QColor>(Qt::red);
    case KJobInfo::Killed:
      return qApp->palette().link();
    default:
      return QVariant();
    }
  }

  return QVariant();
}

int KJobModel::columnCount(const QModelIndex &parent) const
{
  Q_UNUSED(parent);
  return 3;
}

int KJobModel::rowCount(const QModelIndex &parent) const
{
  if (parent.isValid()) {
    return 0;
  }
  return m_data.size();
}

QVariant KJobModel::headerData(int section, Qt::Orientation orientation, int role) const
{
  if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
    switch (section) {
    case 0:
      return tr("Job");
    case 1:
      return tr("Type");
    case 2:
      return tr("Status");
    }
  }
  return QAbstractItemModel::headerData(section, orientation, role);
}

void KJobModel::objectAdded(QObject *obj)
{
  KJob *job = qobject_cast<KJob*>(obj);
  if (!job) {
    return;
  }

  beginInsertRows(QModelIndex(), rowCount(), rowCount());
  KJobInfo jobInfo;
  jobInfo.job = job;
  connect(job, SIGNAL(result(KJob*)), SLOT(jobResult(KJob*)));
  connect(job, SIGNAL(finished(KJob*)), SLOT(jobFinished(KJob*)));
  connect(job, SIGNAL(infoMessage(KJob*,QString,QString)), SLOT(jobInfo(KJob*,QString)));
  jobInfo.name = obj->objectName().isEmpty() ? Util::addressToString(obj) : obj->objectName();
  jobInfo.type = obj->metaObject()->className();
  jobInfo.state = KJobInfo::Running;
  m_data.push_back(jobInfo);
  endInsertRows();
}

void KJobModel::objectRemoved(QObject *obj)
{
  const int pos = indexOfJob(obj);
  if (pos < 0) {
    return;
  }

  // KJob dtor emits finished, so this shouldn't happen, in theory
  // We however seem to get here for very short-lived jobs that emit before objectAdded()
  // is called (while we wait for the vtable to be complete), so we only see the result
  // of their deleteLater().
  if (m_data[pos].state == KJobInfo::Running) {
    m_data[pos].state = KJobInfo::Deleted;
    m_data[pos].statusText = tr("Deleted");
    emit dataChanged(index(pos, 0), index(pos, columnCount() - 1));
  }
}

void KJobModel::jobResult(KJob *job)
{
  const int pos = indexOfJob(job);
  if (pos < 0) {
    return;
  }

  if (job->error()) {
    m_data[pos].state = KJobInfo::Error;
    m_data[pos].statusText = job->errorString();
  } else {
    if (m_data.at(pos).state == KJobInfo::Killed) {
      // we can get finished() before result(), which is perfectly fine
      m_data[pos].statusText.clear();
    }
    m_data[pos].state = KJobInfo::Finished;
  }

  emit dataChanged(index(pos, 0), index(pos, columnCount()-1));
}

void KJobModel::jobFinished(KJob *obj)
{
  const int pos = indexOfJob(obj);
  if (pos < 0) {
    return;
  }

  if (m_data.at(pos).state == KJobInfo::Running) {
    m_data[pos].state = KJobInfo::Killed;
    m_data[pos].statusText = tr("Killed");
  }

  emit dataChanged(index(pos, 0), index(pos, columnCount()-1));
}

void KJobModel::jobInfo(KJob *job, const QString &plainMessage)
{
  const int pos = indexOfJob(job);
  if (pos < 0) {
    return;
  }

  if (m_data.at(pos).state == KJobInfo::Running) {
    m_data[pos].statusText = plainMessage;
  }

  emit dataChanged(index(pos, 0), index(pos, columnCount()-1));
}

int KJobModel::indexOfJob(QObject *obj) const
{
  for (int i = 0; i < m_data.size(); ++i) {
    if (m_data.at(i).job == obj) {
      return i;
    }
  }
  return -1;
}
